shadowsocks/shadowsocks-rust · error

username

Error message

username

What it means

This panic comes from `.expect("username")` on `CString::new(uname)` in `run_as_user` (src/sys.rs:92). `CString::new` fails only when the username string contains an interior NUL byte (`\0`), which cannot be represented in a C string for `libc::getpwnam`. The panic occurs while looking up the user's passwd entry after parsing the `--user` value as a UID succeeded but `getpwuid` returned null.

Source

Thrown at src/sys.rs:92

        }
    }
}

/// setuid(), setgid() for a specific user or uid
#[cfg(unix)]
pub fn run_as_user(uname: &str) -> std::io::Result<()> {
    use log::error;
    use std::{
        ffi::{CStr, CString},
        io::{Error, ErrorKind},
    };

    unsafe {
        let pwd = match uname.parse::<libc::uid_t>() {
            Ok(uid) => {
                let mut pwd = libc::getpwuid(uid);
                if pwd.is_null() {
                    let uname = CString::new(uname).expect("username");
                    pwd = libc::getpwnam(uname.as_ptr())
                }
                pwd
            }
            Err(..) => {
                let uname = CString::new(uname).expect("username");
                libc::getpwnam(uname.as_ptr())
            }
        };

        if pwd.is_null() {
            return Err(Error::new(ErrorKind::InvalidInput, format!("user {} not found", uname)));
        }

        let pwd = &*pwd;

        // setgid first, because we may not allowed to do it anymore after setuid
        if libc::setgid(pwd.pw_gid as libc::gid_t) != 0 {

View on GitHub (pinned to 8eb0f0a65b)

Solutions

  1. Pass a normal NUL-free username to --user, e.g. `--user nobody`.
  2. If the user was given as a numeric UID, ensure a passwd entry exists or pass the username instead so getpwnam is used directly.
  3. Sanitize the user string in the invoking script (strip control characters) before launching.
  4. Patch the code to propagate `CString::new(...)` errors as `Error::new(ErrorKind::InvalidInput, ...)` instead of `.expect`.

Example fix

// before
let uname = CString::new(uname).expect("username");
// after
let uname = CString::new(uname).map_err(|e| {
    Error::new(ErrorKind::InvalidInput, format!("invalid user name: {e}"))
})?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_safe_username(u: &str) -> bool {
    !u.is_empty() && !u.contains('\0') && u.bytes().all(|b| b.is_ascii_graphic() || b == b'_')
}
assert!(is_safe_username(&user_arg), "--user contains invalid characters");

Type guard

fn nul_free(s: &str) -> Option<&str> {
    if s.contains('\0') { None } else { Some(s) }
}

Try / catch

// If patching run_as_user
let uname = CString::new(uname).map_err(|e| {
    Error::new(ErrorKind::InvalidInput, format!("user name contains NUL byte: {e}"))
})?;

Prevention

When it happens

Trigger: Calling `run_as_user` with a username containing an embedded NUL byte — realistically only via a CLI argument or config value built from binary data, an over-long truncated buffer, or programmatic callers passing a String with `\0` inside.

Common situations: Rare: a `--user` argument sourced from corrupted input, environment substitution gone wrong, or a wrapper script inserting null bytes; also hit when a numeric-looking user string parsed as a UID but no matching passwd entry exists, sending execution into this fallback path.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of shadowsocks/shadowsocks-rust@8eb0f0a65b (2026-09-09). Data as JSON: /api/errors/6507c828f8e9b94e. Report an issue: GitHub.