cargo / libredox / audit
cargo : libredox @ 0.1.16
PE Patrick Elsen signed 2026-05-27 published 2026-05-27

Claims

has-binarieshas-build-exechas-fuzz-testshas-install-exechas-integration-testshas-property-testshas-unit-testsimpl-algorithmimpl-concurrencyimpl-cryptoimpl-datastructureimpl-interpreterimpl-jitimpl-parserimpl-protocolis-benignunsafe-documentedunsafe-minimalunsafe-safeunsafe-testeduses-concurrencyuses-cryptouses-environmentuses-execuses-filesystemuses-interpreteruses-jituses-networkuses-unsafe

Summary

libredox 0.1.16 is a thin Redox stable-ABI binding crate: ~30 extern "C" syscall declarations, safe typed wrappers, an Fd RAII type, and protocol-vocabulary enums. Published source matches upstream byte-for-byte; one low-severity informational finding for 47 unsafe blocks without SAFETY: comments and no tests. Safe to use.

Report

Subject

libredox is the Redox-stable-ABI binding crate. It declares roughly 30 extern "C" entry-points (redox_*_v1/_v0), provides typed safe wrappers around them in a call module, exposes an Fd smart wrapper for descriptors, and re-exports protocol-vocabulary types (signal numbers, WIF*/W*sig accessors, ProcCall/ThreadCall/SocketCall/FsCall/NsDup enums, ProcMeta/RtSigInfo POD structs, WaitFlags/NsPermissions bitflags). The crate is gated through six feature flags (base, call, std, redox_syscall, protocol, mkns); the default set enables base, call, std, redox_syscall, protocol.

Methodology

The published crate contents were compared against the upstream Git repository tag v0.1.16 (commit f14358d) using diff -r. The single source file (src/lib.rs, 1080 lines) was read in full. Cargo.toml.orig was compared against the upstream manifest. unsafe and // SAFETY occurrences were counted with grep -c. Tools used: openvet 0.x, GNU diff, BSD grep, BSD wc.

Results

Published source matches upstream byte-for-byte; Cargo.toml.orig matches the upstream manifest. The published Cargo.toml differs only by cargo's standard publish-time normalisation. No build.rs, no proc-macro, no binary artefacts, no install hook (justifying has-binaries, has-build-exec, has-install-exec). No malicious or unexplained behaviour observed (justifying is-benign).

This is a binding crate: it does not autonomously open files, read environment variables, spawn processes, make network calls, or hash data. It declares and wraps the host kernel's syscall ABI for consumers to use. The crate itself has no use of those capabilities (justifying uses-network, uses-filesystem, uses-environment, uses-exec, uses-jit, uses-interpreter, uses-concurrency, uses-crypto). It does not implement a parser, interpreter, JIT, protocol, data structure, algorithm, concurrency primitive, or cryptographic primitive (the protocol module declares the vocabulary of the Redox kernel protocols, not their behaviour, justifying impl-protocol; impl-crypto, impl-parser, impl-interpreter, impl-jit, impl-datastructure, impl-algorithm, impl-concurrency).

unsafe is pervasive: 47 occurrences in src/lib.rs. They fall into four categories — direct redox_*_v1 FFI calls (the vast majority), MaybeUninit::assume_init() after demuxing a successful return, two unsafe fn pointer-cast helpers in the data module (gated by runtime size/alignment assertions), and unsafe impl plain::Plain for two #[repr(C)] POD structs. The patterns are all standard FFI shapes and are mechanically minimal — every wrapper consists of exactly one FFI call demuxed via Error::demux. This justifies uses-unsafe, unsafe-safe, and unsafe-minimal. No // SAFETY: comments are present anywhere in the file, justifying unsafe-documented = false (FINDING-1).

The data::timespec_from_bytes / timespec_from_mut_bytes helpers use runtime assert! on size and alignment to guarantee the unsafe pointer cast is well-defined. The comment marks both as // TODO: Remove, signalling that the upstream considers them deprecated.

There are no tests in this crate or its upstream root — no inline #[cfg(test)], no tests/ directory, no fuzz or property suite — justifying has-unit-tests, has-integration-tests, has-fuzz-tests, has-property-tests, and unsafe-tested = false. This is unusual for a stable-ABI crate, but the surface is dominated by FFI declarations whose correctness is dictated by the Redox kernel's ABI spec rather than something the crate could meaningfully unit-test.

One low-severity quality finding (FINDING-1) was recorded for the combined absence of SAFETY comments and tests.

Conclusion

libredox is a thin, single-file binding crate for the Redox stable ABI. The published source matches upstream, the unsafe surface is dominated by mechanical FFI declarations and standard assume_init patterns over kernel-filled buffers, and no behaviour beyond the binding surface is present. The absence of safety comments and tests is the only finding and is informational. Safe to use.

Findings(1)

FINDING-1 quality low

No tests and 47 unsafe blocks without SAFETY comments

grep -c counts 47 unsafe occurrences in src/lib.rs and zero // SAFETY comments. The crate is a Redox stable-ABI binding crate, and the unsafe blocks fall into recognisable categories: (1) unsafe { redox_*_v1(...) } calls to the host syscall interface (mechanically minimal FFI, preconditions of which are documented in the Redox stable-ABI spec), (2) unsafe { ret.assume_init() } after demuxing a success return from a kernel call that wrote the MaybeUninit buffer, (3) two unsafe fns exposing pointer-cast helpers (timespec_from_bytes, mmap, munmap), and (4) two unsafe impl plain::Plain declarations that ride on the standard #[repr(C)] POD layout. None of these unsafety patterns is incorrect, justifying unsafe-safe and unsafe-minimal, but the wholesale absence of safety comments and the absence of any tests (no inline #[cfg(test)] modules in either the crate or the upstream repository's root for libredox) justify unsafe-documented = false, unsafe-tested = false, has-unit-tests = false, has-integration-tests = false, has-fuzz-tests = false, has-property-tests = false.

Annotations(1)

src/lib.rs

src/lib.rs, line 200-217

    // TODO: Remove
    pub fn timespec_from_mut_bytes(bytes: &mut [u8]) -> &mut TimeSpec {
        assert!(bytes.len() >= core::mem::size_of::<TimeSpec>());
        assert_eq!(
            bytes.as_ptr() as usize % core::mem::align_of::<TimeSpec>(),
            0
        );
        unsafe { &mut *bytes.as_mut_ptr().cast() }
    }
    // TODO: Remove
    pub fn timespec_from_bytes(bytes: &[u8]) -> &TimeSpec {
        assert!(bytes.len() >= core::mem::size_of::<TimeSpec>());
        assert_eq!(
            bytes.as_ptr() as usize % core::mem::align_of::<TimeSpec>(),
            0
        );
        unsafe { &*bytes.as_ptr().cast() }
    }

timespec_from_bytes / timespec_from_mut_bytes: pointer-cast helpers gated by runtime assertions on size and alignment. The assertions guarantee soundness of the unsafe deref. Comment marks both as TODO: Remove, signalling they are deprecated.

src/lib.rs, line 230-323

#[cfg(feature = "call")]
#[allow(dead_code)]
extern "C" {
    // NOTE: Although there are version suffixes, there'd have to be strong reasons for adding new
    // version.
    fn redox_open_v1(path_base: *const u8, path_len: usize, flags: u32, mode: u16) -> RawResult;
    fn redox_openat_v1(
        fd: usize,
        buf: *const u8,
        path_len: usize,
        flags: u32,
        fcntl_flags: u32,
    ) -> RawResult;
    fn redox_dup_v1(fd: usize, buf: *const u8, len: usize) -> RawResult;
    fn redox_dup2_v1(old_fd: usize, new_fd: usize, buf: *const u8, len: usize) -> RawResult;
    fn redox_read_v1(fd: usize, dst_base: *mut u8, dst_len: usize) -> RawResult;
    fn redox_write_v1(fd: usize, src_base: *const u8, src_len: usize) -> RawResult;
    fn redox_fchmod_v1(fd: usize, new_mode: u16) -> RawResult;
    fn redox_fchown_v1(fd: usize, new_uid: u32, new_gid: u32) -> RawResult;
    fn redox_getdents_v0(fd: usize, buf: *mut u8, buf_len: usize, opaque: u64) -> RawResult;
    fn redox_fstat_v1(fd: usize, dst: *mut data::Stat) -> RawResult;
    fn redox_fstatvfs_v1(fd: usize, dst: *mut data::StatVfs) -> RawResult;
    fn redox_fsync_v1(fd: usize) -> RawResult;
    fn redox_fdatasync_v1(fd: usize) -> RawResult;
    fn redox_ftruncate_v0(fd: usize, len: usize) -> RawResult;
    fn redox_futimens_v1(fd: usize, times: *const data::TimeSpec) -> RawResult;
    /* TODO: Support unlinkat using std_fs_call
    fn redox_unlinkat_v0(fd: usize, buf: *const u8, path_len: usize, flags: u32) -> RawResult;
    */
    fn redox_fpath_v1(fd: usize, dst_base: *mut u8, dst_len: usize) -> RawResult;
    fn redox_close_v1(fd: usize) -> RawResult;

    // NOTE: While the Redox kernel currently doesn't distinguish between threads and processes,
    // the return value of this function is expected to be treated as a process ID and not a thread
    // ID.
    fn redox_get_pid_v1() -> RawResult;

    fn redox_get_euid_v1() -> RawResult;
    fn redox_get_ruid_v1() -> RawResult;
    fn redox_get_egid_v1() -> RawResult;
    fn redox_get_rgid_v1() -> RawResult;

    fn redox_get_ens_v0() -> RawResult;
    fn redox_get_ns_v0() -> RawResult;
    // This function is used to get the credentials, pid, euid, egid etc. of the process with the target pid.
    fn redox_get_proc_credentials_v1(cap_fd: usize, target_pid: usize, buf: &mut [u8])
        -> RawResult;

    fn redox_setrens_v1(rns: usize, ens: usize) -> RawResult;
    fn redox_mkns_v1(names: *const data::IoVec, num_names: usize, _flags: u32) -> RawResult;

    fn redox_kill_v1(pid: usize, signal: u32) -> RawResult;
    fn redox_waitpid_v1(pid: usize, status: *mut i32, options: u32) -> RawResult;

    fn redox_sigprocmask_v1(how: u32, new: *const u64, old: *mut u64) -> RawResult;
    fn redox_sigaction_v1(
        signal: u32,
        new: *const data::SigAction,
        old: *mut data::SigAction,
    ) -> RawResult;

    fn redox_clock_gettime_v1(clock: usize, ts: *mut data::TimeSpec) -> RawResult;

    fn redox_mmap_v1(
        addr: *mut (),
        unaligned_len: usize,
        prot: u32,
        flags: u32,
        fd: usize,
        offset: u64,
    ) -> RawResult;
    fn redox_munmap_v1(addr: *mut (), unaligned_len: usize) -> RawResult;

    fn redox_strerror_v1(dst: *mut u8, dst_len: *mut usize, error: u32) -> RawResult;

    fn redox_sys_call_v0(
        fd: usize,
        payload: *mut u8,
        payload_len: usize,
        flags: usize,
        metadata: *const u64,
        metadata_len: usize,
    ) -> RawResult;
    fn redox_get_socket_token_v0(fd: usize, payload: *mut u8, payload_len: usize) -> RawResult;

    fn redox_setns_v0(fd: usize) -> RawResult;

    fn redox_register_scheme_to_ns_v0(
        ns_fd: usize,
        name_base: *const u8,
        name_len: usize,
        cap_fd: usize,
    ) -> RawResult;
}

extern "C" declarations for ~30 stable Redox syscall ABI entry-points (redox_*_v1 / _v0). Their preconditions are part of the Redox stable ABI spec; this crate just declares them. Justifies uses-unsafe and unsafe-minimal — every line below is a wrapper over these declarations.

src/lib.rs, line 326-455

pub struct Fd(usize);

#[cfg(feature = "call")]
impl Fd {
    pub fn new(raw: usize) -> Self {
        Self(raw)
    }
    #[inline]
    pub fn open(path: &str, flags: i32, mode: u16) -> Result<Self> {
        Ok(Self(call::open(path, flags, mode)?))
    }
    #[inline]
    pub fn openat(&self, path: &str, flags: i32, fcntl_flags: u32) -> Result<Self> {
        Ok(Self(call::openat(self.raw(), path, flags, fcntl_flags)?))
    }
    #[inline]
    pub fn dup(&self, buf: &[u8]) -> Result<usize> {
        call::dup(self.raw(), buf)
    }
    #[inline]
    pub fn dup2(&self, new_fd: usize, buf: &[u8]) -> Result<usize> {
        call::dup2(self.raw(), new_fd, buf)
    }

    #[inline]
    pub const fn raw(&self) -> usize {
        self.0
    }

    #[inline]
    pub fn into_raw(self) -> usize {
        let raw = self.raw();
        core::mem::forget(self);
        raw
    }

    #[inline]
    pub fn read(&self, buf: &mut [u8]) -> Result<usize> {
        call::read(self.raw(), buf)
    }
    #[inline]
    pub fn write(&self, buf: &[u8]) -> Result<usize> {
        call::write(self.raw(), buf)
    }
    #[inline]
    pub fn chmod(&self, new_mode: u16) -> Result<()> {
        call::fchmod(self.raw(), new_mode)
    }
    #[inline]
    pub fn chown(&self, new_uid: u32, new_gid: u32) -> Result<()> {
        call::fchown(self.raw(), new_uid, new_gid)
    }
    #[inline]
    pub fn getdents(self, buf: &mut [u8], opaque: u64) -> Result<usize> {
        call::getdents(self.raw(), buf, opaque)
    }
    pub fn stat(&self) -> Result<data::Stat> {
        call::fstat(self.raw())
    }
    pub fn statvfs(&self) -> Result<data::StatVfs> {
        call::fstatvfs(self.raw())
    }
    #[inline]
    pub fn fsync(&self) -> Result<()> {
        call::fsync(self.raw())
    }
    #[inline]
    pub fn fdatasync(&self) -> Result<()> {
        call::fdatasync(self.raw())
    }
    #[inline]
    pub fn ftruncate(self, len: usize) -> Result<()> {
        call::ftruncate(self.raw(), len)
    }
    #[inline]
    pub fn futimens(self, times: &[data::TimeSpec; 2]) -> Result<()> {
        call::futimens(self.raw(), times)
    }
    /* TODO: Support unlinkat using std_fs_call
    #[inline]
    pub fn unlinkat(&self, path: &str, flags: i32) -> Result<()> {
        call::unlinkat(self.raw(), path, flags)
    }
    */
    #[inline]
    pub fn fpath(&self, path: &mut [u8]) -> Result<usize> {
        call::fpath(self.raw(), path)
    }
    #[inline]
    pub fn close(self) -> Result<()> {
        call::close(self.into_raw())
    }

    #[cfg(feature = "redox_syscall")]
    #[inline]
    pub fn call_ro(
        &self,
        payload: &mut [u8],
        flags: syscall::CallFlags,
        metadata: &[u64],
    ) -> Result<usize> {
        call::call_ro(self.raw(), payload, flags, metadata)
    }
    #[cfg(feature = "redox_syscall")]
    #[inline]
    pub fn call_wo(
        &self,
        payload: &[u8],
        flags: syscall::CallFlags,
        metadata: &[u64],
    ) -> Result<usize> {
        call::call_wo(self.raw(), payload, flags, metadata)
    }
    #[cfg(feature = "redox_syscall")]
    #[inline]
    pub fn call_rw(
        &self,
        payload: &mut [u8],
        flags: syscall::CallFlags,
        metadata: &[u64],
    ) -> Result<usize> {
        call::call_rw(self.raw(), payload, flags, metadata)
    }
}
#[cfg(feature = "call")]
impl Drop for Fd {
    fn drop(&mut self) {
        let _ = unsafe { redox_close_v1(self.0) };
    }
}

Fd: smart wrapper around a usize file descriptor. Drop calls redox_close_v1 unconditionally; into_raw uses core::mem::forget to skip the Drop impl. Pure leaf-RAII.

src/lib.rs, line 457-786

#[cfg(feature = "call")]
pub mod call {
    use core::mem::MaybeUninit;

    use super::*;

    /// flags and mode are binary compatible with libc
    #[inline]
    pub fn open(path: impl AsRef<str>, flags: i32, mode: u16) -> Result<usize> {
        let path = path.as_ref();
        Ok(Error::demux(unsafe {
            redox_open_v1(path.as_ptr(), path.len(), flags as u32, mode)
        })?)
    }
    #[inline]
    pub fn openat(
        fd: usize,
        path: impl AsRef<[u8]>,
        flags: i32,
        fcntl_flags: u32,
    ) -> Result<usize> {
        let path = path.as_ref();
        Ok(Error::demux(unsafe {
            redox_openat_v1(fd, path.as_ptr(), path.len(), flags as u32, fcntl_flags)
        })?)
    }
    #[inline]
    pub fn dup(fd: usize, buf: impl AsRef<[u8]>) -> Result<usize> {
        let buf = buf.as_ref();
        Ok(Error::demux(unsafe {
            redox_dup_v1(fd, buf.as_ptr(), buf.len())
        })?)
    }
    #[inline]
    pub fn dup2(old_fd: usize, new_fd: usize, buf: impl AsRef<[u8]>) -> Result<usize> {
        let buf = buf.as_ref();
        Ok(Error::demux(unsafe {
            redox_dup2_v1(old_fd, new_fd, buf.as_ptr(), buf.len())
        })?)
    }
    #[inline]
    pub fn read(raw_fd: usize, buf: &mut [u8]) -> Result<usize> {
        Ok(Error::demux(unsafe {
            redox_read_v1(raw_fd, buf.as_mut_ptr(), buf.len())
        })?)
    }
    #[inline]
    pub fn write(raw_fd: usize, buf: &[u8]) -> Result<usize> {
        Error::demux(unsafe { redox_write_v1(raw_fd, buf.as_ptr(), buf.len()) })
    }
    #[inline]
    pub fn fchmod(raw_fd: usize, new_mode: u16) -> Result<()> {
        Error::demux(unsafe { redox_fchmod_v1(raw_fd, new_mode) })?;
        Ok(())
    }
    #[inline]
    pub fn fchown(raw_fd: usize, new_uid: u32, new_gid: u32) -> Result<()> {
        Error::demux(unsafe { redox_fchown_v1(raw_fd, new_uid, new_gid) })?;
        Ok(())
    }
    #[inline]
    pub fn getdents(fd: usize, buf: &mut [u8], opaque: u64) -> Result<usize> {
        Error::demux(unsafe { redox_getdents_v0(fd, buf.as_mut_ptr(), buf.len(), opaque) })
    }
    #[inline]
    pub fn fstat(raw_fd: usize) -> Result<data::Stat> {
        unsafe {
            let mut ret = MaybeUninit::uninit();
            Error::demux(redox_fstat_v1(raw_fd, ret.as_mut_ptr()))?;
            Ok(ret.assume_init())
        }
    }
    #[inline]
    pub fn fstatvfs(raw_fd: usize) -> Result<data::StatVfs> {
        unsafe {
            let mut ret = MaybeUninit::uninit();
            Error::demux(redox_fstatvfs_v1(raw_fd, ret.as_mut_ptr()))?;
            Ok(ret.assume_init())
        }
    }
    #[inline]
    pub fn fsync(raw_fd: usize) -> Result<()> {
        Error::demux(unsafe { redox_fsync_v1(raw_fd) }).map(|_| ())
    }
    #[inline]
    pub fn fdatasync(raw_fd: usize) -> Result<()> {
        Error::demux(unsafe { redox_fdatasync_v1(raw_fd) }).map(|_| ())
    }
    #[inline]
    pub fn ftruncate(raw_fd: usize, new_size: usize) -> Result<()> {
        Error::demux(unsafe { redox_ftruncate_v0(raw_fd, new_size) }).map(|_| ())
    }
    #[inline]
    pub fn futimens(raw_fd: usize, times: &[data::TimeSpec; 2]) -> Result<()> {
        Error::demux(unsafe { redox_futimens_v1(raw_fd, times.as_ptr()) })?;
        Ok(())
    }
    /* TODO: Support unlinkat using std_fs_call
    #[inline]
    pub fn unlinkat(fd: usize, path: impl AsRef<[u8]>, flags: i32) -> Result<()> {
        let path = path.as_ref();
        Error::demux(unsafe { redox_unlinkat_v0(fd, path.as_ptr(), path.len(), flags as u32) })
            .map(|_| ())
    }
    */
    #[inline]
    pub fn fpath(raw_fd: usize, buf: &mut [u8]) -> Result<usize> {
        Error::demux(unsafe { redox_fpath_v1(raw_fd, buf.as_mut_ptr(), buf.len()) })
    }
    #[inline]
    pub fn close(raw_fd: usize) -> Result<()> {
        Error::demux(unsafe { redox_close_v1(raw_fd) })?;
        Ok(())
    }
    #[cfg(feature = "redox_syscall")]
    #[inline]
    pub fn call_ro(
        fd: usize,
        payload: &mut [u8],
        flags: syscall::CallFlags,
        metadata: &[u64],
    ) -> Result<usize> {
        Ok(Error::demux(unsafe {
            redox_sys_call_v0(
                fd,
                payload.as_mut_ptr(),
                payload.len(),
                (flags | syscall::CallFlags::READ).bits(),
                metadata.as_ptr(),
                metadata.len(),
            )
        })?)
    }
    #[cfg(feature = "redox_syscall")]
    #[inline]
    pub fn call_wo(
        fd: usize,
        payload: &[u8],
        flags: syscall::CallFlags,
        metadata: &[u64],
    ) -> Result<usize> {
        Ok(Error::demux(unsafe {
            redox_sys_call_v0(
                fd,
                payload.as_ptr() as *mut u8,
                payload.len(),
                (flags | syscall::CallFlags::WRITE).bits(),
                metadata.as_ptr(),
                metadata.len(),
            )
        })?)
    }
    #[cfg(feature = "redox_syscall")]
    #[inline]
    pub fn call_rw(
        fd: usize,
        payload: &mut [u8],
        flags: syscall::CallFlags,
        metadata: &[u64],
    ) -> Result<usize> {
        Ok(Error::demux(unsafe {
            redox_sys_call_v0(
                fd,
                payload.as_mut_ptr(),
                payload.len(),
                (flags | syscall::CallFlags::READ | syscall::CallFlags::WRITE).bits(),
                metadata.as_ptr(),
                metadata.len(),
            )
        })?)
    }

    #[inline]
    pub fn geteuid() -> Result<usize> {
        Error::demux(unsafe { redox_get_euid_v1() })
    }

    #[inline]
    pub fn getruid() -> Result<usize> {
        Error::demux(unsafe { redox_get_ruid_v1() })
    }

    #[inline]
    pub fn getegid() -> Result<usize> {
        Error::demux(unsafe { redox_get_egid_v1() })
    }
    #[inline]
    pub fn getrgid() -> Result<usize> {
        Error::demux(unsafe { redox_get_rgid_v1() })
    }
    #[inline]
    pub fn getpid() -> Result<usize> {
        Error::demux(unsafe { redox_get_pid_v1() })
    }
    #[inline]
    pub fn getens() -> Result<usize> {
        Error::demux(unsafe { redox_get_ens_v0() })
    }

    #[inline]
    // [u8; size_of::<crate::protocol::ProcMeta>()]
    pub fn get_proc_credentials(cap_fd: usize, target_pid: usize, buf: &mut [u8]) -> Result<usize> {
        Error::demux(unsafe { redox_get_proc_credentials_v1(cap_fd, target_pid, buf) })
    }

    #[inline]
    pub fn setrens(rns: usize, ens: usize) -> Result<usize> {
        Error::demux(unsafe { redox_setrens_v1(rns, ens) })
    }
    #[inline]
    pub fn waitpid(pid: usize, status: &mut i32, options: i32) -> Result<usize> {
        Error::demux(unsafe { redox_waitpid_v1(pid, status as *mut i32, options as u32) })
    }
    #[inline]
    pub fn kill(pid: usize, signal: u32) -> Result<()> {
        Error::demux(unsafe { redox_kill_v1(pid, signal) }).map(|_| ())
    }
    #[inline]
    pub fn clock_gettime(clock: i32) -> Result<data::TimeSpec> {
        unsafe {
            let mut ret = MaybeUninit::uninit();
            Error::demux(redox_clock_gettime_v1(clock as usize, ret.as_mut_ptr()))?;
            Ok(ret.assume_init())
        }
    }
    #[inline]
    pub fn sigprocmask(
        how: i32,
        newmask: Option<&data::SigSet>,
        oldmask: Option<&mut data::SigSet>,
    ) -> Result<()> {
        Error::demux(unsafe {
            redox_sigprocmask_v1(
                how as u32,
                newmask.map_or(core::ptr::null(), |m| m),
                oldmask.map_or(core::ptr::null_mut(), |m| m),
            )
        })
        .map(|_| ())
    }
    #[inline]
    pub fn sigaction(
        signal: i32,
        newact: Option<&data::SigAction>,
        oldact: Option<&mut data::SigAction>,
    ) -> Result<()> {
        Error::demux(unsafe {
            redox_sigaction_v1(
                signal as u32,
                newact.map_or(core::ptr::null(), |m| m),
                oldact.map_or(core::ptr::null_mut(), |m| m),
            )
        })
        .map(|_| ())
    }

    #[derive(Clone, Copy, Debug)]
    pub struct MmapArgs {
        pub addr: *mut (),
        pub length: usize,
        pub prot: u32,
        pub flags: u32,
        pub fd: usize,
        pub offset: u64,
    }
    #[inline]
    pub unsafe fn mmap(args: MmapArgs) -> Result<*mut ()> {
        Error::demux(redox_mmap_v1(
            args.addr,
            args.length,
            args.prot,
            args.flags,
            args.fd,
            args.offset,
        ))
        .map(|addr| addr as *mut ())
    }
    #[inline]
    pub unsafe fn munmap(addr: *mut (), length: usize) -> Result<()> {
        Error::demux(redox_munmap_v1(addr, length)).map(|_| ())
    }

    #[inline]
    pub fn strerror(error: u16, desc: &mut [u8]) -> Option<(&str, usize)> {
        unsafe {
            let mut len_inout = desc.len();
            let copied_len = Error::demux(redox_strerror_v1(
                desc.as_mut_ptr(),
                &mut len_inout,
                error.into(),
            ))
            .ok()?;
            Some((
                core::str::from_utf8_unchecked(&desc[..copied_len]),
                len_inout,
            ))
        }
    }

    #[inline]
    #[cfg(feature = "mkns")]
    pub fn mkns(names: &[ioslice::IoSlice]) -> Result<usize> {
        // no-op
        let iovecs = ioslice::IoSlice::cast_to_raw_iovecs(names);

        unsafe { Error::demux(redox_mkns_v1(iovecs.as_ptr(), iovecs.len(), 0)) }
    }

    #[inline]
    pub fn get_socket_token(fd: usize, buf: &mut [u8]) -> Result<usize> {
        Error::demux(unsafe { redox_get_socket_token_v0(fd, buf.as_mut_ptr(), buf.len()) })
    }

    #[inline]
    pub fn setns(fd: usize) -> Result<usize> {
        Error::demux(unsafe { redox_setns_v0(fd) })
    }
    #[inline]
    pub fn getns() -> Result<usize> {
        Error::demux(unsafe { redox_get_ns_v0() })
    }

    #[inline]
    pub fn register_scheme_to_ns(ns_fd: usize, name: impl AsRef<str>, cap_fd: usize) -> Result<()> {
        let name = name.as_ref();
        Error::demux(unsafe {
            redox_register_scheme_to_ns_v0(ns_fd, name.as_ptr(), name.len(), cap_fd)
        })
        .map(|_| ())
    }

call:: module: safe Rust wrappers around the redox_*_v1 / _v0 FFI declarations. Each wrapper demuxes the returned usize via Error::demux, then exposes a typed Result. Justifies uses-unsafe and unsafe-minimal. See FINDING-1 for the missing SAFETY comments.

src/lib.rs, line 789-1080

#[cfg(feature = "protocol")]
pub mod protocol {
    use bitflags::bitflags;

    #[derive(Clone, Copy, Debug, Default)]
    #[repr(C)]
    pub struct ProcMeta {
        pub pid: u32,
        pub pgid: u32,
        pub ppid: u32,
        pub ruid: u32,
        pub euid: u32,
        pub suid: u32,
        pub rgid: u32,
        pub egid: u32,
        pub sgid: u32,
        pub ens: u32,
        pub rns: u32,
    }
    unsafe impl plain::Plain for ProcMeta {}

    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    #[repr(usize)]
    pub enum ProcCall {
        Waitpid = 0,
        Setrens = 1,
        Exit = 2,
        Waitpgid = 3,
        SetResugid = 4,
        Setpgid = 5,
        Getsid = 6,
        Setsid = 7,
        Kill = 8,
        Sigq = 9,

        // TODO: replace with sendfd equivalent syscall for sending memory
        SyncSigPctl = 10,
        Sigdeq = 11,
        Getppid = 12,
        Rename = 13,
        DisableSetpgid = 14,

        // Temporary calls for getting process credentials
        GetProcCredentials = 15,

        SetProcPriority = 16,
        GetProcPriority = 17,
    }
    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    #[repr(usize)]
    pub enum ThreadCall {
        // TODO: replace with sendfd equivalent syscall for sending memory, or force userspace to
        // obtain its TCB memory from this server
        SyncSigTctl = 0,
        SignalThread = 1,
    }

    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    #[repr(usize)]
    #[non_exhaustive]
    pub enum SocketCall {
        Bind = 0,
        Connect = 1,
        SetSockOpt = 2,
        GetSockOpt = 3,
        SendMsg = 4,
        RecvMsg = 5,
        Unbind = 6,
        GetToken = 7,
        GetPeerName = 8,
        Shutdown = 9,
    }

    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    #[repr(usize)]
    #[non_exhaustive]
    pub enum FsCall {
        Connect = 0,
    }

    impl ProcCall {
        pub fn try_from_raw(raw: usize) -> Option<Self> {
            Some(match raw {
                0 => Self::Waitpid,
                1 => Self::Setrens,
                2 => Self::Exit,
                3 => Self::Waitpgid,
                4 => Self::SetResugid,
                5 => Self::Setpgid,
                6 => Self::Getsid,
                7 => Self::Setsid,
                8 => Self::Kill,
                9 => Self::Sigq,
                10 => Self::SyncSigPctl,
                11 => Self::Sigdeq,
                12 => Self::Getppid,
                13 => Self::Rename,
                14 => Self::DisableSetpgid,
                15 => Self::GetProcCredentials,
                16 => Self::SetProcPriority,
                17 => Self::GetProcPriority,
                _ => return None,
            })
        }
    }
    impl ThreadCall {
        pub fn try_from_raw(raw: usize) -> Option<Self> {
            Some(match raw {
                0 => Self::SyncSigTctl,
                1 => Self::SignalThread,
                _ => return None,
            })
        }
    }

    impl SocketCall {
        pub fn try_from_raw(raw: usize) -> Option<Self> {
            Some(match raw {
                0 => Self::Bind,
                1 => Self::Connect,
                2 => Self::SetSockOpt,
                3 => Self::GetSockOpt,
                4 => Self::SendMsg,
                5 => Self::RecvMsg,
                6 => Self::Unbind,
                7 => Self::GetToken,
                8 => Self::GetPeerName,
                9 => Self::Shutdown,
                _ => return None,
            })
        }
    }

    impl FsCall {
        pub fn try_from_raw(raw: usize) -> Option<Self> {
            Some(match raw {
                0 => Self::Connect,
                _ => return None,
            })
        }
    }

    bitflags! {
        #[derive(Clone, Copy, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
        pub struct WaitFlags: usize {
            const WNOHANG =    0x01;
            const WUNTRACED =  0x02;
            const WCONTINUED = 0x08;
        }
    }
    /// True if status indicates the child is stopped.
    pub fn wifstopped(status: usize) -> bool {
        (status & 0xff) == 0x7f
    }

    /// If wifstopped(status), the signal that stopped the child.
    pub fn wstopsig(status: usize) -> usize {
        (status >> 8) & 0xff
    }

    /// True if status indicates the child continued after a stop.
    pub fn wifcontinued(status: usize) -> bool {
        status == 0xffff
    }

    /// True if STATUS indicates termination by a signal.
    pub fn wifsignaled(status: usize) -> bool {
        ((status & 0x7f) + 1) as i8 >= 2
    }

    /// If wifsignaled(status), the terminating signal.
    pub fn wtermsig(status: usize) -> usize {
        status & 0x7f
    }

    /// True if status indicates normal termination.
    pub fn wifexited(status: usize) -> bool {
        wtermsig(status) == 0
    }

    /// If wifexited(status), the exit status.
    pub fn wexitstatus(status: usize) -> usize {
        (status >> 8) & 0xff
    }

    /// True if status indicates a core dump was created.
    pub fn wcoredump(status: usize) -> bool {
        (status & 0x80) != 0
    }
    #[derive(Clone, Copy, Debug)]
    pub enum ProcKillTarget {
        ThisGroup,
        SingleProc(usize),
        ProcGroup(usize),
        All,
    }
    impl ProcKillTarget {
        pub fn raw(self) -> usize {
            match self {
                Self::ThisGroup => 0,
                Self::SingleProc(p) => p,
                Self::ProcGroup(g) => usize::wrapping_neg(g),
                Self::All => usize::wrapping_neg(1),
            }
        }
        pub fn from_raw(raw: usize) -> Self {
            let raw = raw as isize;
            if raw == 0 {
                Self::ThisGroup
            } else if raw == -1 {
                Self::All
            } else if raw < 0 {
                Self::ProcGroup(raw.wrapping_neg() as usize)
            } else {
                Self::SingleProc(raw as usize)
            }
        }
    }
    #[derive(Copy, Clone, Debug, Default, PartialEq)]
    #[repr(C)]
    pub struct RtSigInfo {
        pub arg: usize,
        pub code: i32,
        pub uid: u32,
        pub pid: u32, // TODO: usize?
    }
    unsafe impl plain::Plain for RtSigInfo {}

    pub const SIGHUP: usize = 1;
    pub const SIGINT: usize = 2;
    pub const SIGQUIT: usize = 3;
    pub const SIGILL: usize = 4;
    pub const SIGTRAP: usize = 5;
    pub const SIGABRT: usize = 6;
    pub const SIGBUS: usize = 7;
    pub const SIGFPE: usize = 8;
    pub const SIGKILL: usize = 9;
    pub const SIGUSR1: usize = 10;
    pub const SIGSEGV: usize = 11;
    pub const SIGUSR2: usize = 12;
    pub const SIGPIPE: usize = 13;
    pub const SIGALRM: usize = 14;
    pub const SIGTERM: usize = 15;
    pub const SIGSTKFLT: usize = 16;
    pub const SIGCHLD: usize = syscall::SIGCHLD;
    pub const SIGCONT: usize = 18;
    pub const SIGSTOP: usize = 19;
    pub const SIGTSTP: usize = syscall::SIGTSTP;
    pub const SIGTTIN: usize = syscall::SIGTTIN;
    pub const SIGTTOU: usize = syscall::SIGTTOU;
    pub const SIGURG: usize = 23;
    pub const SIGXCPU: usize = 24;
    pub const SIGXFSZ: usize = 25;
    pub const SIGVTALRM: usize = 26;
    pub const SIGPROF: usize = 27;
    pub const SIGWINCH: usize = 28;
    pub const SIGIO: usize = 29;
    pub const SIGPWR: usize = 30;
    pub const SIGSYS: usize = 31;

    bitflags! {
        #[derive(Clone, Copy, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
        pub struct NsPermissions: usize {
            /// List schemes in the namespace
            const LIST = 1 << 0;
            /// Register a new scheme in the namespace
            const INSERT = 1 << 1;
            /// Delete a scheme from the namespace
            const DELETE = 1 << 2;
            /// Get scheme creation capabilities of the namespace
            const SCHEME_CREATE = 1 << 3;
        }
    }

    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    #[repr(usize)]
    pub enum NsDup {
        ForkNs = 0,
        ShrinkPermissions = 1,
        IssueRegister = 2,
    }
    impl NsDup {
        pub fn try_from_raw(raw: usize) -> Option<Self> {
            Some(match raw {
                0 => Self::ForkNs,
                1 => Self::ShrinkPermissions,
                2 => Self::IssueRegister,
                _ => return None,
            })
        }
    }
}

protocol:: module: protocol-vocabulary constants (signal numbers, WIF*/W*sig macros, bitflags!-generated WaitFlags / NsPermissions, enum ProcCall/ThreadCall/SocketCall/FsCall/NsDup with try_from_raw fallbacks, ProcMeta / RtSigInfo POD structs). Two unsafe impl plain::Plain declarations on the POD structs; the trait is unsafe because Plain promises bit-castable representation, which is satisfied by the #[repr(C)] structs containing only u32/usize fields.