cargo / rustix / audit
cargo : rustix @ 1.1.4
PE Patrick Elsen signed 2026-05-28 published 2026-05-28

Claims

build-exec-deterministicbuild-exec-minimalbuild-exec-no-networkbuild-exec-no-write-outbuild-exec-safeconcurrency-documentedconcurrency-safeenvironment-safefilesystem-safehas-binarieshas-build-exechas-fuzz-testshas-install-exechas-integration-testshas-property-testshas-unit-testsimpl-algorithmimpl-concurrencyimpl-cryptoimpl-datastructureimpl-interpreterimpl-jitimpl-parserimpl-protocolis-benignnetwork-safeunsafe-documentedunsafe-minimalunsafe-safeunsafe-testeduses-concurrencyuses-cryptouses-environmentuses-execuses-filesystemuses-interpreteruses-jituses-networkuses-unsafe

Summary

rustix 1.1.4 provides memory-safe and I/O-safe wrappers over POSIX/Linux/Winsock syscalls, with a libc-free linux_raw inline-asm backend. The audit read the syscall-argument safety system (typed register marshalling, error-range decoding, unsafe trait ABI contracts) and found one low-severity quality issue: per-block unsafe safety comments are inconsistent across the syscall modules.

Report

Subject

rustix 1.1.4 provides memory-safe and I/O-safe Rust wrappers over POSIX, Unix, Linux, and Winsock syscall-like APIs. It is a bytecodealliance project, widely used as a low-level OS-interface layer (it can build as a dependency of the Rust standard library via the rustc-dep-of-std feature). The public API spans file, network, memory-mapping, process, thread, time, terminal, io_uring, and ioctl surfaces, each gated behind a cargo feature, exposing file descriptors as OwnedFd/BorrowedFd, paths via the Arg trait, flags via bitflags, and errors as a Result<_, Errno>. The crate has two backends selected at build time: linux_raw, which issues raw syscalls through inline assembly with no libc, and libc, used on non-Linux targets, unsupported Linux ABIs, under Miri, or on request.

Methodology

Tools: openvet 0.6.0, ripgrep, diff, git, the Read tool. diff -rq contents vcs shows the source tree is byte-identical to the upstream git checkout (sha1 c4caf5caaa7e93828a2e4a4cdba1dd0171e45717); the only difference is the cargo-normalised Cargo.toml, and the integration tests/ and examples/ directories are present in VCS but excluded from the published crate by the manifest include list. The crate is large: 316 .rs files, about 72K LOC, with roughly 1584 unsafe constructs. Rather than read every site, I surveyed structure with grep, then read the syscall-arg safety machinery in full: build.rs, src/lib.rs, the x86-64 inline-asm syscall functions, the ArgReg/RetReg/ToAsm/FromAsm register-marshalling system (reg.rs), the argument-conversion layer (conv.rs), the syscall-return error decoder (io/errno.rs), the read/readv/write iovec handling (io/syscalls.rs), the mmap family, the path-to-CStr conversion (path/arg.rs), the SocketAddrArg and Ioctl unsafe traits, the vDSO parser and wrappers, and the syscall dispatch macros. I scanned the whole tree for obfuscation, telemetry, and suspicious endpoints and found none.

Results

The source is byte-equivalent to upstream VCS. The crate contains no prebuilt binaries (has-binaries) and has a build.rs, so it has build-time execution (has-build-exec); the script emits only cargo:rustc-cfg flags, reads CARGO_CFG_*/RUSTC/OUT_DIR/CARGO_ENCODED_RUSTFLAGS, probes the host rustc by compiling tiny snippets fed on stdin, and writes only a throwaway metadata file inside OUT_DIR, making no network requests and touching nothing outside the conventional output area (build-exec-safe, build-exec-no-network, build-exec-no-write-out, build-exec-minimal). Because the chosen cfgs depend on the host compiler's version and capabilities, the output is not a pure function of declared inputs (build-exec-deterministic). The build-time reading of these CARGO_*/RUSTC variables is the crate's only environment use (uses-environment, environment-safe); the runtime library does not read named process env vars. There is no installation-time execution (has-install-exec).

The crate is fundamentally an unsafe-FFI/syscall layer (uses-unsafe), and it wraps filesystem (uses-filesystem) and socket (uses-network) syscalls. It performs no cryptography (uses-crypto), spawns no processes (uses-exec), and embeds no JIT or interpreter (uses-jit, uses-interpreter). It uses AtomicPtr for the vDSO function-pointer cache but does not itself implement synchronisation primitives, parsers, data structures, or algorithms as its purpose (uses-concurrency, impl-concurrency, impl-crypto, impl-parser, impl-interpreter, impl-jit, impl-protocol, impl-datastructure, impl-algorithm).

The syscall-argument safety system is the load-bearing design. On the linux_raw backend, src/lib.rs:101 applies #![cfg_attr(linux_raw, deny(unsafe_code))], so every unsafe block must live in a module that explicitly opts in, concentrating it in the backend, the conversion layer, and the public unsafe APIs. Arguments and return values cross the asm boundary as provenance-preserving *mut Opaque, wrapped in non-Copy, #[must_use] ArgReg<'a, Num>/RetReg<Num> newtypes whose phantom argument-index types prevent misrouting a value to the wrong register, and whose lifetimes prevent an argument outliving its source. The x86-64 asm places the syscall number and arguments in the ABI-correct registers, clobbers rcx/r11, and applies options(readonly) precisely to the syscalls that do not write through pointers. Error decoding checks Linux's -4095..0 error range before distinguishing error from success, and Errno(u16) uses rustc_layout_scalar_valid_range attributes whose niche invariant is upheld by an in-range assert in from_errno. Buffer APIs clamp iovec counts to MAX_IOV, and read/mmap/ioctl are unsafe fn with documented preconditions. The SocketAddrArg and Ioctl unsafe traits push kernel-ABI obligations to explicit # Safety contracts that the built-in safe wrappers satisfy; path conversion bounds-checks its stack buffer, NUL-terminates, and rejects interior NULs via CStr::from_bytes_with_nul. Every unsafe boundary reviewed upholds its invariants, and the unsafe is used where syscalls genuinely require it (unsafe-safe, unsafe-minimal). Network return values and socket-address encoding validate the kernel result and supply correct lengths (network-safe); filesystem path handling resists embedded NULs and the fd-decoding path validates before constructing OwnedFd (filesystem-safe); the vDSO atomic caches are accessed correctly and are the only shared mutable runtime state (concurrency-safe, concurrency-documented).

The crate ships unit tests inline (has-unit-tests) and a large integration suite in VCS (354 test functions across tests/, has-integration-tests); the upstream project runs these under Miri (the build.rs selects the libc backend under Miri specifically so Miri can model the FFI), which exercises the unsafe invariants (unsafe-tested). There are no fuzz or property tests (has-fuzz-tests, has-property-tests).

One low-severity quality finding records that 21 linux_raw syscall modules set #![allow(clippy::undocumented_unsafe_blocks)], so per-block // SAFETY: comments are inconsistent (about 206 across roughly 1584 unsafe sites); the safety reasoning is documented at module granularity and through the type system instead. This justifies unsafe-documented being asserted false while unsafe-safe holds. No obfuscation, telemetry, or suspicious behaviour was observed (is-benign).

Conclusion

The audit read the syscall-argument safety system in full and found a layered design that confines unsafe to opt-in backend modules (via deny(unsafe_code) on the linux_raw path), encodes register marshalling in provenance-preserving typed newtypes, checks the kernel error range before decoding return values, and pushes kernel-ABI preconditions onto explicit unsafe fn and unsafe trait boundaries that the safe wrappers uphold. The x86-64 inline assembly matches the documented Linux syscall ABI. The single finding is a low-severity documentation-completeness observation about inconsistent per-block safety comments, not a soundness defect. The crate has no prebuilt binaries, performs no network or filesystem I/O of its own beyond the syscalls it wraps, and its build script only probes the compiler and emits cfg flags. The published source is byte-identical to the upstream git tag.

Findings(1)

FINDING-1 quality low

Per-block unsafe safety comments are inconsistent in linux_raw syscall modules

The linux_raw syscall modules carry per-block safety comments inconsistently. 21 modules under src/backend/linux_raw/ open with #![allow(clippy::undocumented_unsafe_blocks)] (for example src/backend/linux_raw/io/syscalls.rs:7, src/backend/linux_raw/net/syscalls.rs, src/backend/linux_raw/mm/syscalls.rs), suppressing the lint that would otherwise require a // SAFETY: comment on each unsafe block. Across roughly 1584 unsafe constructs in src/, about 206 carry an explicit // SAFETY: comment.

The safety reasoning is instead documented at coarser granularity. Each such module begins with a # Safety doc section pointing to the backend's shared invariants, and the type-driven argument system in src/backend/linux_raw/reg.rs and src/backend/linux_raw/conv.rs encapsulates the register-marshalling invariants so the call-site unsafe blocks largely reduce to "this is the documented return shape for this syscall." The crate-level #![cfg_attr(linux_raw, deny(unsafe_code))] at src/lib.rs:101 confines all unsafe to modules that explicitly opt in.

This is a documentation-completeness observation, not a soundness defect: the individual unsafe blocks reviewed uphold their invariants. It justifies unsafe-documented being asserted false while unsafe-safe and unsafe-minimal hold.

Annotations(7)

build.rs

The build script emits cargo:rustc-cfg flags only. It reads CARGO_CFG_TARGET_*, CARGO_FEATURE_*, RUSTC, TARGET, OUT_DIR, and CARGO_ENCODED_RUSTFLAGS to choose between the linux_raw and libc backends and to detect optional compiler features. can_compile (line 235) spawns the configured rustc (honouring RUSTC_WRAPPER), feeds a tiny test snippet on stdin, and writes only a throwaway metadata file inside OUT_DIR (line 258). No network access, no writes outside OUT_DIR, no reads of unexpected environment. Because the chosen cfgs depend on the host compiler's version and capabilities, the output is not a pure function of declared inputs.

Justifies has-build-exec, build-exec-safe, build-exec-no-network, build-exec-no-write-out, build-exec-minimal, build-exec-deterministic, uses-environment, and environment-safe.

src/backend/linux_raw/arch/x86_64.rs

src/backend/linux_raw/arch/x86_64.rs, line 11-294

#[inline]
pub(in crate::backend) unsafe fn syscall0_readonly(nr: SyscallNumber<'_>) -> RetReg<R0> {
    let r0;
    asm!(
        "syscall",
        inlateout("rax") nr.to_asm() => r0,
        lateout("rcx") _,
        lateout("r11") _,
        options(nostack, preserves_flags, readonly)
    );
    FromAsm::from_asm(r0)
}

#[inline]
pub(in crate::backend) unsafe fn syscall1(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> RetReg<R0> {
    let r0;
    asm!(
        "syscall",
        inlateout("rax") nr.to_asm() => r0,
        in("rdi") a0.to_asm(),
        lateout("rcx") _,
        lateout("r11") _,
        options(nostack, preserves_flags)
    );
    FromAsm::from_asm(r0)
}

#[inline]
pub(in crate::backend) unsafe fn syscall1_readonly(
    nr: SyscallNumber<'_>,
    a0: ArgReg<'_, A0>,
) -> RetReg<R0> {
    let r0;
    asm!(
        "syscall",
        inlateout("rax") nr.to_asm() => r0,
        in("rdi") a0.to_asm(),
        lateout("rcx") _,
        lateout("r11") _,
        options(nostack, preserves_flags, readonly)
    );
    FromAsm::from_asm(r0)
}

#[inline]
pub(in crate::backend) unsafe fn syscall1_noreturn(nr: SyscallNumber<'_>, a0: ArgReg<'_, A0>) -> ! {
    asm!(
        "syscall",
        "ud2",
        in("rax") nr.to_asm(),
        in("rdi") a0.to_asm(),
        options(nostack, noreturn)
    )
}

#[inline]
pub(in crate::backend) unsafe fn syscall2(
    nr: SyscallNumber<'_>,
    a0: ArgReg<'_, A0>,
    a1: ArgReg<'_, A1>,
) -> RetReg<R0> {
    let r0;
    asm!(
        "syscall",
        inlateout("rax") nr.to_asm() => r0,
        in("rdi") a0.to_asm(),
        in("rsi") a1.to_asm(),
        lateout("rcx") _,
        lateout("r11") _,
        options(nostack, preserves_flags)
    );
    FromAsm::from_asm(r0)
}

#[inline]
pub(in crate::backend) unsafe fn syscall2_readonly(
    nr: SyscallNumber<'_>,
    a0: ArgReg<'_, A0>,
    a1: ArgReg<'_, A1>,
) -> RetReg<R0> {
    let r0;
    asm!(
        "syscall",
        inlateout("rax") nr.to_asm() => r0,
        in("rdi") a0.to_asm(),
        in("rsi") a1.to_asm(),
        lateout("rcx") _,
        lateout("r11") _,
        options(nostack, preserves_flags, readonly)
    );
    FromAsm::from_asm(r0)
}

#[inline]
pub(in crate::backend) unsafe fn syscall3(
    nr: SyscallNumber<'_>,
    a0: ArgReg<'_, A0>,
    a1: ArgReg<'_, A1>,
    a2: ArgReg<'_, A2>,
) -> RetReg<R0> {
    let r0;
    asm!(
        "syscall",
        inlateout("rax") nr.to_asm() => r0,
        in("rdi") a0.to_asm(),
        in("rsi") a1.to_asm(),
        in("rdx") a2.to_asm(),
        lateout("rcx") _,
        lateout("r11") _,
        options(nostack, preserves_flags)
    );
    FromAsm::from_asm(r0)
}

#[inline]
pub(in crate::backend) unsafe fn syscall3_readonly(
    nr: SyscallNumber<'_>,
    a0: ArgReg<'_, A0>,
    a1: ArgReg<'_, A1>,
    a2: ArgReg<'_, A2>,
) -> RetReg<R0> {
    let r0;
    asm!(
        "syscall",
        inlateout("rax") nr.to_asm() => r0,
        in("rdi") a0.to_asm(),
        in("rsi") a1.to_asm(),
        in("rdx") a2.to_asm(),
        lateout("rcx") _,
        lateout("r11") _,
        options(nostack, preserves_flags, readonly)
    );
    FromAsm::from_asm(r0)
}

#[inline]
pub(in crate::backend) unsafe fn syscall4(
    nr: SyscallNumber<'_>,
    a0: ArgReg<'_, A0>,
    a1: ArgReg<'_, A1>,
    a2: ArgReg<'_, A2>,
    a3: ArgReg<'_, A3>,
) -> RetReg<R0> {
    let r0;
    asm!(
        "syscall",
        inlateout("rax") nr.to_asm() => r0,
        in("rdi") a0.to_asm(),
        in("rsi") a1.to_asm(),
        in("rdx") a2.to_asm(),
        in("r10") a3.to_asm(),
        lateout("rcx") _,
        lateout("r11") _,
        options(nostack, preserves_flags)
    );
    FromAsm::from_asm(r0)
}

#[inline]
pub(in crate::backend) unsafe fn syscall4_readonly(
    nr: SyscallNumber<'_>,
    a0: ArgReg<'_, A0>,
    a1: ArgReg<'_, A1>,
    a2: ArgReg<'_, A2>,
    a3: ArgReg<'_, A3>,
) -> RetReg<R0> {
    let r0;
    asm!(
        "syscall",
        inlateout("rax") nr.to_asm() => r0,
        in("rdi") a0.to_asm(),
        in("rsi") a1.to_asm(),
        in("rdx") a2.to_asm(),
        in("r10") a3.to_asm(),
        lateout("rcx") _,
        lateout("r11") _,
        options(nostack, preserves_flags, readonly)
    );
    FromAsm::from_asm(r0)
}

#[inline]
pub(in crate::backend) unsafe fn syscall5(
    nr: SyscallNumber<'_>,
    a0: ArgReg<'_, A0>,
    a1: ArgReg<'_, A1>,
    a2: ArgReg<'_, A2>,
    a3: ArgReg<'_, A3>,
    a4: ArgReg<'_, A4>,
) -> RetReg<R0> {
    let r0;
    asm!(
        "syscall",
        inlateout("rax") nr.to_asm() => r0,
        in("rdi") a0.to_asm(),
        in("rsi") a1.to_asm(),
        in("rdx") a2.to_asm(),
        in("r10") a3.to_asm(),
        in("r8") a4.to_asm(),
        lateout("rcx") _,
        lateout("r11") _,
        options(nostack, preserves_flags)
    );
    FromAsm::from_asm(r0)
}

#[inline]
pub(in crate::backend) unsafe fn syscall5_readonly(
    nr: SyscallNumber<'_>,
    a0: ArgReg<'_, A0>,
    a1: ArgReg<'_, A1>,
    a2: ArgReg<'_, A2>,
    a3: ArgReg<'_, A3>,
    a4: ArgReg<'_, A4>,
) -> RetReg<R0> {
    let r0;
    asm!(
        "syscall",
        inlateout("rax") nr.to_asm() => r0,
        in("rdi") a0.to_asm(),
        in("rsi") a1.to_asm(),
        in("rdx") a2.to_asm(),
        in("r10") a3.to_asm(),
        in("r8") a4.to_asm(),
        lateout("rcx") _,
        lateout("r11") _,
        options(nostack, preserves_flags, readonly)
    );
    FromAsm::from_asm(r0)
}

#[inline]
pub(in crate::backend) unsafe fn syscall6(
    nr: SyscallNumber<'_>,
    a0: ArgReg<'_, A0>,
    a1: ArgReg<'_, A1>,
    a2: ArgReg<'_, A2>,
    a3: ArgReg<'_, A3>,
    a4: ArgReg<'_, A4>,
    a5: ArgReg<'_, A5>,
) -> RetReg<R0> {
    let r0;
    asm!(
        "syscall",
        inlateout("rax") nr.to_asm() => r0,
        in("rdi") a0.to_asm(),
        in("rsi") a1.to_asm(),
        in("rdx") a2.to_asm(),
        in("r10") a3.to_asm(),
        in("r8") a4.to_asm(),
        in("r9") a5.to_asm(),
        lateout("rcx") _,
        lateout("r11") _,
        options(nostack, preserves_flags)
    );
    FromAsm::from_asm(r0)
}

#[inline]
pub(in crate::backend) unsafe fn syscall6_readonly(
    nr: SyscallNumber<'_>,
    a0: ArgReg<'_, A0>,
    a1: ArgReg<'_, A1>,
    a2: ArgReg<'_, A2>,
    a3: ArgReg<'_, A3>,
    a4: ArgReg<'_, A4>,
    a5: ArgReg<'_, A5>,
) -> RetReg<R0> {
    let r0;
    asm!(
        "syscall",
        inlateout("rax") nr.to_asm() => r0,
        in("rdi") a0.to_asm(),
        in("rsi") a1.to_asm(),
        in("rdx") a2.to_asm(),
        in("r10") a3.to_asm(),
        in("r8") a4.to_asm(),
        in("r9") a5.to_asm(),
        lateout("rcx") _,
        lateout("r11") _,
        options(nostack, preserves_flags, readonly)
    );
    FromAsm::from_asm(r0)
}

x86-64 syscall invocation. Each syscallN places the syscall number in rax, arguments in rdi, rsi, rdx, r10, r8, r9 per the Linux x86-64 syscall ABI, and marks rcx and r11 as clobbered (the kernel overwrites them on syscall). The _readonly variants add options(readonly) so the compiler knows the syscall does not write through pointer arguments; the non-readonly variants omit it. syscall1_noreturn uses options(noreturn) with a trailing ud2. Arguments arrive as *mut Opaque from ToAsm::to_asm, preserving pointer provenance through the asm boundary. The register choice and clobber set match the documented kernel ABI.

Justifies uses-unsafe, unsafe-safe, and unsafe-minimal.

src/backend/linux_raw/io/errno.rs

src/backend/linux_raw/io/errno.rs, line 84-251

    /// Convert from a C `errno` value (which is positive) to an `Errno`.
    const fn from_errno(raw: u32) -> Self {
        // We store error values in negated form, so that we don't have to
        // negate them after every syscall.
        let encoded = raw.wrapping_neg() as u16;

        // TODO: Use Range::contains, once that's `const`.
        assert!(encoded >= 0xf001);

        // SAFETY: Linux syscalls return negated error values in the range
        // `-4095..0`, which we just asserted.
        unsafe { Self(encoded) }
    }
}

/// Check for an error from the result of a syscall which encodes a
/// `c::c_int` on success.
#[inline]
pub(in crate::backend) fn try_decode_c_int<Num: RetNumber>(
    raw: RetReg<Num>,
) -> io::Result<c::c_int> {
    if raw.is_in_range(-4095..0) {
        // SAFETY: `raw` must be in `-4095..0`, and we just checked that raw is
        // in that range.
        return Err(unsafe { Errno(raw.decode_error_code()) });
    }

    Ok(raw.decode_c_int())
}

/// Check for an error from the result of a syscall which encodes a
/// `c::c_uint` on success.
#[inline]
pub(in crate::backend) fn try_decode_c_uint<Num: RetNumber>(
    raw: RetReg<Num>,
) -> io::Result<c::c_uint> {
    if raw.is_in_range(-4095..0) {
        // SAFETY: `raw` must be in `-4095..0`, and we just checked that raw is
        // in that range.
        return Err(unsafe { Errno(raw.decode_error_code()) });
    }

    Ok(raw.decode_c_uint())
}

/// Check for an error from the result of a syscall which encodes a `usize` on
/// success.
#[inline]
pub(in crate::backend) fn try_decode_usize<Num: RetNumber>(raw: RetReg<Num>) -> io::Result<usize> {
    if raw.is_in_range(-4095..0) {
        // SAFETY: `raw` must be in `-4095..0`, and we just checked that raw is
        // in that range.
        return Err(unsafe { Errno(raw.decode_error_code()) });
    }

    Ok(raw.decode_usize())
}

/// Check for an error from the result of a syscall which encodes a
/// `*mut c_void` on success.
#[inline]
pub(in crate::backend) fn try_decode_void_star<Num: RetNumber>(
    raw: RetReg<Num>,
) -> io::Result<*mut c::c_void> {
    if raw.is_in_range(-4095..0) {
        // SAFETY: `raw` must be in `-4095..0`, and we just checked that raw is
        // in that range.
        return Err(unsafe { Errno(raw.decode_error_code()) });
    }

    Ok(raw.decode_void_star())
}

/// Check for an error from the result of a syscall which encodes a
/// `u64` on success.
#[cfg(target_pointer_width = "64")]
#[inline]
pub(in crate::backend) fn try_decode_u64<Num: RetNumber>(raw: RetReg<Num>) -> io::Result<u64> {
    if raw.is_in_range(-4095..0) {
        // SAFETY: `raw` must be in `-4095..0`, and we just checked that raw is
        // in that range.
        return Err(unsafe { Errno(raw.decode_error_code()) });
    }

    Ok(raw.decode_u64())
}

/// Check for an error from the result of a syscall which encodes a file
/// descriptor on success.
///
/// # Safety
///
/// This must only be used with syscalls which return file descriptors on
/// success.
#[inline]
pub(in crate::backend) unsafe fn try_decode_raw_fd<Num: RetNumber>(
    raw: RetReg<Num>,
) -> io::Result<RawFd> {
    // Instead of using `check_result` here, we just check for negative, since
    // this function is only used for system calls which return file
    // descriptors, and this produces smaller code.
    if raw.is_negative() {
        debug_assert!(raw.is_in_range(-4095..0));

        // Tell the optimizer that we know the value is in the error range.
        // This helps it avoid unnecessary integer conversions.
        #[cfg(core_intrinsics)]
        {
            core::intrinsics::assume(raw.is_in_range(-4095..0));
        }

        return Err(Errno(raw.decode_error_code()));
    }

    Ok(raw.decode_raw_fd())
}

/// Check for an error from the result of a syscall which encodes no value on
/// success. On success, return the unconsumed `raw` value.
///
/// # Safety
///
/// This must only be used with syscalls which return no value on success.
#[inline]
pub(in crate::backend) unsafe fn try_decode_void<Num: RetNumber>(
    raw: RetReg<Num>,
) -> io::Result<()> {
    // Instead of using `check_result` here, we just check for zero, since this
    // function is only used for system calls which have no other return value,
    // and this produces smaller code.
    if raw.is_nonzero() {
        debug_assert!(raw.is_in_range(-4095..0));

        // Tell the optimizer that we know the value is in the error range.
        // This helps it avoid unnecessary integer conversions.
        #[cfg(core_intrinsics)]
        {
            core::intrinsics::assume(raw.is_in_range(-4095..0));
        }

        return Err(Errno(raw.decode_error_code()));
    }

    raw.decode_void();

    Ok(())
}

/// Check for an error from the result of a syscall which does not return on
/// success. On success, return the unconsumed `raw` value.
///
/// # Safety
///
/// This must only be used with syscalls which do not return on success.
#[cfg(any(feature = "event", feature = "runtime", feature = "system"))]
#[inline]
pub(in crate::backend) unsafe fn try_decode_error<Num: RetNumber>(raw: RetReg<Num>) -> io::Errno {
    debug_assert!(raw.is_in_range(-4095..0));

    // Tell the optimizer that we know the value is in the error range.
    // This helps it avoid unnecessary integer conversions.
    #[cfg(core_intrinsics)]
    {
        core::intrinsics::assume(raw.is_in_range(-4095..0));
    }

    Errno(raw.decode_error_code())
}

Syscall return decoding. Linux returns error codes in the range -4095..0; every try_decode_* function checks is_in_range(-4095..0) (or is_negative/is_nonzero for the fd and void cases) before deciding error versus success, and only then constructs an Errno. The Errno(u16) type stores negated codes and uses rustc_layout_scalar_valid_range_start/end attributes to constrain the niche to 0xf001..=0xffff; from_errno asserts the encoded value is in range before the unsafe { Self(..) } construction, so the validity-range invariant holds. try_decode_raw_fd constructs an OwnedFd only after confirming the value is not in the error range.

Justifies uses-unsafe, unsafe-safe, network-safe, and filesystem-safe.

src/backend/linux_raw/reg.rs

src/backend/linux_raw/reg.rs, line 81-205

/// Syscall arguments use register-sized types. We use a newtype to
/// discourage accidental misuse of the raw integer values.
///
/// This type doesn't implement `Clone` or `Copy`; it should be used exactly
/// once. And it has a lifetime to ensure that it doesn't outlive any resources
/// it might be pointing to.
#[repr(transparent)]
#[must_use]
pub(super) struct ArgReg<'a, Num: ArgNumber> {
    raw: *mut Opaque,
    _phantom: PhantomData<(&'a (), Num)>,
}

impl<'a, Num: ArgNumber> ToAsm for ArgReg<'a, Num> {
    #[inline]
    unsafe fn to_asm(self) -> *mut Opaque {
        self.raw
    }
}

/// Syscall return values use register-sized types. We use a newtype to
/// discourage accidental misuse of the raw integer values.
///
/// This type doesn't implement `Clone` or `Copy`; it should be used exactly
/// once.
#[repr(transparent)]
#[must_use]
pub(super) struct RetReg<Num: RetNumber> {
    raw: *mut Opaque,
    _phantom: PhantomData<Num>,
}

impl<Num: RetNumber> RetReg<Num> {
    #[inline]
    pub(super) fn decode_usize(self) -> usize {
        debug_assert!(!(-4095..0).contains(&(self.raw as isize)));
        self.raw as usize
    }

    #[inline]
    pub(super) fn decode_raw_fd(self) -> RawFd {
        let bits = self.decode_usize();
        let raw_fd = bits as RawFd;

        // Converting `raw` to `RawFd` should be lossless.
        debug_assert_eq!(raw_fd as usize, bits);

        raw_fd
    }

    #[inline]
    pub(super) fn decode_c_int(self) -> c::c_int {
        let bits = self.decode_usize();
        let c_int_ = bits as c::c_int;

        // Converting `raw` to `c_int` should be lossless.
        debug_assert_eq!(c_int_ as usize, bits);

        c_int_
    }

    #[inline]
    pub(super) fn decode_c_uint(self) -> c::c_uint {
        let bits = self.decode_usize();
        let c_uint_ = bits as c::c_uint;

        // Converting `raw` to `c_uint` should be lossless.
        debug_assert_eq!(c_uint_ as usize, bits);

        c_uint_
    }

    #[inline]
    pub(super) fn decode_void_star(self) -> *mut c::c_void {
        self.raw.cast()
    }

    #[cfg(target_pointer_width = "64")]
    #[inline]
    pub(super) fn decode_u64(self) -> u64 {
        self.decode_usize() as u64
    }

    #[inline]
    pub(super) fn decode_void(self) {
        let ignore = self.decode_usize();
        debug_assert_eq!(ignore, 0);
    }

    #[inline]
    pub(super) fn decode_error_code(self) -> u16 {
        let bits = self.raw as usize;

        // `raw` must be in `-4095..0`. Linux always returns errors in
        // `-4095..0`, and we double-check it here.
        debug_assert!((-4095..0).contains(&(bits as isize)));

        bits as u16
    }

    #[inline]
    pub(super) fn is_nonzero(&self) -> bool {
        !self.raw.is_null()
    }

    #[inline]
    pub(super) fn is_negative(&self) -> bool {
        (self.raw as isize) < 0
    }

    #[inline]
    pub(super) fn is_in_range(&self, range: Range<isize>) -> bool {
        range.contains(&(self.raw as isize))
    }
}

impl<Num: RetNumber> FromAsm for RetReg<Num> {
    #[inline]
    unsafe fn from_asm(raw: *mut Opaque) -> Self {
        Self {
            raw,
            _phantom: PhantomData,
        }
    }
}

The syscall-argument type system. Arguments and return values cross the asm boundary as *mut Opaque to preserve provenance, wrapped in ArgReg<'a, Num> and RetReg<Num> newtypes that are non-Copy, #[must_use], and carry a phantom argument-index type so a value destined for one register cannot be misrouted to another. ArgReg carries a lifetime bound to the data it points at, so an argument cannot outlive the borrow that produced it. The ToAsm/FromAsm traits are sealed and unsafe, usable only by the syscall machinery. RetReg::decode_* methods debug-assert that error-range values are not misinterpreted as success values.

Justifies uses-unsafe, unsafe-safe, and unsafe-minimal.

src/backend/linux_raw/vdso_wrappers.rs

src/backend/linux_raw/vdso_wrappers.rs, line 328-460

/// `AtomicPtr` can't hold a `fn` pointer, so we use a `*` pointer to this
/// placeholder type, and cast it as needed.
struct Function;
#[cfg(feature = "time")]
static CLOCK_GETTIME: AtomicPtr<Function> = AtomicPtr::new(null_mut());
#[cfg(feature = "thread")]
#[cfg(any(
    target_arch = "x86_64",
    target_arch = "x86",
    target_arch = "riscv64",
    target_arch = "powerpc",
    target_arch = "powerpc64",
    target_arch = "s390x",
))]
static GETCPU: AtomicPtr<Function> = AtomicPtr::new(null_mut());
#[cfg(target_arch = "x86")]
static SYSCALL: AtomicPtr<Function> = AtomicPtr::new(null_mut());

#[cfg(feature = "time")]
#[must_use]
unsafe extern "C" fn clock_gettime_via_syscall(clockid: c::c_int, res: *mut Timespec) -> c::c_int {
    match _clock_gettime_via_syscall(clockid, res) {
        Ok(()) => 0,
        Err(err) => err.raw_os_error().wrapping_neg(),
    }
}

#[cfg(feature = "time")]
#[cfg(target_pointer_width = "32")]
unsafe fn _clock_gettime_via_syscall(clockid: c::c_int, res: *mut Timespec) -> io::Result<()> {
    let r0 = syscall!(__NR_clock_gettime64, c_int(clockid), res);
    match ret(r0) {
        Err(io::Errno::NOSYS) => _clock_gettime_via_syscall_old(clockid, res),
        otherwise => otherwise,
    }
}

#[cfg(feature = "time")]
#[cfg(target_pointer_width = "32")]
unsafe fn _clock_gettime_via_syscall_old(clockid: c::c_int, res: *mut Timespec) -> io::Result<()> {
    // Ordinarily `rustix` doesn't like to emulate system calls, but in the
    // case of time APIs, it's specific to Linux, specific to 32-bit
    // architectures *and* specific to old kernel versions, and it's not that
    // hard to fix up here, so that no other code needs to worry about this.
    let mut old_result = MaybeUninit::<__kernel_old_timespec>::uninit();
    let r0 = syscall!(__NR_clock_gettime, c_int(clockid), &mut old_result);
    match ret(r0) {
        Ok(()) => {
            let old_result = old_result.assume_init();
            *res = Timespec {
                tv_sec: old_result.tv_sec.into(),
                tv_nsec: old_result.tv_nsec.into(),
            };
            Ok(())
        }
        otherwise => otherwise,
    }
}

#[cfg(feature = "time")]
#[cfg(target_pointer_width = "64")]
unsafe fn _clock_gettime_via_syscall(clockid: c::c_int, res: *mut Timespec) -> io::Result<()> {
    ret(syscall!(__NR_clock_gettime, c_int(clockid), res))
}

#[cfg(feature = "thread")]
#[cfg(any(
    target_arch = "x86_64",
    target_arch = "x86",
    target_arch = "riscv64",
    target_arch = "powerpc",
    target_arch = "powerpc64",
    target_arch = "s390x",
))]
unsafe extern "C" fn getcpu_via_syscall(
    cpu: *mut u32,
    node: *mut u32,
    unused: *mut c_void,
) -> c::c_int {
    match ret(syscall!(__NR_getcpu, cpu, node, unused)) {
        Ok(()) => 0,
        Err(err) => err.raw_os_error().wrapping_neg(),
    }
}

#[cfg(target_arch = "x86")]
extern "C" {
    /// A symbol pointing to an x86 `int 0x80` instruction. This “function”
    /// is only called from assembly, and only with the x86 syscall calling
    /// convention, so its signature here is not its true signature.
    ///
    /// This extern block and the `global_asm!` below can be replaced with
    /// `#[naked]` if it's stabilized.
    fn rustix_x86_int_0x80();
}

// This uses `.weak` so that it doesn't conflict if multiple versions of rustix
// are linked in in non-lto builds, and `.ifndef` so that it doesn't conflict
// if multiple versions of rustix are linked in in lto builds.
#[cfg(target_arch = "x86")]
global_asm!(
    r#"
    .ifndef     rustix_x86_int_0x80
    .section    .text.rustix_x86_int_0x80,"ax",@progbits
    .p2align    4
    .weak       rustix_x86_int_0x80
    .hidden     rustix_x86_int_0x80
    .type       rustix_x86_int_0x80, @function
rustix_x86_int_0x80:
    .cfi_startproc
    int    0x80
    ret
    .cfi_endproc
    .size rustix_x86_int_0x80, .-rustix_x86_int_0x80
    .endif
"#
);

fn minimal_init() {
    // Store default function addresses in static storage so that if we
    // end up making any system calls while we read the vDSO, they'll work. If
    // the memory happens to already be initialized, this is redundant, but not
    // harmful.
    #[cfg(feature = "time")]
    {
        CLOCK_GETTIME
            .compare_exchange(
                null_mut(),
                clock_gettime_via_syscall as *mut Function,
                Relaxed,
                Relaxed,
            )
            .ok();

The vDSO machinery resolves kernel-provided fast-path functions (clock_gettime, getcpu, the x86 __kernel_vsyscall). Resolved function pointers are cached in AtomicPtr<Function> statics, initialized via compare_exchange to syscall-fallback implementations so any concurrent caller works before the vDSO scan completes. vdso.rs is a transliteration of Linux's reference parse_vdso.c, parsing the kernel-supplied vDSO ELF with check_raw_pointer bounds checks and Option-guarded offset arithmetic; its input comes from the kernel via auxv, not from an untrusted source. The atomic caches are the crate's only shared mutable runtime state and are accessed with Relaxed ordering on idempotent pointer values.

Justifies uses-concurrency, concurrency-safe, concurrency-documented, uses-unsafe, and unsafe-safe.

src/net/addr.rs

src/net/addr.rs, line 46-163

pub unsafe trait SocketAddrArg {
    /// Call a closure with the pointer and length to the corresponding C type.
    ///
    /// The memory pointed to by the pointer of size length is guaranteed to be
    /// valid only for the duration of the call.
    ///
    /// The API uses a closure so that:
    ///  - The libc types are not exposed in the rustix API.
    ///  - Types like `SocketAddrUnix` that contain their corresponding C type
    ///    can pass it directly without a copy.
    ///  - Other socket types can construct their C-compatible struct on the
    ///    stack and call the closure with a pointer to it.
    ///
    /// # Safety
    ///
    /// For `f` to use its pointer argument, it'll contain an `unsafe` block.
    /// The caller of `with_sockaddr` here is responsible for ensuring that the
    /// safety condition for that `unsafe` block is satisfied by the guarantee
    /// that `with_sockaddr` here provides.
    unsafe fn with_sockaddr<R>(
        &self,
        f: impl FnOnce(*const SocketAddrOpaque, SocketAddrLen) -> R,
    ) -> R;

    /// Convert to `SocketAddrAny`.
    fn as_any(&self) -> SocketAddrAny {
        let mut storage = MaybeUninit::<SocketAddrStorage>::uninit();
        // SAFETY: We've allocated `storage` here, we're writing to it, and
        // we're using the number of bytes written.
        unsafe {
            let len = self.write_sockaddr(storage.as_mut_ptr());
            SocketAddrAny::new(storage, len)
        }
    }

    /// Encode an address into a `SocketAddrStorage`.
    ///
    /// Returns the number of bytes that were written.
    ///
    /// For a safe interface to this functionality, use [`as_any`].
    ///
    /// [`as_any`]: Self::as_any
    ///
    /// # Safety
    ///
    /// `storage` must be valid to write up to `size_of<SocketAddrStorage>()`
    /// bytes to.
    unsafe fn write_sockaddr(&self, storage: *mut SocketAddrStorage) -> SocketAddrLen {
        // The closure dereferences exactly `len` bytes at `ptr`.
        self.with_sockaddr(|ptr, len| {
            ptr::copy_nonoverlapping(ptr.cast::<u8>(), storage.cast::<u8>(), len as usize);
            len
        })
    }
}

/// Helper for implementing `SocketAddrArg::with_sockaddr`.
///
/// # Safety
///
/// This calls `f` with a pointer to an object it has a reference to, with the
/// and the length of that object, so they'll be valid for the duration of the
/// call.
pub(crate) unsafe fn call_with_sockaddr<A, R>(
    addr: &A,
    f: impl FnOnce(*const SocketAddrOpaque, SocketAddrLen) -> R,
) -> R {
    let ptr = as_ptr(addr).cast();
    let len = size_of::<A>() as SocketAddrLen;
    f(ptr, len)
}

// SAFETY: This just forwards to the inner `SocketAddrArg` implementations.
unsafe impl SocketAddrArg for SocketAddr {
    unsafe fn with_sockaddr<R>(
        &self,
        f: impl FnOnce(*const SocketAddrOpaque, SocketAddrLen) -> R,
    ) -> R {
        match self {
            Self::V4(v4) => v4.with_sockaddr(f),
            Self::V6(v6) => v6.with_sockaddr(f),
        }
    }
}

// SAFETY: `with_sockaddr` calls `f` using `call_with_sockaddr`, which handles
// calling `f` with the needed preconditions.
unsafe impl SocketAddrArg for SocketAddrV4 {
    unsafe fn with_sockaddr<R>(
        &self,
        f: impl FnOnce(*const SocketAddrOpaque, SocketAddrLen) -> R,
    ) -> R {
        call_with_sockaddr(&encode_sockaddr_v4(self), f)
    }
}

// SAFETY: `with_sockaddr` calls `f` using `call_with_sockaddr`, which handles
// calling `f` with the needed preconditions.
unsafe impl SocketAddrArg for SocketAddrV6 {
    unsafe fn with_sockaddr<R>(
        &self,
        f: impl FnOnce(*const SocketAddrOpaque, SocketAddrLen) -> R,
    ) -> R {
        call_with_sockaddr(&encode_sockaddr_v6(self), f)
    }
}

#[cfg(unix)]
// SAFETY: `with_sockaddr` calls `f` using `call_with_sockaddr`, which handles
// calling `f` with the needed preconditions.
unsafe impl SocketAddrArg for SocketAddrUnix {
    unsafe fn with_sockaddr<R>(
        &self,
        f: impl FnOnce(*const SocketAddrOpaque, SocketAddrLen) -> R,
    ) -> R {
        f(as_ptr(&self.unix).cast(), self.addr_len())
    }
}

SocketAddrArg is an unsafe trait whose contract requires implementers to call f with a pointer readable for the supplied length pointing to a valid socket address. The built-in impls satisfy this: SocketAddrV4/SocketAddrV6 route through call_with_sockaddr, which passes a pointer to a stack value and its size_of length; SocketAddrUnix passes its embedded C struct with its computed length. The as_any default method writes into MaybeUninit<SocketAddrStorage> and copies exactly the returned len bytes. Kernel-ABI obligations are pushed to the explicit # Safety contract rather than assumed.

Justifies uses-unsafe, unsafe-safe, uses-network, and network-safe.

src/path/arg.rs

src/path/arg.rs, line 946-985

#[allow(unsafe_code, clippy::int_plus_one)]
#[inline]
fn with_c_str<T, F>(bytes: &[u8], f: F) -> io::Result<T>
where
    F: FnOnce(&CStr) -> io::Result<T>,
{
    // Most paths are less than `SMALL_PATH_BUFFER_SIZE` long. The rest can go
    // through the dynamic allocation path. If you're opening many files in a
    // directory with a long path, consider opening the directory and using
    // `openat` to open the files under it, which will avoid this, and is often
    // faster in the OS as well.

    // Test with `>=` so that we have room for the trailing NUL.
    if bytes.len() >= SMALL_PATH_BUFFER_SIZE {
        return with_c_str_slow_path(bytes, f);
    }

    // Taken from
    // <https://github.com/rust-lang/rust/blob/a00f8ba7fcac1b27341679c51bf5a3271fa82df3/library/std/src/sys/common/small_c_string.rs>
    let mut buf = MaybeUninit::<[u8; SMALL_PATH_BUFFER_SIZE]>::uninit();
    let buf_ptr = buf.as_mut_ptr().cast::<u8>();

    // This helps test our safety condition below.
    debug_assert!(bytes.len() + 1 <= SMALL_PATH_BUFFER_SIZE);

    // SAFETY: `bytes.len() < SMALL_PATH_BUFFER_SIZE` which means we have space
    // for `bytes.len() + 1` `u8`s:
    unsafe {
        ptr::copy_nonoverlapping(bytes.as_ptr(), buf_ptr, bytes.len());
        buf_ptr.add(bytes.len()).write(b'\0');
    }

    // SAFETY: We just wrote the bytes above and they will remain valid for the
    // duration of `f` because `buf` doesn't get dropped until the end of the
    // function.
    match CStr::from_bytes_with_nul(unsafe { slice::from_raw_parts(buf_ptr, bytes.len() + 1) }) {
        Ok(s) => f(s),
        Err(_) => Err(io::Errno::INVAL),
    }
}

Path-argument conversion to a NUL-terminated CStr. The fast path checks bytes.len() >= SMALL_PATH_BUFFER_SIZE and falls back to the heap (or a larger stack buffer in no-alloc builds) otherwise, so the fixed-size MaybeUninit buffer always has room for the bytes plus a trailing NUL. It then copies exactly bytes.len() bytes, writes the NUL at buf_ptr.add(bytes.len()), and validates the result with CStr::from_bytes_with_nul, which rejects interior NUL bytes by returning Errno::INVAL rather than silently truncating. The unsafe blocks carry matching safety comments and a debug_assert on the length bound.

Justifies uses-filesystem, filesystem-safe, uses-unsafe, and unsafe-safe.