Skip to content

ffi: No interior NULs (part 1) - #8245

Draft
joshuamegnauth54 wants to merge 1 commit into
RustPython:mainfrom
joshuamegnauth54:host_env-windows-nuls
Draft

ffi: No interior NULs (part 1)#8245
joshuamegnauth54 wants to merge 1 commit into
RustPython:mainfrom
joshuamegnauth54:host_env-windows-nuls

Conversation

@joshuamegnauth54

@joshuamegnauth54 joshuamegnauth54 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

(This is WIP; the Windows code doesn't even compile yet 😆 Opening as placeholder + CodeRabbit lints )

Interior NULs is a security hazard for C-style strings. A NUL byte truncates a string which can lead the caller and callee to see two different strings. It can cause path traversal attacks where a path in Python looks complete but it is interpreted differently through FFI.

RustPython needs to handle this for some of its C-API as well as raw libc or Windows calls. Both Rust's standard library as well as Rustix handle interior NULs for us with CStrings, so this mostly affects Windows or areas where we have raw bytes that weren't checked by CString.

Finally, this PR is non-exhaustive. I will have to rely heavily on CodeRabbit to help lint it to ensure that interior NUL checks are only introduced for FFI and not outside of it. Most of RustPython seems to handle interior NULs already due to CString as well as WideCString.

AI disclosure: I relied on AI to ensure I'm solving this problem correctly. Mainly, I used it to check if the FFI functions I'm modifying need to handle interior NULs.

Sources:

Assisted-by: Codex:gpt-5.4

Summary

Summary by CodeRabbit

  • Bug Fixes
    • Improved Windows path, registry, networking, and environment-variable handling.
    • Embedded null characters in strings are now detected consistently and reported as clear Python errors instead of being silently mishandled.
    • Wide-character conversions now validate input and propagate failures safely.
    • Improved Windows file-extension detection for executable-related behavior.
  • Refactor
    • Standardized string conversion and null-termination behavior across platform integrations.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: b3843ff3-7426-4d19-9478-6b1866e16752

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Windows and WTF-8 FFI string handling now uses fallible, interior-NUL-aware conversions. Host APIs, registry bindings, path operations, ctypes helpers, and Python exception mapping were updated to propagate conversion failures.

Changes

Fallible encoding primitives

Layer / File(s) Summary
Wtf8 FFI encoding primitives
crates/wtf8/src/lib.rs
Adds encode_wide_ffi, EncodeWideForFfi, and InteriorNulError for validated UTF-16 FFI encoding.

Host conversion contracts

Layer / File(s) Summary
Host string conversion contracts
crates/host_env/src/ctypes.rs, crates/host_env/src/windows.rs, crates/host_env/src/fileutils.rs, crates/host_env/src/winapi.rs
Adds validated wide and byte-string helpers, fallible ToWideString methods, and updated Windows path-extension handling and safety documentation.

Windows API propagation

Layer / File(s) Summary
Windows API conversion propagation
crates/host_env/src/nt.rs, crates/host_env/src/overlapped.rs, crates/stdlib/src/overlapped.rs, crates/stdlib/src/_winapi.rs
Propagates wide-string conversion failures through path and address APIs, while retaining explicit-length handling for LCMapStringEx.

Registry integration

Layer / File(s) Summary
Windows registry wide-string boundaries
crates/host_env/src/winreg.rs, crates/vm/src/stdlib/winreg.rs
Registry host functions now consume WideCStr values, and VM bindings construct wide strings before calling them.

VM integration

Layer / File(s) Summary
VM error and ctypes integration
crates/vm/src/exceptions.rs, crates/vm/src/stdlib/_ctypes/*, crates/vm/src/stdlib/nt.rs
Maps InteriorNulError to Python ValueError and updates ctypes and path functions for fallible wide conversions.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PythonVM
  participant Wtf8
  participant HostWindowsAPI
  PythonVM->>Wtf8: encode_wide_ffi()
  Wtf8-->>PythonVM: UTF-16 units or InteriorNulError
  PythonVM->>HostWindowsAPI: call with validated WideCString
  HostWindowsAPI-->>PythonVM: API result or conversion error
Loading

Possibly related PRs

Suggested reviewers: youknowone, shaharnaveh

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the PR’s main theme: adding FFI handling to prevent interior NUL issues.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@joshuamegnauth54
joshuamegnauth54 marked this pull request as draft July 9, 2026 02:05

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/host_env/src/fileutils.rs (1)

80-97: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Add the missing Ok(()) and propagate the result

crates/host_env/src/fileutils.rs:76-97 currently ends with (), even though the signature returns Result<(), io::Error>, so this won’t compile. The crates/host_env/src/nt.rs:831,836,857 call sites also discard the returned Result, which drops path.to_wide()? failures; thread it through with ?.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/fileutils.rs` around lines 80 - 97, The helper in
fileutils.rs returns Result<(), io::Error> but currently falls off the end with
unit, so add an explicit Ok(()) after the permission update logic. Also make the
nt.rs callers that use this helper propagate its Result instead of ignoring it,
so failures from path.to_wide()? are not dropped; update the call sites around
the file metadata handling to use ? and thread the error upward.
crates/vm/src/stdlib/_ctypes/base.rs (1)

1596-1596: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Handle the fallible str_to_wchar_bytes result here. This call still destructures a Result; ? only works once InteriorNulError is mapped into PyBaseExceptionRef, so either add that conversion or convert the error explicitly at this call site.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/_ctypes/base.rs` at line 1596, The call in the wchar
conversion path is still destructuring a fallible `str_to_wchar_bytes` result
directly, so update the logic around `str_to_wchar_bytes` to properly handle its
`Result` before destructuring. In the `_ctypes::base` code path that builds the
wide string buffer, either add a conversion from `InteriorNulError` into
`PyBaseExceptionRef` so `?` can be used cleanly, or explicitly map the error at
the call site before extracting `holder` and `ptr`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/host_env/src/ctypes.rs`:
- Around line 542-546: The vec_into_bytes helper currently reinterprets the
original allocation with Vec::from_raw_parts, which can deallocate with the
wrong layout for wide-string buffers. Update vec_into_bytes in ctypes.rs to copy
the bytes out of the source Vec<T> instead of casting the allocation; keep the
existing size_of::<T>() guard, but replace the raw-parts reconstruction with a
safe byte copy approach so the returned Vec<u8> owns a correctly laid-out
allocation.

In `@crates/host_env/src/windows.rs`:
- Around line 413-417: The `to_wide` method in `windows.rs` is using `io::Error`
as a direct mapper, which won’t compile in this context. Update the
`WideCString::from_os_str(self)` error handling to use `io::Error::other`,
matching the pattern used by the other conversion methods, while keeping the
rest of `to_wide` unchanged.
- Around line 428-435: The Wtf8 implementation of ToWideString is incomplete and
uses the wrong encoder for the checked Result-based API. In the ToWideString
impl for Wtf8, add the missing to_wide_cstring method alongside to_wide and
to_wide_with_nul, and update all three methods to use encode_wide_ffi() so they
return Result<Vec<u16>, io::Error> correctly and reject interior NULs. Keep the
fix localized to the Wtf8 trait implementation in windows.rs.

In `@crates/vm/src/stdlib/_ctypes/function.rs`:
- Line 158: In the ctypes conversion helpers, the `?` operator is being used on
errors that do not automatically convert into `PyBaseExceptionRef`, so fix the
error handling in the functions that call
`rustpython_host_env::ctypes::utf16z_bytes` and `null_terminated_bytes` by
explicitly mapping those `InteriorNulError` and `NulError` values into the
Python exception type before propagating them. Apply the same change in the
corresponding logic in `function.rs` and `base.rs`, keeping the conversion
localized near the existing `utf16z_bytes` / `null_terminated_bytes` calls.

In `@crates/wtf8/src/lib.rs`:
- Around line 915-916: The doc comment on encode_wide_ffi names the wrong
encoding: it currently says the function converts to potentially ill-formed
UTF-8, but this helper returns potentially ill-formed UTF-16 wide code units
like encode_wide. Update the comment text for encode_wide_ffi to describe UTF-16
instead of UTF-8, keeping the note about checking for interior NULs.

---

Outside diff comments:
In `@crates/host_env/src/fileutils.rs`:
- Around line 80-97: The helper in fileutils.rs returns Result<(), io::Error>
but currently falls off the end with unit, so add an explicit Ok(()) after the
permission update logic. Also make the nt.rs callers that use this helper
propagate its Result instead of ignoring it, so failures from path.to_wide()?
are not dropped; update the call sites around the file metadata handling to use
? and thread the error upward.

In `@crates/vm/src/stdlib/_ctypes/base.rs`:
- Line 1596: The call in the wchar conversion path is still destructuring a
fallible `str_to_wchar_bytes` result directly, so update the logic around
`str_to_wchar_bytes` to properly handle its `Result` before destructuring. In
the `_ctypes::base` code path that builds the wide string buffer, either add a
conversion from `InteriorNulError` into `PyBaseExceptionRef` so `?` can be used
cleanly, or explicitly map the error at the call site before extracting `holder`
and `ptr`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: de55e068-6ea7-4cd4-8e9e-98e767127b8a

📥 Commits

Reviewing files that changed from the base of the PR and between 5c36d5c and ec9ad96.

📒 Files selected for processing (6)
  • crates/host_env/src/ctypes.rs
  • crates/host_env/src/fileutils.rs
  • crates/host_env/src/windows.rs
  • crates/vm/src/stdlib/_ctypes/base.rs
  • crates/vm/src/stdlib/_ctypes/function.rs
  • crates/wtf8/src/lib.rs

Comment thread crates/host_env/src/ctypes.rs Outdated
Comment thread crates/host_env/src/windows.rs Outdated
Comment thread crates/host_env/src/windows.rs
Comment thread crates/vm/src/stdlib/_ctypes/function.rs Outdated
Comment thread crates/wtf8/src/lib.rs Outdated
@joshuamegnauth54
joshuamegnauth54 marked this pull request as ready for review July 9, 2026 18:57
@joshuamegnauth54
joshuamegnauth54 force-pushed the host_env-windows-nuls branch from ec9ad96 to ee369e2 Compare July 9, 2026 18:58
@joshuamegnauth54
joshuamegnauth54 marked this pull request as draft July 9, 2026 18:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/host_env/src/ctypes.rs (1)

1095-1097: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add tests for the new fallible null_terminated_bytes.

This is a security-critical function (interior NUL detection for FFI), but the test module has no coverage for it. Consider adding tests for: valid input without NULs, input containing an interior NUL (should return Err(NulError)), and empty input.

🧪 Suggested tests
#[test]
fn null_terminated_bytes_valid() {
    assert_eq!(
        null_terminated_bytes(b"hello").unwrap(),
        b"hello\0"
    );
}

#[test]
fn null_terminated_bytes_interior_nul_rejected() {
    assert!(null_terminated_bytes(b"hel\0lo").is_err());
}

#[test]
fn null_terminated_bytes_empty() {
    assert_eq!(null_terminated_bytes(b"").unwrap(), b"\0");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/ctypes.rs` around lines 1095 - 1097, Add test coverage
for the fallible null_terminated_bytes helper in ctypes.rs, since it now
performs FFI-safe interior NUL validation via CString::new. Extend the existing
test module with cases for valid non-NUL input, input containing an interior NUL
that must return Err(NulError), and empty input; use the null_terminated_bytes
function name directly so the tests clearly target the new behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/host_env/src/ctypes.rs`:
- Around line 2-3: The `CString` import in `ctypes.rs` is incorrectly gated with
`#[cfg(unix)]` even though `null_terminated_bytes` uses `CString`
unconditionally and is called from `ensure_z_null_terminated` in `base.rs` and
`conv_param` in `function.rs` on all targets. Remove the unix-only cfg from the
`CString` import so `null_terminated_bytes` can compile on non-unix platforms as
well, and make sure the identifier references in `ctypes.rs` remain valid
without any platform-specific gating.

---

Nitpick comments:
In `@crates/host_env/src/ctypes.rs`:
- Around line 1095-1097: Add test coverage for the fallible
null_terminated_bytes helper in ctypes.rs, since it now performs FFI-safe
interior NUL validation via CString::new. Extend the existing test module with
cases for valid non-NUL input, input containing an interior NUL that must return
Err(NulError), and empty input; use the null_terminated_bytes function name
directly so the tests clearly target the new behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 2c9c6278-53c9-4285-ac5d-4a5a4b8a716e

📥 Commits

Reviewing files that changed from the base of the PR and between ec9ad96 and ee369e2.

📒 Files selected for processing (6)
  • crates/host_env/src/ctypes.rs
  • crates/host_env/src/fileutils.rs
  • crates/host_env/src/windows.rs
  • crates/vm/src/stdlib/_ctypes/base.rs
  • crates/vm/src/stdlib/_ctypes/function.rs
  • crates/wtf8/src/lib.rs
💤 Files with no reviewable changes (1)
  • crates/wtf8/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/host_env/src/fileutils.rs
  • crates/vm/src/stdlib/_ctypes/function.rs
  • crates/vm/src/stdlib/_ctypes/base.rs
  • crates/host_env/src/windows.rs

Comment thread crates/host_env/src/ctypes.rs Outdated
@joshuamegnauth54
joshuamegnauth54 marked this pull request as ready for review July 12, 2026 00:52
@joshuamegnauth54
joshuamegnauth54 marked this pull request as draft July 12, 2026 00:52
@joshuamegnauth54
joshuamegnauth54 force-pushed the host_env-windows-nuls branch 2 times, most recently from b3816bb to 9b0f70c Compare July 14, 2026 17:39
@joshuamegnauth54
joshuamegnauth54 marked this pull request as ready for review July 15, 2026 17:55
@joshuamegnauth54
joshuamegnauth54 force-pushed the host_env-windows-nuls branch 2 times, most recently from 08865bf to b7ce43b Compare July 15, 2026 17:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/stdlib/src/overlapped.rs (1)

213-225: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Map InteriorNulError to a Python exception explicitly.

Type error: collect::<Result<_, _>>() produces an InteriorNulError on failure, but ? attempts to implicitly convert it to PyBaseExceptionRef (the error type of PyResult). Since there is no automatic From conversion, this will cause a compilation error. You must explicitly map the error.

🐛 Proposed fix
             2 => {
                 // IPv4: (host, port)
                 let host: PyStrRef = addr_obj[0].clone().try_into_value(vm)?;
                 let port: u16 = addr_obj[1].clone().try_to_value(vm)?;
-                let host_wide: Vec<u16> =
-                    host.as_wtf8().encode_wide_ffi().collect::<Result<_, _>>()?;
+                let host_wide: Vec<u16> = host.as_wtf8()
+                    .encode_wide_ffi()
+                    .collect::<Result<_, _>>()
+                    .map_err(|e| e.to_pyexception(vm))?;
                 host_overlapped::parse_address_v4_wide(&host_wide, port)
                     .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm))
             }
             4 => {
                 // IPv6: (host, port, flowinfo, scope_id)
                 let host: PyStrRef = addr_obj[0].clone().try_into_value(vm)?;
                 let port: u16 = addr_obj[1].clone().try_to_value(vm)?;
                 let flowinfo: u32 = addr_obj[2].clone().try_to_value(vm)?;
                 let scope_id: u32 = addr_obj[3].clone().try_to_value(vm)?;
-                let host_wide: Vec<u16> =
-                    host.as_wtf8().encode_wide_ffi().collect::<Result<_, _>>()?;
+                let host_wide: Vec<u16> = host.as_wtf8()
+                    .encode_wide_ffi()
+                    .collect::<Result<_, _>>()
+                    .map_err(|e| e.to_pyexception(vm))?;
                 host_overlapped::parse_address_v6_wide(&host_wide, port, flowinfo, scope_id)
                     .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm))
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/stdlib/src/overlapped.rs` around lines 213 - 225, Update the IPv4 and
IPv6 host encoding in the address-parsing function to explicitly map
`InteriorNulError` from `encode_wide_ffi().collect::<Result<_, _>>()` into the
expected Python exception type before using `?`; preserve the existing
`parse_address_v4_wide` and IPv6 parsing flow.
♻️ Duplicate comments (1)
crates/host_env/src/windows.rs (1)

426-427: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Complete to_wide_cstring; WideCString::from_ cannot compile.

Collect the checked UTF-16 units and construct a WideCString with the constructor supported by the repository’s pinned widestring version.

As per coding guidelines, follow default rustfmt style and run cargo clippy, fixing introduced warnings before completion.

#!/bin/bash
set -euo pipefail

rg -n -C3 'name = "widestring"|widestring\s*=' Cargo.lock Cargo.toml
rg -n -C3 'WideCString::from_(vec|vec_with_nul|os_str|str)' --type rust .
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/windows.rs` around lines 426 - 427, Complete the
to_wide_cstring method by collecting the validated UTF-16 units and constructing
WideCString with a constructor available in the repository’s pinned widestring
version, replacing the incomplete WideCString::from_ call. Apply default rustfmt
formatting and run cargo clippy, resolving any warnings introduced by this
change.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/host_env/src/ctypes.rs`:
- Around line 533-535: Update wchar_null_terminated_bytes to use
encode_wide_ffi() instead of casting code points directly to WChar, preserving
non-BMP characters as surrogate pairs on 16-bit targets while retaining the
existing null-terminated byte iteration behavior.

In `@crates/host_env/src/nt.rs`:
- Line 220: Update downstream callers of `access`, `test_file_type_by_name`, and
`test_file_exists_by_name` to handle their `Result<bool, io::Error>` returns. In
the VM stdlib `access` binding, map I/O errors to the appropriate Python
exception; for the internal file-testing helpers, convert errors to `false` with
`.unwrap_or(false)` while preserving existing boolean behavior.
- Line 986: Update the return statement in the surrounding function to return
the boolean value as a successful Result, matching its Result<bool, io::Error>
return type; preserve the existing false outcome.

In `@crates/host_env/src/windows.rs`:
- Around line 403-405: Update the remaining Windows callers of
ToWideString::to_wide_with_nul and ToWideString::to_wide_cstring to handle their
Result values explicitly. Propagate or otherwise handle conversion errors in the
callers in windows.rs, winsound, and winreg, preserving each call site’s
existing success behavior and avoiding infallible assumptions.

In `@crates/host_env/src/winreg.rs`:
- Around line 512-515: Update expand_environment_strings to stop calling
into_vec_with_nul on the borrowed input; pass input.as_ptr() directly to
ExpandEnvironmentStringsW and remove the unnecessary wide_input allocation while
preserving the existing expansion behavior.
- Around line 353-355: Update the error mapping in the wide_sub_key conversion
within the relevant registry query flow so map_err returns a concrete
QueryStringError::Utf16 instance containing the conversion error, rather than
the tuple variant constructor. Preserve the existing QueryStringError return
path and propagate the original FromUtf16Error value.
- Around line 464-471: Update set_default_value to explicitly map the
to_wide_cstring ContainsNul failure into io::Error before using ?, then update
the SetValue caller to handle the Result<u32, io::Error> contract instead of
comparing the result directly with zero; preserve the existing success and
Windows error-code behavior.

In `@crates/vm/src/stdlib/_ctypes/function.rs`:
- Around line 779-780: Validate the function symbol name before constructing the
terminated string in the surrounding function of the lookup_function_symbol_addr
call. Reject names containing interior NUL bytes and return the existing error
path, then preserve the current format!("{name}\0") lookup flow for valid names.

In `@crates/vm/src/stdlib/winreg.rs`:
- Around line 844-846: Update the error mapping in the `WideCString::from_str`
conversion within the surrounding winreg function to pass the available `vm`
context to `to_pyexception`, matching the existing `expand_environment_strings`
mapping. Leave the successful conversion and environment expansion behavior
unchanged.

In `@crates/wtf8/src/lib.rs`:
- Around line 1532-1548: Reject every source NUL immediately in the encoder
iterator, returning InteriorNulError and setting the iterator’s completion state
so iteration cannot resume. Apply this change at crates/wtf8/src/lib.rs lines
1532-1548 and crates/host_env/src/ctypes.rs lines 545-557, preserving the
synthesized terminator behavior while removing the scan-and-accept path.
- Around line 1557-1560: Update the size_hint method to account for the
iterator’s possible terminator output and early termination on interior-NUL
errors; do not forward self.iter.size_hint() unchanged. Return bounds that never
overstate the minimum or maximum number of items the iterator can emit,
preserving the appropriate unbounded case.

---

Outside diff comments:
In `@crates/stdlib/src/overlapped.rs`:
- Around line 213-225: Update the IPv4 and IPv6 host encoding in the
address-parsing function to explicitly map `InteriorNulError` from
`encode_wide_ffi().collect::<Result<_, _>>()` into the expected Python exception
type before using `?`; preserve the existing `parse_address_v4_wide` and IPv6
parsing flow.

---

Duplicate comments:
In `@crates/host_env/src/windows.rs`:
- Around line 426-427: Complete the to_wide_cstring method by collecting the
validated UTF-16 units and constructing WideCString with a constructor available
in the repository’s pinned widestring version, replacing the incomplete
WideCString::from_ call. Apply default rustfmt formatting and run cargo clippy,
resolving any warnings introduced by this change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: b91be2b8-7920-42f4-ab35-75fc3a170cc4

📥 Commits

Reviewing files that changed from the base of the PR and between ee369e2 and b7ce43b.

📒 Files selected for processing (18)
  • crates/host_env/src/ctypes.rs
  • crates/host_env/src/fileutils.rs
  • crates/host_env/src/nt.rs
  • crates/host_env/src/overlapped.rs
  • crates/host_env/src/winapi.rs
  • crates/host_env/src/windows.rs
  • crates/host_env/src/winreg.rs
  • crates/stdlib/src/overlapped.rs
  • crates/vm/src/exceptions.rs
  • crates/vm/src/stdlib/_ctypes/array.rs
  • crates/vm/src/stdlib/_ctypes/base.rs
  • crates/vm/src/stdlib/_ctypes/function.rs
  • crates/vm/src/stdlib/_ctypes/pointer.rs
  • crates/vm/src/stdlib/_ctypes/simple.rs
  • crates/vm/src/stdlib/_winapi.rs
  • crates/vm/src/stdlib/nt.rs
  • crates/vm/src/stdlib/winreg.rs
  • crates/wtf8/src/lib.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/stdlib/src/overlapped.rs (1)

213-225: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Map InteriorNulError to a Python exception explicitly.

Type error: collect::<Result<_, _>>() produces an InteriorNulError on failure, but ? attempts to implicitly convert it to PyBaseExceptionRef (the error type of PyResult). Since there is no automatic From conversion, this will cause a compilation error. You must explicitly map the error.

🐛 Proposed fix
             2 => {
                 // IPv4: (host, port)
                 let host: PyStrRef = addr_obj[0].clone().try_into_value(vm)?;
                 let port: u16 = addr_obj[1].clone().try_to_value(vm)?;
-                let host_wide: Vec<u16> =
-                    host.as_wtf8().encode_wide_ffi().collect::<Result<_, _>>()?;
+                let host_wide: Vec<u16> = host.as_wtf8()
+                    .encode_wide_ffi()
+                    .collect::<Result<_, _>>()
+                    .map_err(|e| e.to_pyexception(vm))?;
                 host_overlapped::parse_address_v4_wide(&host_wide, port)
                     .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm))
             }
             4 => {
                 // IPv6: (host, port, flowinfo, scope_id)
                 let host: PyStrRef = addr_obj[0].clone().try_into_value(vm)?;
                 let port: u16 = addr_obj[1].clone().try_to_value(vm)?;
                 let flowinfo: u32 = addr_obj[2].clone().try_to_value(vm)?;
                 let scope_id: u32 = addr_obj[3].clone().try_to_value(vm)?;
-                let host_wide: Vec<u16> =
-                    host.as_wtf8().encode_wide_ffi().collect::<Result<_, _>>()?;
+                let host_wide: Vec<u16> = host.as_wtf8()
+                    .encode_wide_ffi()
+                    .collect::<Result<_, _>>()
+                    .map_err(|e| e.to_pyexception(vm))?;
                 host_overlapped::parse_address_v6_wide(&host_wide, port, flowinfo, scope_id)
                     .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm))
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/stdlib/src/overlapped.rs` around lines 213 - 225, Update the IPv4 and
IPv6 host encoding in the address-parsing function to explicitly map
`InteriorNulError` from `encode_wide_ffi().collect::<Result<_, _>>()` into the
expected Python exception type before using `?`; preserve the existing
`parse_address_v4_wide` and IPv6 parsing flow.
♻️ Duplicate comments (1)
crates/host_env/src/windows.rs (1)

426-427: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Complete to_wide_cstring; WideCString::from_ cannot compile.

Collect the checked UTF-16 units and construct a WideCString with the constructor supported by the repository’s pinned widestring version.

As per coding guidelines, follow default rustfmt style and run cargo clippy, fixing introduced warnings before completion.

#!/bin/bash
set -euo pipefail

rg -n -C3 'name = "widestring"|widestring\s*=' Cargo.lock Cargo.toml
rg -n -C3 'WideCString::from_(vec|vec_with_nul|os_str|str)' --type rust .
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/windows.rs` around lines 426 - 427, Complete the
to_wide_cstring method by collecting the validated UTF-16 units and constructing
WideCString with a constructor available in the repository’s pinned widestring
version, replacing the incomplete WideCString::from_ call. Apply default rustfmt
formatting and run cargo clippy, resolving any warnings introduced by this
change.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/host_env/src/ctypes.rs`:
- Around line 533-535: Update wchar_null_terminated_bytes to use
encode_wide_ffi() instead of casting code points directly to WChar, preserving
non-BMP characters as surrogate pairs on 16-bit targets while retaining the
existing null-terminated byte iteration behavior.

In `@crates/host_env/src/nt.rs`:
- Line 220: Update downstream callers of `access`, `test_file_type_by_name`, and
`test_file_exists_by_name` to handle their `Result<bool, io::Error>` returns. In
the VM stdlib `access` binding, map I/O errors to the appropriate Python
exception; for the internal file-testing helpers, convert errors to `false` with
`.unwrap_or(false)` while preserving existing boolean behavior.
- Line 986: Update the return statement in the surrounding function to return
the boolean value as a successful Result, matching its Result<bool, io::Error>
return type; preserve the existing false outcome.

In `@crates/host_env/src/windows.rs`:
- Around line 403-405: Update the remaining Windows callers of
ToWideString::to_wide_with_nul and ToWideString::to_wide_cstring to handle their
Result values explicitly. Propagate or otherwise handle conversion errors in the
callers in windows.rs, winsound, and winreg, preserving each call site’s
existing success behavior and avoiding infallible assumptions.

In `@crates/host_env/src/winreg.rs`:
- Around line 512-515: Update expand_environment_strings to stop calling
into_vec_with_nul on the borrowed input; pass input.as_ptr() directly to
ExpandEnvironmentStringsW and remove the unnecessary wide_input allocation while
preserving the existing expansion behavior.
- Around line 353-355: Update the error mapping in the wide_sub_key conversion
within the relevant registry query flow so map_err returns a concrete
QueryStringError::Utf16 instance containing the conversion error, rather than
the tuple variant constructor. Preserve the existing QueryStringError return
path and propagate the original FromUtf16Error value.
- Around line 464-471: Update set_default_value to explicitly map the
to_wide_cstring ContainsNul failure into io::Error before using ?, then update
the SetValue caller to handle the Result<u32, io::Error> contract instead of
comparing the result directly with zero; preserve the existing success and
Windows error-code behavior.

In `@crates/vm/src/stdlib/_ctypes/function.rs`:
- Around line 779-780: Validate the function symbol name before constructing the
terminated string in the surrounding function of the lookup_function_symbol_addr
call. Reject names containing interior NUL bytes and return the existing error
path, then preserve the current format!("{name}\0") lookup flow for valid names.

In `@crates/vm/src/stdlib/winreg.rs`:
- Around line 844-846: Update the error mapping in the `WideCString::from_str`
conversion within the surrounding winreg function to pass the available `vm`
context to `to_pyexception`, matching the existing `expand_environment_strings`
mapping. Leave the successful conversion and environment expansion behavior
unchanged.

In `@crates/wtf8/src/lib.rs`:
- Around line 1532-1548: Reject every source NUL immediately in the encoder
iterator, returning InteriorNulError and setting the iterator’s completion state
so iteration cannot resume. Apply this change at crates/wtf8/src/lib.rs lines
1532-1548 and crates/host_env/src/ctypes.rs lines 545-557, preserving the
synthesized terminator behavior while removing the scan-and-accept path.
- Around line 1557-1560: Update the size_hint method to account for the
iterator’s possible terminator output and early termination on interior-NUL
errors; do not forward self.iter.size_hint() unchanged. Return bounds that never
overstate the minimum or maximum number of items the iterator can emit,
preserving the appropriate unbounded case.

---

Outside diff comments:
In `@crates/stdlib/src/overlapped.rs`:
- Around line 213-225: Update the IPv4 and IPv6 host encoding in the
address-parsing function to explicitly map `InteriorNulError` from
`encode_wide_ffi().collect::<Result<_, _>>()` into the expected Python exception
type before using `?`; preserve the existing `parse_address_v4_wide` and IPv6
parsing flow.

---

Duplicate comments:
In `@crates/host_env/src/windows.rs`:
- Around line 426-427: Complete the to_wide_cstring method by collecting the
validated UTF-16 units and constructing WideCString with a constructor available
in the repository’s pinned widestring version, replacing the incomplete
WideCString::from_ call. Apply default rustfmt formatting and run cargo clippy,
resolving any warnings introduced by this change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: b91be2b8-7920-42f4-ab35-75fc3a170cc4

📥 Commits

Reviewing files that changed from the base of the PR and between ee369e2 and b7ce43b.

📒 Files selected for processing (18)
  • crates/host_env/src/ctypes.rs
  • crates/host_env/src/fileutils.rs
  • crates/host_env/src/nt.rs
  • crates/host_env/src/overlapped.rs
  • crates/host_env/src/winapi.rs
  • crates/host_env/src/windows.rs
  • crates/host_env/src/winreg.rs
  • crates/stdlib/src/overlapped.rs
  • crates/vm/src/exceptions.rs
  • crates/vm/src/stdlib/_ctypes/array.rs
  • crates/vm/src/stdlib/_ctypes/base.rs
  • crates/vm/src/stdlib/_ctypes/function.rs
  • crates/vm/src/stdlib/_ctypes/pointer.rs
  • crates/vm/src/stdlib/_ctypes/simple.rs
  • crates/vm/src/stdlib/_winapi.rs
  • crates/vm/src/stdlib/nt.rs
  • crates/vm/src/stdlib/winreg.rs
  • crates/wtf8/src/lib.rs
🛑 Comments failed to post (11)
crates/host_env/src/ctypes.rs (1)

533-535: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C3 '\b(type WChar|WCHAR_SIZE|wchar_to_bytes|wchar_null_terminated_bytes)\b' \
  crates/host_env/src/ctypes.rs
rg -n -C3 '\bencode_wide(_ffi)?\s*\(' crates/host_env/src/ctypes.rs crates/wtf8/src/lib.rs

Repository: RustPython/RustPython

Length of output: 5652


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '520,590p' crates/host_env/src/ctypes.rs
printf '\n---\n'
sed -n '900,940p' crates/wtf8/src/lib.rs
printf '\n---\n'
sed -n '1,220p' crates/wtf8/src/lib.rs | sed -n '1,40p'

Repository: RustPython/RustPython

Length of output: 5517


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
text = Path('crates/wtf8/src/lib.rs').read_text()
for needle in ['struct EncodeWide', 'impl Iterator for EncodeWide', 'struct EncodeWideForFfi', 'impl Iterator for EncodeWideForFfi']:
    idx = text.find(needle)
    print('\n###', needle, idx)
    if idx != -1:
        start = text.rfind('\n', 0, idx-200)
        end = text.find('\n\n', idx)
        print(text[idx:idx+1400])
PY

Repository: RustPython/RustPython

Length of output: 5913


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C4 'EncodeWide|EncodeWideForFfi|encode_wide\(' crates/wtf8/src/lib.rs

Repository: RustPython/RustPython

Length of output: 2350


Use encode_wide_ffi() here. Casting code points directly to WChar drops non-BMP characters on 16-bit targets instead of emitting surrogate pairs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/ctypes.rs` around lines 533 - 535, Update
wchar_null_terminated_bytes to use encode_wide_ffi() instead of casting code
points directly to WChar, preserving non-BMP characters as surrogate pairs on
16-bit targets while retaining the existing null-terminated byte iteration
behavior.
crates/host_env/src/nt.rs (2)

220-220: 🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift

Update downstream callers to handle the new Result return type.

Changing the return type of access (as well as test_file_type_by_name and test_file_exists_by_name) to Result<bool, io::Error> causes compilation errors in downstream consumers like crates/vm/src/stdlib/nt.rs, which still expect a bool.

You need to update the callers to handle the Result. For instance, in crates/vm/src/stdlib/nt.rs, map the error to a Python exception for access, and use .unwrap_or(false) for the internal file testing helpers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/nt.rs` at line 220, Update downstream callers of
`access`, `test_file_type_by_name`, and `test_file_exists_by_name` to handle
their `Result<bool, io::Error>` returns. In the VM stdlib `access` binding, map
I/O errors to the appropriate Python exception; for the internal file-testing
helpers, convert errors to `false` with `.unwrap_or(false)` while preserving
existing boolean behavior.

986-986: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Wrap the return value in Ok.

Type mismatch: the function returns Result<bool, io::Error>, so returning (false) will cause a compilation error.

🐛 Proposed fix
-                return (false);
+                return Ok(false);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

                return Ok(false);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/nt.rs` at line 986, Update the return statement in the
surrounding function to return the boolean value as a successful Result,
matching its Result<bool, io::Error> return type; preserve the existing false
outcome.
crates/host_env/src/windows.rs (1)

403-405: 🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C4 --type rust '\.to_wide_(with_nul|cstring)\s*\(' .

Repository: RustPython/RustPython

Length of output: 47727


🏁 Script executed:

#!/bin/bash
set -euo pipefail

wc -l crates/host_env/src/windows.rs
sed -n '1,260p' crates/host_env/src/windows.rs
printf '\n---SNIP---\n'
sed -n '260,520p' crates/host_env/src/windows.rs

Repository: RustPython/RustPython

Length of output: 13806


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show all call sites that may still rely on the old infallible behavior.
rg -n -C2 --type rust 'to_wide_with_nul\(\)|to_wide_cstring\(' crates/host_env crates/vm

Repository: RustPython/RustPython

Length of output: 30715


Propagate the fallible wide-string conversions to the remaining Windows callers. Several sites still treat to_wide_with_nul() / to_wide_cstring() as infallible (crates/host_env/src/windows.rs:157,184, crates/vm/src/stdlib/winsound.rs:148, crates/vm/src/stdlib/winreg.rs:744,763), so the Windows build still breaks until each one handles the Result.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/windows.rs` around lines 403 - 405, Update the remaining
Windows callers of ToWideString::to_wide_with_nul and
ToWideString::to_wide_cstring to handle their Result values explicitly.
Propagate or otherwise handle conversion errors in the callers in windows.rs,
winsound, and winreg, preserving each call site’s existing success behavior and
avoiding infallible assumptions.
crates/host_env/src/winreg.rs (3)

353-355: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Pass an error instance rather than a variant constructor.

Type error: QueryStringError::Utf16 is a tuple variant that expects a FromUtf16Error argument. Passing the variant name without an argument to map_err returns a function pointer rather than an error instance, causing a compilation failure. Consider mapping to an appropriate Windows error code instead.

🐛 Proposed fix
         let wide_sub_key = sub_key
             .to_wide_cstring()
-            .map_err(|_| QueryStringError::Utf16)?;
+            .map_err(|_| QueryStringError::Code(windows_sys::Win32::Foundation::ERROR_INVALID_DATA))?;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        let wide_sub_key = sub_key
            .to_wide_cstring()
            .map_err(|_| QueryStringError::Code(windows_sys::Win32::Foundation::ERROR_INVALID_DATA))?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/winreg.rs` around lines 353 - 355, Update the error
mapping in the wide_sub_key conversion within the relevant registry query flow
so map_err returns a concrete QueryStringError::Utf16 instance containing the
conversion error, rather than the tuple variant constructor. Preserve the
existing QueryStringError return path and propagate the original FromUtf16Error
value.

464-471: 🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift

Map ContainsNul error and update downstream consumers.

Two issues exist here:

  1. sub_key.to_wide_cstring() returns Result<WideCString, ContainsNul<u16>>, which cannot be implicitly converted to io::Error via ?. You must explicitly map the error.
  2. The signature change of set_default_value to Result<u32, io::Error> breaks the downstream caller SetValue in crates/vm/src/stdlib/winreg.rs (which expects a raw u32 error code to perform if res == 0). You will need to update the caller to match the new Result.
🐛 Proposed fix for the local type error
 pub fn set_default_value(
     hkey: Registry::HKEY,
     sub_key: &OsStr,
     typ: u32,
     value: &OsStr,
 ) -> Result<u32, io::Error> {
     let child_key = if !sub_key.is_empty() {
-        let wide_sub_key = sub_key.to_wide_cstring()?;
+        let wide_sub_key = sub_key.to_wide_cstring().map_err(io::Error::other)?;
         let mut out_key = core::ptr::null_mut();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

pub fn set_default_value(
    hkey: Registry::HKEY,
    sub_key: &OsStr,
    typ: u32,
    value: &OsStr,
) -> Result<u32, io::Error> {
    let child_key = if !sub_key.is_empty() {
        let wide_sub_key = sub_key.to_wide_cstring().map_err(io::Error::other)?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/winreg.rs` around lines 464 - 471, Update
set_default_value to explicitly map the to_wide_cstring ContainsNul failure into
io::Error before using ?, then update the SetValue caller to handle the
Result<u32, io::Error> contract instead of comparing the result directly with
zero; preserve the existing success and Windows error-code behavior.

512-515: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Avoid taking ownership of a borrowed reference and eliminate unnecessary allocations.

Type error: into_vec_with_nul consumes a WideCString by value, but input is a reference (&WideCStr). This will fail to compile because you cannot move out of a shared reference.

Since ExpandEnvironmentStringsW only requires a pointer, you can avoid allocating a new Vec entirely by passing input.as_ptr() directly.

🐛 Proposed fix
 pub fn expand_environment_strings(
     input: &WideCStr,
 ) -> Result<String, ExpandEnvironmentStringsError> {
-    let wide_input = input.into_vec_with_nul();
     let required_size = unsafe {
-        Environment::ExpandEnvironmentStringsW(wide_input.as_ptr(), core::ptr::null_mut(), 0)
+        Environment::ExpandEnvironmentStringsW(input.as_ptr(), core::ptr::null_mut(), 0)
     };
     if required_size == 0 {
         return Err(ExpandEnvironmentStringsError::Os);
     }
 
     let mut out = vec![0u16; required_size as usize];
     let written = unsafe {
-        Environment::ExpandEnvironmentStringsW(wide_input.as_ptr(), out.as_mut_ptr(), required_size)
+        Environment::ExpandEnvironmentStringsW(input.as_ptr(), out.as_mut_ptr(), required_size)
     };

Also applies to: 524-526

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/winreg.rs` around lines 512 - 515, Update
expand_environment_strings to stop calling into_vec_with_nul on the borrowed
input; pass input.as_ptr() directly to ExpandEnvironmentStringsW and remove the
unnecessary wide_input allocation while preserving the existing expansion
behavior.
crates/vm/src/stdlib/_ctypes/function.rs (1)

779-780: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Validate interior NULs for function symbol names.

Using format!("{name}\0") allows any interior NUL bytes in name to pass through into the resulting byte slice, which can cause the underlying C-style API (e.g., dlsym or GetProcAddress) to truncate the string and look up an unintended symbol. Since this PR aims to secure FFI paths against interior NULs, you should validate name as well.

🛡️ Proposed fix to prevent FFI string truncation
-            let terminated = format!("{name}\0");
+            let terminated = rustpython_host_env::ctypes::null_terminated_bytes(name.as_bytes())
+                .map_err(|e| e.to_pyexception(vm))?;
             let ptr_val = match rustpython_host_env::ctypes::lookup_function_symbol_addr(
                 handle
                     .to_usize()
                     .ok_or_else(|| vm.new_value_error("Invalid handle"))?,
-                terminated.as_bytes(),
+                &terminated,
             ) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            let terminated = rustpython_host_env::ctypes::null_terminated_bytes(name.as_bytes())
                .map_err(|e| e.to_pyexception(vm))?;
            let ptr_val = match rustpython_host_env::ctypes::lookup_function_symbol_addr(
                handle
                    .to_usize()
                    .ok_or_else(|| vm.new_value_error("Invalid handle"))?,
                &terminated,
            ) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/_ctypes/function.rs` around lines 779 - 780, Validate
the function symbol name before constructing the terminated string in the
surrounding function of the lookup_function_symbol_addr call. Reject names
containing interior NUL bytes and return the existing error path, then preserve
the current format!("{name}\0") lookup flow for valid names.
crates/vm/src/stdlib/winreg.rs (1)

844-846: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Provide the vm context parameter.

Type error: to_pyexception() requires the vm parameter (&VirtualMachine) to instantiate the Python exception. This will cause a compilation error.

🐛 Proposed fix
     fn ExpandEnvironmentStrings(i: String, vm: &VirtualMachine) -> PyResult<String> {
-        let i = WideCString::from_str(&i).map_err(|err| err.to_pyexception())?;
+        let i = WideCString::from_str(&i).map_err(|err| err.to_pyexception(vm))?;
         host_winreg::expand_environment_strings(&i).map_err(|err| err.to_pyexception(vm))
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    fn ExpandEnvironmentStrings(i: String, vm: &VirtualMachine) -> PyResult<String> {
        let i = WideCString::from_str(&i).map_err(|err| err.to_pyexception(vm))?;
        host_winreg::expand_environment_strings(&i).map_err(|err| err.to_pyexception(vm))
    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/winreg.rs` around lines 844 - 846, Update the error
mapping in the `WideCString::from_str` conversion within the surrounding winreg
function to pass the available `vm` context to `to_pyexception`, matching the
existing `expand_environment_strings` mapping. Leave the successful conversion
and environment expansion behavior unchanged.
crates/wtf8/src/lib.rs (2)

1532-1548: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject all source NULs consistently. Both encoders synthesize their own terminator, so every NUL found in the input is interior and must fail.

  • crates/wtf8/src/lib.rs#L1532-L1548: return InteriorNulError immediately for any source NUL and mark the iterator complete.
  • crates/host_env/src/ctypes.rs#L545-L557: apply the same rule and prevent iteration from resuming after the error.
📍 Affects 2 files
  • crates/wtf8/src/lib.rs#L1532-L1548 (this comment)
  • crates/host_env/src/ctypes.rs#L545-L557
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/wtf8/src/lib.rs` around lines 1532 - 1548, Reject every source NUL
immediately in the encoder iterator, returning InteriorNulError and setting the
iterator’s completion state so iteration cannot resume. Apply this change at
crates/wtf8/src/lib.rs lines 1532-1548 and crates/host_env/src/ctypes.rs lines
545-557, preserving the synthesized terminator behavior while removing the
scan-and-accept path.

1557-1560: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the iterator’s size_hint.

The iterator can emit an additional terminator, while an early interior-NUL error can produce fewer items than the wrapped iterator’s lower bound. Forwarding the original hint violates both bounds.

Proposed fix
 fn size_hint(&self) -> (usize, Option<usize>) {
-    self.iter.size_hint()
+    if self.complete {
+        return (0, Some(0));
+    }
+    let (_, upper) = self.iter.size_hint();
+    (1, upper.and_then(|len| len.checked_add(1)))
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        if self.complete {
            return (0, Some(0));
        }
        let (_, upper) = self.iter.size_hint();
        (1, upper.and_then(|len| len.checked_add(1)))
    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/wtf8/src/lib.rs` around lines 1557 - 1560, Update the size_hint method
to account for the iterator’s possible terminator output and early termination
on interior-NUL errors; do not forward self.iter.size_hint() unchanged. Return
bounds that never overstate the minimum or maximum number of items the iterator
can emit, preserving the appropriate unbounded case.

@joshuamegnauth54
joshuamegnauth54 marked this pull request as draft July 15, 2026 18:16
@joshuamegnauth54
joshuamegnauth54 marked this pull request as ready for review July 18, 2026 00:44

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
crates/host_env/src/winreg.rs (3)

509-524: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Fix compilation error: into_vec_with_nul consumes by value.

into_vec_with_nul is a method on WideCString and cannot be called on a reference &WideCStr. Since ExpandEnvironmentStringsW only requires a pointer, you can pass input.as_ptr() directly and avoid the allocation.

🐛 Proposed fix
 pub fn expand_environment_strings(
     input: &widestring::WideCStr,
 ) -> Result<String, ExpandEnvironmentStringsError> {
-    let wide_input = input.into_vec_with_nul();
     let required_size = unsafe {
-        Environment::ExpandEnvironmentStringsW(wide_input.as_ptr(), core::ptr::null_mut(), 0)
+        Environment::ExpandEnvironmentStringsW(input.as_ptr(), core::ptr::null_mut(), 0)
     };
     if required_size == 0 {
         return Err(ExpandEnvironmentStringsError::Os);
     }

     let mut out = vec![0u16; required_size as usize];
     let written = unsafe {
-        Environment::ExpandEnvironmentStringsW(wide_input.as_ptr(), out.as_mut_ptr(), required_size)
+        Environment::ExpandEnvironmentStringsW(input.as_ptr(), out.as_mut_ptr(), required_size)
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/winreg.rs` around lines 509 - 524, Update
expand_environment_strings to stop calling the consuming into_vec_with_nul
method on the borrowed input; pass input.as_ptr() directly to both
Environment::ExpandEnvironmentStringsW calls and remove the unnecessary
allocation.

463-507: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Fix unresolved variable, return type mismatch, and incorrect argument type.

The parameter was renamed to wide_sub_key but the body references sub_key. Additionally, the function is declared to return u32 but attempts to return Ok(res) at the end.

🐛 Proposed fix
 pub fn set_default_value(
     hkey: Registry::HKEY,
     wide_sub_key: &widestring::WideCStr,
     typ: u32,
     wide_value: &widestring::WideCStr,
 ) -> u32 {
-    let child_key = if !sub_key.is_empty() {
+    let child_key = if !wide_sub_key.is_empty() {
         let mut out_key = core::ptr::null_mut();
         let res = unsafe {
             create_key_ex(
                 hkey,
-                &wide_sub_key,
+                wide_sub_key,
                 0,
                 core::ptr::null_mut(),
                 0,
                 Registry::KEY_SET_VALUE,
                 core::ptr::null(),
                 &mut out_key,
                 core::ptr::null_mut(),
             )
         };
         if res != 0 {
             return res;
         }
         Some(out_key)
     } else {
         None
     };

     let target_key = child_key.unwrap_or(hkey);
     let res = unsafe {
         set_value_ex(
             target_key,
             None,
             typ,
             wide_value.as_ptr() as *const u8,
             (wide_value.len() * 2) as u32,
         )
     };

     if let Some(ck) = child_key {
         close_key(ck);
     }
-    Ok(res)
+    res
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/winreg.rs` around lines 463 - 507, Update
set_default_value to use the existing wide_sub_key parameter instead of the
unresolved sub_key reference, pass the expected key type to create_key_ex, and
return the u32 result directly rather than wrapping it in Ok. Preserve the
existing child-key creation, value-setting, and cleanup flow.

350-362: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Fix unresolved variable and incorrect argument type.

The parameter was renamed to wide_sub_key, but the body still references sub_key. Additionally, open_key_ex should take the unwrapped sub_key from the if let binding rather than the Option wrapper wide_sub_key.

🐛 Proposed fix
 pub fn query_default_value(
     hkey: Registry::HKEY,
     wide_sub_key: Option<&widestring::WideCStr>,
 ) -> Result<String, QueryStringError> {
-    let child_key = if let Some(sub_key) = sub_key.filter(|s| !s.is_empty()) {
+    let child_key = if let Some(sub_key) = wide_sub_key.filter(|s| !s.is_empty()) {
         let mut out_key = core::ptr::null_mut();
         let res = unsafe {
             open_key_ex(
                 hkey,
-                &wide_sub_key,
+                Some(sub_key),
                 0,
                 Registry::KEY_QUERY_VALUE,
                 &mut out_key,
             )
         };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/winreg.rs` around lines 350 - 362, In the child-key
handling branch of the registry query function, use the bound `sub_key` value
instead of the renamed `wide_sub_key` variable. Pass this unwrapped `sub_key`
directly to `open_key_ex`, preserving the existing filtering of empty subkeys
and the surrounding registry logic.
crates/vm/src/stdlib/winreg.rs (1)

616-623: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Pass the converted wide strings to set_default_value.

The local variables wide_sub_key and wide_value were created but not passed to set_default_value, causing a type mismatch since the host function signature was updated to expect &WideCStr.

🐛 Proposed fix
-        let wide_sub_key = WideCString::from_str(sub_key)?;
-        let wide_value = WideCString::from_str(value)?;
+        let wide_sub_key = WideCString::from_str(sub_key).map_err(|e| e.to_pyexception(vm))?;
+        let wide_value = WideCString::from_str(value).map_err(|e| e.to_pyexception(vm))?;
         let res = host_winreg::set_default_value(
             hkey,
-            std::ffi::OsStr::new(&sub_key),
+            &wide_sub_key,
             typ,
-            std::ffi::OsStr::new(&value),
+            &wide_value,
         );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/winreg.rs` around lines 616 - 623, Update the
set_default_value call in the winreg flow to pass references to the
already-created wide_sub_key and wide_value variables instead of constructing
OsStr values from sub_key and value. Preserve the existing typ and hkey
arguments and rely on the WideCString conversions already performed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/host_env/src/ctypes.rs`:
- Around line 543-586: Update wchar_ffi_bytes so supplementary code points are
encoded as UTF-16 surrogate pairs when WChar is 2 bytes, reusing
Wtf8::encode_wide_ffi or the equivalent platform-specific path; retain the
existing direct conversion for wider WChar representations and preserve NUL
termination/interior-NUL handling.

In `@crates/host_env/src/nt.rs`:
- Line 239: Map every to_wide_with_nul conversion error to the surrounding I/O
error type before applying ?, using io::Error::other at
crates/host_env/src/nt.rs lines 239, 386, 419, 451, 618, 656, 1383, 1421, 1449,
1466, and 1598; at lines 1281-1284, map it to
ReadlinkError::Io(io::Error::other(e)).
- Line 944: Update test_file_type_by_name to return result directly instead of
wrapping it in Ok, matching the function’s bool return type and preserving the
computed value.

In `@crates/vm/src/stdlib/_ctypes/base.rs`:
- Line 398: Make the shared wchar conversion helper in
crates/vm/src/stdlib/_ctypes/base.rs fallible and reject interior NULs by
returning the existing InteriorNulError. Update the function.rs conversion path
to use the checked helper and propagate that error, preserving valid wchar
conversions; apply the corresponding changes at base.rs lines 398-398 and
function.rs lines 153-153.

In `@crates/vm/src/stdlib/winreg.rs`:
- Around line 569-570: Map all Windows registry wide-string conversion errors
into PyException with the current vm context. In crates/vm/src/stdlib/winreg.rs
lines 569-570, map WideCString::from_str errors via to_pyexception(vm); at lines
577-578, append the same map_err before ?; at line 587, pass vm to
to_wide_cstring; and at lines 848-849, pass vm to the existing exception
mapping.

---

Outside diff comments:
In `@crates/host_env/src/winreg.rs`:
- Around line 509-524: Update expand_environment_strings to stop calling the
consuming into_vec_with_nul method on the borrowed input; pass input.as_ptr()
directly to both Environment::ExpandEnvironmentStringsW calls and remove the
unnecessary allocation.
- Around line 463-507: Update set_default_value to use the existing wide_sub_key
parameter instead of the unresolved sub_key reference, pass the expected key
type to create_key_ex, and return the u32 result directly rather than wrapping
it in Ok. Preserve the existing child-key creation, value-setting, and cleanup
flow.
- Around line 350-362: In the child-key handling branch of the registry query
function, use the bound `sub_key` value instead of the renamed `wide_sub_key`
variable. Pass this unwrapped `sub_key` directly to `open_key_ex`, preserving
the existing filtering of empty subkeys and the surrounding registry logic.

In `@crates/vm/src/stdlib/winreg.rs`:
- Around line 616-623: Update the set_default_value call in the winreg flow to
pass references to the already-created wide_sub_key and wide_value variables
instead of constructing OsStr values from sub_key and value. Preserve the
existing typ and hkey arguments and rely on the WideCString conversions already
performed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 8a3654d2-a5a6-4359-be3e-6a7b6a4155d1

📥 Commits

Reviewing files that changed from the base of the PR and between b7ce43b and 6f595cd.

📒 Files selected for processing (15)
  • crates/host_env/src/ctypes.rs
  • crates/host_env/src/fileutils.rs
  • crates/host_env/src/nt.rs
  • crates/host_env/src/overlapped.rs
  • crates/host_env/src/winapi.rs
  • crates/host_env/src/windows.rs
  • crates/host_env/src/winreg.rs
  • crates/stdlib/src/overlapped.rs
  • crates/vm/src/exceptions.rs
  • crates/vm/src/stdlib/_ctypes/base.rs
  • crates/vm/src/stdlib/_ctypes/function.rs
  • crates/vm/src/stdlib/_winapi.rs
  • crates/vm/src/stdlib/nt.rs
  • crates/vm/src/stdlib/winreg.rs
  • crates/wtf8/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (8)
  • crates/vm/src/exceptions.rs
  • crates/host_env/src/fileutils.rs
  • crates/stdlib/src/overlapped.rs
  • crates/vm/src/stdlib/nt.rs
  • crates/host_env/src/winapi.rs
  • crates/wtf8/src/lib.rs
  • crates/vm/src/stdlib/_winapi.rs
  • crates/host_env/src/windows.rs

Comment on lines +543 to +586
/// Encode buffer as a wide string but yield bytes instead of u16.
///
/// The resulting bytes buffer is suitable for FFI. It is NUL capped without interior
/// NULs.
pub fn wchar_ffi_bytes(s: &Wtf8) -> impl Iterator<Item = Result<u8, InteriorNulError>> {
let mut iter = s.code_points().map(|cp| cp.to_u32() as WChar);
let mut pending: Option<array::IntoIter<_, _>> = None;
let mut complete = false;

iter::from_fn(move || {
if let Some(pending) = pending.as_mut()
&& let Some(next) = pending.next()
{
return Some(Ok(next));
}

match iter.next() {
Some(0) => {
core::hint::cold_path();
complete = true;

for next in iter.by_ref() {
if next != 0 {
return Some(Err(InteriorNulError));
}
}

pending = Some((0 as WChar).to_ne_bytes().into_iter());
}
Some(next) => {
pending = Some(next.to_ne_bytes().into_iter());
}
None if !complete => {
complete = true;
pending = Some((0 as WChar).to_ne_bytes().into_iter());
}
None => {
return None;
}
}

pending.as_mut().unwrap().next().map(Result::Ok)
})
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift

Fix code point truncation when WChar is 2 bytes.

The wchar_ffi_bytes function truncates supplementary code points by casting them directly to WChar (cp.to_u32() as WChar). As correctly noted in the nearby wchar_null_terminated_bytes function, on platforms where WChar is 2 bytes (e.g., Windows), this will truncate supplementary characters instead of encoding them as surrogate pairs, leading to invalid UTF-16 data and potential data corruption.

Please conditionally compile this function or use conditional logic to call s.encode_wide_ffi() on platforms where size_of::<WChar>() == 2 to yield properly encoded bytes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/ctypes.rs` around lines 543 - 586, Update wchar_ffi_bytes
so supplementary code points are encoded as UTF-16 surrogate pairs when WChar is
2 bytes, reusing Wtf8::encode_wide_ffi or the equivalent platform-specific path;
retain the existing direct conversion for wider WChar representations and
preserve NUL termination/interior-NUL handling.

Comment thread crates/host_env/src/nt.rs Outdated
};

let wide_path = path.as_os_str().to_wide_with_nul();
let wide_path = path.as_os_str().to_wide_with_nul()?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Map wide-string conversion errors to io::Error.

The to_wide_with_nul() method returns Result<Vec<u16>, ContainsNul<u16>>. Because ContainsNul does not implicitly convert to std::io::Error via the ? operator, these calls fail to compile. Map the error to io::Error before applying ?.

  • crates/host_env/src/nt.rs#L239-L239: Map the error using .map_err(io::Error::other)?.
  • crates/host_env/src/nt.rs#L386-L386: Map the error using .map_err(io::Error::other)?.
  • crates/host_env/src/nt.rs#L419-L419: Map the error using .map_err(io::Error::other)?.
  • crates/host_env/src/nt.rs#L451-L451: Map the error using .map_err(io::Error::other)?.
  • crates/host_env/src/nt.rs#L618-L618: Map the error using .map_err(io::Error::other)?.
  • crates/host_env/src/nt.rs#L656-L656: Map the error using .map_err(io::Error::other)?.
  • crates/host_env/src/nt.rs#L1281-L1284: Map the error to the enum variant using .map_err(|e| ReadlinkError::Io(io::Error::other(e)))?.
  • crates/host_env/src/nt.rs#L1383-L1383: Map the error using .map_err(io::Error::other)?.
  • crates/host_env/src/nt.rs#L1421-L1421: Map the error using .map_err(io::Error::other)?.
  • crates/host_env/src/nt.rs#L1449-L1449: Map the error using .map_err(io::Error::other)?.
  • crates/host_env/src/nt.rs#L1466-L1466: Map the error using .map_err(io::Error::other)?.
  • crates/host_env/src/nt.rs#L1598-L1598: Map the error using .map_err(io::Error::other)?.
📍 Affects 1 file
  • crates/host_env/src/nt.rs#L239-L239 (this comment)
  • crates/host_env/src/nt.rs#L386-L386
  • crates/host_env/src/nt.rs#L419-L419
  • crates/host_env/src/nt.rs#L451-L451
  • crates/host_env/src/nt.rs#L618-L618
  • crates/host_env/src/nt.rs#L656-L656
  • crates/host_env/src/nt.rs#L1281-L1284
  • crates/host_env/src/nt.rs#L1383-L1383
  • crates/host_env/src/nt.rs#L1421-L1421
  • crates/host_env/src/nt.rs#L1449-L1449
  • crates/host_env/src/nt.rs#L1466-L1466
  • crates/host_env/src/nt.rs#L1598-L1598
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/nt.rs` at line 239, Map every to_wide_with_nul conversion
error to the surrounding I/O error type before applying ?, using
io::Error::other at crates/host_env/src/nt.rs lines 239, 386, 419, 451, 618,
656, 1383, 1421, 1449, 1466, and 1598; at lines 1281-1284, map it to
ReadlinkError::Io(io::Error::other(e)).

Comment thread crates/host_env/src/nt.rs Outdated
let result = test_file_type_by_handle(handle, tested_type, false);
unsafe { CloseHandle(handle) };
return result;
return Ok(result);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Fix return type mismatch.

The function test_file_type_by_name returns a bool, but this line attempts to return Ok(result). This will fail to compile.

🐛 Proposed fix
-        return Ok(result);
+        return result;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return Ok(result);
return result;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host_env/src/nt.rs` at line 944, Update test_file_type_by_name to
return result directly instead of wrapping it in Ok, matching the function’s
bool return type and preserving the computed value.

/// Convert str to null-terminated wchar_t buffer. Returns (PyBytes holder, pointer).
pub(super) fn str_to_wchar_bytes(s: &Wtf8, vm: &VirtualMachine) -> (PyObjectRef, usize) {
let bytes = rustpython_host_env::ctypes::wchar_null_terminated_bytes(s);
let bytes = wchar_null_terminated_bytes(s);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- base.rs outline ---\n'
ast-grep outline crates/vm/src/stdlib/_ctypes/base.rs --view expanded || true
printf '\n--- function.rs outline ---\n'
ast-grep outline crates/vm/src/stdlib/_ctypes/function.rs --view expanded || true

printf '\n--- relevant snippets ---\n'
sed -n '340,430p' crates/vm/src/stdlib/_ctypes/base.rs
printf '\n'
sed -n '120,190p' crates/vm/src/stdlib/_ctypes/function.rs

printf '\n--- search for wchar helpers / InteriorNulError / utf16z_bytes / wchar_null_terminated_bytes ---\n'
rg -n "wchar_null_terminated_bytes|utf16z_bytes|InteriorNulError|wchar_ffi_bytes|str_to_wchar_bytes" crates/vm/src -S

Repository: RustPython/RustPython

Length of output: 250


🏁 Script executed:

sed -n '340,430p' crates/vm/src/stdlib/_ctypes/base.rs && printf '\n---\n' && sed -n '120,190p' crates/vm/src/stdlib/_ctypes/function.rs && printf '\n---\n' && rg -n "wchar_null_terminated_bytes|utf16z_bytes|InteriorNulError|wchar_ffi_bytes|str_to_wchar_bytes" crates/vm/src -S

Repository: RustPython/RustPython

Length of output: 7477


🏁 Script executed:

pwd && ls -1 && git ls-files crates/vm/src/stdlib/_ctypes | sed -n '1,40p'

Repository: RustPython/RustPython

Length of output: 817


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in crates/vm/src/stdlib/_ctypes/simple.rs crates/vm/src/stdlib/_ctypes/pointer.rs crates/vm/src/stdlib/_ctypes/array.rs crates/vm/src/stdlib/_ctypes/base.rs; do
  echo "--- $f ---"
  rg -n -C 2 "str_to_wchar_bytes|utf16z_bytes" "$f" || true
  echo
done

Repository: RustPython/RustPython

Length of output: 2922


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the host_env ctypes helpers if vendored in this repo, and inspect NUL-related APIs.
fd -a "ctypes.rs" . || true
fd -a "host_env" . || true
rg -n "fn wchar_null_terminated_bytes|fn utf16z_bytes|InteriorNulError|null_terminated_bytes" . -S

Repository: RustPython/RustPython

Length of output: 2308


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '520,590p' crates/host_env/src/ctypes.rs
printf '\n---\n'
sed -n '1148,1185p' crates/host_env/src/ctypes.rs
printf '\n---\n'
sed -n '1190,1215p' crates/vm/src/exceptions.rs

Repository: RustPython/RustPython

Length of output: 4068


Make ctypes wchar conversion reject interior NULs.
The shared _ctypes wchar path still accepts embedded NULs, so native callees can see truncated strings. Make the helper fallible and switch the function.rs path to the checked API that raises InteriorNulError.

📍 Affects 2 files
  • crates/vm/src/stdlib/_ctypes/base.rs#L398-L398 (this comment)
  • crates/vm/src/stdlib/_ctypes/function.rs#L153-L153
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/_ctypes/base.rs` at line 398, Make the shared wchar
conversion helper in crates/vm/src/stdlib/_ctypes/base.rs fallible and reject
interior NULs by returning the existing InteriorNulError. Update the function.rs
conversion path to use the checked helper and propagate that error, preserving
valid wchar conversions; apply the corresponding changes at base.rs lines
398-398 and function.rs lines 153-153.

Comment on lines +569 to +570
let sub_key = sub_key.map(WideCString::from_str).transpose()?;
host_winreg::query_default_value(hkey, sub_key.as_deref())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Map wide-string conversion errors to PyException.

Calls to WideCString::from_str(...) return a ContainsNul error, and to_wide_cstring() requires the vm context. Without explicitly mapping these errors to PyException via .map_err(|e| e.to_pyexception(vm)) (or passing vm to to_wide_cstring), the ? operator fails to compile because ContainsNul cannot be implicitly converted to PyResult.

  • crates/vm/src/stdlib/winreg.rs#L569-L570: Change the mapping to .map(|s| WideCString::from_str(s).map_err(|e| e.to_pyexception(vm))).
  • crates/vm/src/stdlib/winreg.rs#L577-L578: Append .map_err(|e| e.to_pyexception(vm))? to the conversion call.
  • crates/vm/src/stdlib/winreg.rs#L587-L587: Add the missing vm argument to the method call: .to_wide_cstring(vm)?.
  • crates/vm/src/stdlib/winreg.rs#L848-L849: Pass the vm context to the exception mapping: .map_err(|err| err.to_pyexception(vm))?.
📍 Affects 1 file
  • crates/vm/src/stdlib/winreg.rs#L569-L570 (this comment)
  • crates/vm/src/stdlib/winreg.rs#L577-L578
  • crates/vm/src/stdlib/winreg.rs#L587-L587
  • crates/vm/src/stdlib/winreg.rs#L848-L849
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/winreg.rs` around lines 569 - 570, Map all Windows
registry wide-string conversion errors into PyException with the current vm
context. In crates/vm/src/stdlib/winreg.rs lines 569-570, map
WideCString::from_str errors via to_pyexception(vm); at lines 577-578, append
the same map_err before ?; at line 587, pass vm to to_wide_cstring; and at lines
848-849, pass vm to the existing exception mapping.

@joshuamegnauth54
joshuamegnauth54 marked this pull request as draft July 18, 2026 00:54
Interior NULs is a security hazard for C-style strings. A NUL byte
truncates a string which can lead the caller and callee to see two
different strings. It can cause path traversal attacks where a path in
Python looks complete but it is interpreted differently through FFI.

RustPython needs to handle this for some of its C-API as well as raw
libc or Windows calls. Both Rust's standard library as well as Rustix
handle interior NULs for us with CStrings, so this mostly affects
Windows or areas where we have raw bytes that weren't checked by
CString.

Finally, this PR is non-exhaustive. I will have to rely heavily on
CodeRabbit to help lint it to ensure that interior NUL checks are only
introduced for FFI and not outside of it. Most of RustPython seems to
handle interior NULs already due to CString as well as WideCString.

**AI disclosure:** I relied on AI to ensure I'm solving this problem
correctly. Mainly, I used it to check if the FFI functions I'm modifying
need to handle interior NULs.

**Sources:**
* https://owasp.org/www-community/attacks/Embedding_Null_Code
* python/cpython#11656

Assisted-by: Codex:gpt-5.4
@joshuamegnauth54 joshuamegnauth54 mentioned this pull request Jul 20, 2026
2 tasks
joshuamegnauth54 added a commit to joshuamegnauth54/RustPython that referenced this pull request Jul 21, 2026
Part of RustPython#8245 to reduce the amount of work needed to review. I
simplified the embedded nul errors by forwarding to the implementations
in `vm::exceptions`.
joshuamegnauth54 added a commit to joshuamegnauth54/RustPython that referenced this pull request Jul 21, 2026
Part of RustPython#8245 to reduce the amount of work needed to review. I
simplified the embedded nul errors by forwarding to the implementations
in `vm::exceptions`.
joshuamegnauth54 added a commit to joshuamegnauth54/RustPython that referenced this pull request Jul 21, 2026
Part of RustPython#8245 to reduce the amount of work needed to review. I
simplified the embedded nul errors by forwarding to the implementations
in `vm::exceptions`.
joshuamegnauth54 added a commit to joshuamegnauth54/RustPython that referenced this pull request Jul 22, 2026
Part of RustPython#8245 to reduce the amount of work needed to review. I
simplified the embedded nul errors by forwarding to the implementations
in `vm::exceptions`.
youknowone pushed a commit that referenced this pull request Jul 23, 2026
Part of #8245 to reduce the amount of work needed to review. I
simplified the embedded nul errors by forwarding to the implementations
in `vm::exceptions`.
@coderabbitai coderabbitai Bot mentioned this pull request Jul 30, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant