Skip to content

socket: accept a filesystem-encoded hostname in sethostname - #8437

Merged
youknowone merged 1 commit into
RustPython:mainfrom
youknowone:socket-sethostname-fsencode
Aug 3, 2026
Merged

socket: accept a filesystem-encoded hostname in sethostname#8437
youknowone merged 1 commit into
RustPython:mainfrom
youknowone:socket-sethostname-fsencode

Conversation

@youknowone

@youknowone youknowone commented Aug 3, 2026

Copy link
Copy Markdown
Member

socket.sethostname took PyUtf8StrRef, so it rejected bytes outright and
refused a str carrying a surrogate escape.

socketmodule.c socket_sethostname accepts a bytes object directly ("S"),
falls back to PyUnicode_FSConverter for anything else, and hands the syscall
the resulting buffer together with its length — the name is never required to be
UTF-8.

This takes FsPath, the converter if_nametoindex in this same module already
uses, and passes its bytes down. host_env::socket::sethostname correspondingly
takes &[u8] and builds the OsStr from them; nix::unistd::sethostname accepts
AsRef<OsStr> and passes pointer and length to the syscall, so nothing on the path
needs a NUL terminator or valid UTF-8.

Lib/test/test_socket.py test_sethostname already covers this — it calls
socket.sethostname(b'bar') and asserts the hostname changed. The test is skipped
unless run as root, which is why the gap went unnoticed.

Verification

Built both ways and called sethostname as a non-root user, where every accepted
argument reaches the syscall and comes back EPERM. That makes the exception type
the discriminator: PermissionError means the argument was converted and reached
the OS, anything else means it was rejected before that.

argument before after CPython 3.14
'bar' PermissionError PermissionError PermissionError
b'bar' TypeError: Expected type 'str' but 'bytes' found. PermissionError PermissionError
'x\udcff' UnicodeEncodeError: 'utf-8' codec can't encode character '\udcff' PermissionError PermissionError

The hostname itself is never changed by this check, so it is safe to run anywhere.

Summary by CodeRabbit

  • Bug Fixes
    • Improved hostname handling to support filesystem-encoded bytes, including valid non-UTF-8 hostnames.
    • Updated the hostname-setting interface to accept filesystem path-style values.

`socket.sethostname` took `PyUtf8StrRef`, so it rejected `bytes` outright and
refused a `str` carrying a surrogate escape. `socketmodule.c
socket_sethostname` accepts a bytes object directly, falls back to
`PyUnicode_FSConverter` for anything else, and hands the syscall the resulting
buffer and its length — the name is never required to be UTF-8.

Take `FsPath`, the converter `if_nametoindex` in this same module already uses,
and pass its bytes down. `host_env::socket::sethostname` correspondingly takes
`&[u8]` and builds the `OsStr` from them; `nix::unistd::sethostname` accepts
`AsRef<OsStr>` and passes pointer and length to the syscall, so nothing on the
path needs a NUL terminator or valid UTF-8.

`Lib/test/test_socket.py test_sethostname` covers this: it calls
`socket.sethostname(b'bar')` and asserts the hostname changed. The test is
skipped unless run as root, which is why the gap went unnoticed.

Assisted-by: Claude
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The socket API now accepts filesystem-encoded hostname bytes. The standard library passes FsPath bytes to the Unix host environment implementation, which converts them to OsStr before invoking sethostname.

Changes

Hostname encoding

Layer / File(s) Summary
Hostname API forwarding
crates/stdlib/src/socket.rs, crates/host_env/src/socket.rs
sethostname now accepts FsPath in the standard library and filesystem-encoded bytes in the Unix host environment layer. The host implementation converts the bytes to OsStr before the system call.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: copilot, hbina

🚥 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 and concisely describes the main change to accept filesystem-encoded hostnames in socket.sethostname.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates RustPython’s socket.sethostname to accept filesystem-encoded hostnames (including bytes and str values containing surrogate escapes), aligning more closely with CPython’s socket_sethostname behavior by passing raw bytes down to the OS.

Changes:

  • Change _socket.sethostname to accept FsPath instead of PyUtf8StrRef, enabling bytes and surrogate-containing str inputs.
  • Update host_env::socket::sethostname to take a &[u8] and build an OsStr from bytes before calling nix::unistd::sethostname.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
crates/stdlib/src/socket.rs Switches sethostname to accept FsPath and forwards bytes to the host syscall wrapper.
crates/host_env/src/socket.rs Changes host wrapper to accept raw bytes and pass them to nix::unistd::sethostname via OsStr::from_bytes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +2328 to 2330
fn sethostname(hostname: FsPath) -> std::io::Result<()> {
host_socket::sethostname(hostname.as_bytes())
}
Comment on lines +2328 to +2329
fn sethostname(hostname: FsPath) -> std::io::Result<()> {
host_socket::sethostname(hostname.as_bytes())

@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

🤖 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/stdlib/src/socket.rs`:
- Around line 2328-2329: Update sethostname to obtain the hostname bytes through
hostname.as_os_str(vm)? before calling host_socket::sethostname, ensuring
filesystem encoding handles surrogate strings. Add a root-gated regression test
covering a surrogate-string hostname in addition to the existing ASCII string
test.
🪄 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 Plus

Run ID: 345e6549-abd0-4042-82e9-7a13484ee10f

📥 Commits

Reviewing files that changed from the base of the PR and between 74ae2d4 and a94c325.

📒 Files selected for processing (2)
  • crates/host_env/src/socket.rs
  • crates/stdlib/src/socket.rs

Comment on lines +2328 to +2329
fn sethostname(hostname: FsPath) -> std::io::Result<()> {
host_socket::sethostname(hostname.as_bytes())

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate relevant files =="
git ls-files | rg '(^|/)socket\.rs$|fspath\.rs$|fs\.rs$|path\.rs$|sethostname|host_socket|host_env' || true

echo "== socket.rs outline around sethostname =="
if [ -f crates/stdlib/src/socket.rs ]; then
  wc -l crates/stdlib/src/socket.rs
  rg -n "sethostname|FsPath|as_bytes|as_os_str|#\\[pyfunction\\]" crates/stdlib/src/socket.rs
  sed -n '2290,2345p' crates/stdlib/src/socket.rs
fi

echo "== fspath implementation outline/search =="
for f in $(git ls-files | rg 'fspath\.rs$'); do
  echo "--- $f"
  wc -l "$f"
  rg -n "struct FsPath|enum Fspath|impl .*FsPath|as_bytes|as_os_str|fsencode|surrog" "$f"
  sed -n '1,260p' "$f"
done

echo "== search sethostname in codebase =="
rg -n "fn sethostname|host_socket::sethostname|host_env::sethostname|PyUnicode_FSConverter|sethostname" .

Repository: RustPython/RustPython

Length of output: 12842


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== host_env socket sethostname =="
sed -n '1,90p' crates/host_env/src/socket.rs

echo "== fsencode implementation search =="
rg -n "fn fsencode|fsencode\\(" crates/vm/src crates/host_env/src | head -80
for f in $(git ls-files | rg 'crates/(vm|host_env)/src/.*'); do
  if rg -q "fn fsencode|fsencode\\(" "$f"; then
    echo "--- $f"
    rg -n -A 40 -B 5 "fn fsencode|fsencode\\(" "$f"
  fi
done

echo "== existing socket hostname tests =="
sed -n '1130,1180p' Lib/test/test_socket.py 2>/dev/null || true

echo "== sethostname call context in stdlib =="
sed -n '2200,2335p' crates/stdlib/src/socket.rs

echo "== import/return type context for sethostname =="
sed -n '1,35p' crates/stdlib/src/socket.rs

Repository: RustPython/RustPython

Length of output: 20484


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== encode_text implementation candidates =="
rg -n "encode_text|fs_encode_errors|codec_registry|FS_ERRORS|surrogate" crates/vm/src crates/host_env/src | head -120

echo "== codec registry outline/search =="
rg -n "struct CodecRegistry|fn encode_text|encode_text" crates/vm/src | head -80

for f in $(git ls-files | rg 'crates/vm/src/.*'); do
  if rg -q "encode_text\\(" "$f"; then
    echo "--- $f"
    rg -n -A 80 -B 5 "encode_text\\(|struct CodecRegistry|codec_registry" "$f"
  fi
done

echo "== fs decode/encode tests =="
rg -n "fsencode|fsdecode|surrogates|\\udc[a-f0-9][0-9a-f]{2}|\\\\udcf" crates/stdlib Lib test crates/vm | head -160 | sed -n '1,180p'

Repository: RustPython/RustPython

Length of output: 36215


Use filesystem encoding before forwarding hostname.

FsPath::as_bytes() returns raw string bytes for FsPath::Str, and host_socket::sethostname forwards those bytes directly to sethostname. A surrogate string such as "\udcff" therefore bypasses vm.fsencode; use hostname.as_os_str(vm)? and pass its Unix OsStr bytes to host_socket::sethostname. Add a root-gated regression test for the surrogate-string branch; the current string test only covers ASCII.

🤖 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/socket.rs` around lines 2328 - 2329, Update sethostname to
obtain the hostname bytes through hostname.as_os_str(vm)? before calling
host_socket::sethostname, ensuring filesystem encoding handles surrogate
strings. Add a root-gated regression test covering a surrogate-string hostname
in addition to the existing ASCII string test.

Source: MCP tools

@youknowone
youknowone merged commit a635d0b into RustPython:main Aug 3, 2026
27 checks passed
@youknowone
youknowone deleted the socket-sethostname-fsencode branch August 3, 2026 12:46
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.

2 participants