cargo / socket2 / audit
cargo : socket2 @ 0.6.4
PE Patrick Elsen signed 2026-06-02 published 2026-06-02

Claims

filesystem-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

socket2 0.6.4 is the rust-lang FFI wrapper over the platform socket API: Socket, SockAddr, SockRef, TcpKeepalive, plus MsgHdr/MaybeUninitSlice. Source matches upstream byte-for-byte. ~240 unsafe sites, all behind a single syscall! macro per back-end (libc on Unix/WASI, windows-sys on Windows); fd/SOCKET lifetimes delegated to libstd's OwnedFd/OwnedSocket. No filesystem, env, exec, crypto, or concurrency primitives implemented. No findings.

Report

Subject

socket2 is a Rust library for creating and configuring sockets with finer-grained control than the standard library exposes. It is owned by the rust-lang organisation. The crate's headline type, Socket, is a thin owned-handle wrapper over std::os::fd::OwnedFd on Unix-likes / WASI and std::os::windows::io::OwnedSocket on Windows. Around that, the crate exposes domain/type/protocol newtype constants, the SockAddr/SockAddrStorage types over the platform's sockaddr_storage, a SockRef borrow that re-uses the API against externally-owned descriptors, TcpKeepalive parameters, and the MsgHdr/MsgHdrMut/MaybeUninitSlice types used for the scatter-gather APIs. The crate is no-build-script and exports the all feature to opt into platform-specific extras.

The crate is by design a syscall-level wrapper. It does not implement any network protocol, validation, or buffering of its own; everything routes to a single corresponding libc or WinSock entry point.

Methodology

The published crate was downloaded by openvet audit new and unpacked into contents/; the upstream Git repository (https://github.com/rust-lang/socket2) was cloned into vcs/ and checked out at the commit recorded in .cargo_vcs_info.json.

Tools used:

  • openvet audit (workspace creation, annotations, claims, findings, dependency narratives, report).
  • diff -rq to compare contents/src/ against vcs/src/.
  • grep to enumerate unsafe keyword sites (~240 total), extern "C" declarations (none in this crate), and standard-library I/O patterns outside socket APIs (std::env, std::process::Command, std::fs::File/OpenOptions, std::thread::spawn, tokio, reqwest, cryptography crates).
  • wc -l for line counts.

Reading: src/lib.rs in full; src/sockref.rs in full; head and key sections of src/sockaddr.rs (around as_socket and the From<SocketAddr*> impls); src/socket.rs head and around Socket::from_raw; the syscall! macro and its surrounding type definitions in both src/sys/unix.rs (line 345) and src/sys/windows.rs (line 149); Windows-specific init (std::sync::Once-gated init(), src/sys/windows.rs:280-289); the getsockopt/setsockopt definitions (unix.rs:1358-1391, windows.rs:874-900); the OwnedFd/OwnedSocket type aliases (unix.rs:929-930, windows.rs:291). The integration-test file (vcs/tests/socket.rs, ~1970 lines) was surveyed for shape; it covers TCP and UDP socket creation, every option setter/getter, Unix domain sockets, and platform-conditional behaviour.

Results

The diff between the published contents/src/ and the upstream Git checkout shows that all source files match byte-for-byte. The Cargo.toml differences are limited to cargo's standard normalisation. The published artefact excludes .github/, tests/, and other repository-only files via the include = [...] list.

The crate ships no binary artefacts (justifying has-binaries), no build.rs (justifying has-build-exec), and no installer hook (justifying has-install-exec). The [lib] section sets no proc-macro = true. There are no direct extern "C" blocks; every FFI call goes through libc (unix) or windows_sys::Win32::Networking::WinSock (windows). Both are external crates not in scope for this audit.

unsafe is pervasive (~240 occurrences) but disciplined. The dominant pattern is the syscall! macro in each back-end, which wraps a single libc::$fn(...) or winsock::$fn(...) call in an unsafe { ... } block and translates the C error convention into an io::Result. Beyond that, unsafe appears at: (1) the sockaddr family-tagged casts in src/sockaddr.rs (only after ss_family is checked); (2) MaybeUninit::assume_init after a syscall has filled the storage (with len cross-checked); (3) the Socket::from_raw_fd/from_raw_socket calls in sockref.rs:80-111, paired with an assert! that the raw value is in range; (4) mem::zeroed::<sockaddr_storage>() which is sound because sockaddr_storage is a C struct of integer fields with no invalid bit patterns. Each non-trivial unsafe block carries a // SAFETY: comment. The crate's MSRV is 1.70 and OwnedFd/OwnedSocket are used for lifetime management, so the historical "close-on-drop fd safety" surface is delegated to the standard library. Together this is the basis for uses-unsafe, unsafe-safe, unsafe-documented, and unsafe-minimal.

The crate is intrinsically network: Socket::new, bind, connect, listen, accept, send, recv and dozens of option setters are the public API. Inputs (addresses, options, buffers) are passed straight to the kernel without crate-side validation; the kernel performs whatever validation it does. The crate does not introduce a network-validation surface of its own, supporting uses-network and network-safe. The crate does not pick or enforce a "secure protocol by default" — it exposes raw socket primitives — so network-secure is not asserted; this is the natural shape of a primitives library.

Filesystem usage is limited to AF_UNIX socket paths, which the crate accepts as &Path/&[u8] and copies into the sun_path field of sockaddr_un (src/sys/unix.rs:703-741). The crate does not open, read, or write files itself; it merely forwards the path to bind(2). No traversal-relevant logic is introduced; the path is the caller's responsibility. Supports uses-filesystem and filesystem-safe.

No use of std::env, std::process::Command, std::fs::*, thread spawning, cryptography, JIT, or interpreters was found. Justifies uses-environment, uses-exec, uses-crypto, uses-jit, and uses-interpreter. A single std::sync::Once is used in src/sys/windows.rs to trigger libstd's WinSock initialisation by binding a dummy UdpSocket; this is not a concurrency primitive the crate implements or maintains, just a one-shot init guard. Justifies uses-concurrency.

Per the registry-specific guidance, the crate does not itself implement any parser, interpreter, JIT, protocol, data structure, algorithm, cryptographic primitive, or concurrency primitive; the corresponding impl-* claims (impl-parser, impl-interpreter, impl-jit, impl-protocol, impl-datastructure, impl-algorithm, impl-crypto, impl-concurrency) are all false. The crate is a kernel binding, not an implementation.

Tests: a single ~1970-line integration test (tests/socket.rs) lives upstream and is shipped neither in the .crate (excluded via the manifest's include list) nor referenced in the [[test]] section of the published Cargo.toml. CI exercises it on every platform the crate claims to support (per vcs/.github/). No #[cfg(test)] unit-test modules are present in src/, and no fuzz harness or property-test harness ships with the crate, justifying has-unit-tests = false, has-integration-tests = true (the file exists upstream and runs in CI), has-fuzz-tests = false, and has-property-tests = false. The integration tests run the actual socket APIs against the host kernel, which is what supports unsafe-tested.

No findings were recorded. The crate is authored by rust-lang maintainers, matches its upstream commit byte-for-byte, and contains no behaviour at odds with its documented purpose. This is the basis for is-benign.

Conclusion

socket2 is a mature, well-scoped FFI binding to the socket APIs of every supported platform. Its unsafe surface is large (because that's what binding a C-shaped API entails) but is structured around a single syscall! macro per back-end, with the lifetime-of-fd concerns delegated to libstd's OwnedFd/OwnedSocket. The crate does not itself implement any protocol or attempt validation; that's the caller's job. No findings were observed.

Findings

No findings.

Annotations(6)

src

Five Rust source files plus two platform back-ends (sys/unix.rs, sys/windows.rs). lib.rs defines the cross-platform vocabulary (Domain, Type, Protocol, RecvFlags, TcpKeepalive, MaybeUninitSlice, the cross-platform MsgHdr/MsgHdrMut). socket.rs is the main Socket API (~2470 lines of thin wrappers over the back-end). sockaddr.rs provides the SockAddr / SockAddrStorage types over sockaddr_storage. sockref.rs is a ManuallyDrop-based borrowed-view onto a raw fd/SOCKET so the crate's API can be applied to std::net::TcpStream etc. without taking ownership. The platform back-ends do all FFI through libc or windows_sys — no direct extern "C" block.

src/lib.rs

src/lib.rs, line 175-187

mod sockaddr;
mod socket;
mod sockref;

#[cfg_attr(
    any(unix, all(target_os = "wasi", not(target_env = "p1"))),
    path = "sys/unix.rs"
)]
#[cfg_attr(windows, path = "sys/windows.rs")]
mod sys;

#[cfg(not(any(windows, unix, all(target_os = "wasi", not(target_env = "p1")))))]
compile_error!("Socket2 doesn't support the compile target");

Platform back-end is selected via #[cfg_attr(...path="sys/unix.rs")] / #[cfg_attr(windows, path="sys/windows.rs")] with a compile_error! for unsupported targets. Justifies the strict separation of FFI per platform.

src/sockaddr.rs

src/sockaddr.rs, line 281-311

    /// Returns this address as a `SocketAddr` if it is in the `AF_INET` (IPv4)
    /// or `AF_INET6` (IPv6) family, otherwise returns `None`.
    pub fn as_socket(&self) -> Option<SocketAddr> {
        if self.storage.ss_family == AF_INET as sa_family_t {
            // SAFETY: if the `ss_family` field is `AF_INET` then storage must
            // be a `sockaddr_in`.
            let addr = unsafe { &*(ptr::addr_of!(self.storage).cast::<sockaddr_in>()) };
            let ip = crate::sys::from_in_addr(addr.sin_addr);
            let port = u16::from_be(addr.sin_port);
            Some(SocketAddr::V4(SocketAddrV4::new(ip, port)))
        } else if self.storage.ss_family == AF_INET6 as sa_family_t {
            // SAFETY: if the `ss_family` field is `AF_INET6` then storage must
            // be a `sockaddr_in6`.
            let addr = unsafe { &*(ptr::addr_of!(self.storage).cast::<sockaddr_in6>()) };
            let ip = crate::sys::from_in6_addr(addr.sin6_addr);
            let port = u16::from_be(addr.sin6_port);
            Some(SocketAddr::V6(SocketAddrV6::new(
                ip,
                port,
                addr.sin6_flowinfo,
                #[cfg(any(unix, all(target_os = "wasi", not(target_env = "p1"))))]
                addr.sin6_scope_id,
                #[cfg(windows)]
                unsafe {
                    addr.Anonymous.sin6_scope_id
                },
            )))
        } else {
            None
        }
    }

SockAddr::as_socket performs the family-tagged cast from sockaddr_storage to sockaddr_in or sockaddr_in6. The unsafe cast is guarded by checking ss_family == AF_INET / AF_INET6 (per the SAFETY comments). Sound by construction.

src/sockref.rs

src/sockref.rs, line 80-111

#[cfg(any(unix, all(target_os = "wasi", not(target_env = "p1"))))]
impl<'s, S> From<&'s S> for SockRef<'s>
where
    S: AsFd,
{
    /// The caller must ensure `S` is actually a socket.
    fn from(socket: &'s S) -> Self {
        let fd = socket.as_fd().as_raw_fd();
        assert!(fd >= 0);
        SockRef {
            socket: ManuallyDrop::new(unsafe { Socket::from_raw_fd(fd) }),
            _lifetime: PhantomData,
        }
    }
}

/// On Unix, a corresponding `From<&impl AsFd>` implementation exists.
#[cfg(windows)]
impl<'s, S> From<&'s S> for SockRef<'s>
where
    S: AsSocket,
{
    /// See the `From<&impl AsFd>` implementation.
    fn from(socket: &'s S) -> Self {
        let socket = socket.as_socket().as_raw_socket();
        assert!(socket != windows_sys::Win32::Networking::WinSock::INVALID_SOCKET as _);
        SockRef {
            socket: ManuallyDrop::new(unsafe { Socket::from_raw_socket(socket) }),
            _lifetime: PhantomData,
        }
    }
}

SockRef::from reconstructs a Socket from a raw fd/SOCKET, wraps it in ManuallyDrop so the close-on-drop doesn't fire, and ties its lifetime to &'s S via PhantomData. This is what lets Socket's API methods be invoked on an unowned descriptor (e.g. one held by std::net::TcpStream). The unsafe from_raw_fd/from_raw_socket calls are paired with assertions that the descriptor is non-negative / non-INVALID_SOCKET.

src/sys/unix.rs

src/sys/unix.rs, line 345-356

/// Helper macro to execute a system call that returns an `io::Result`.
macro_rules! syscall {
    ($fn: ident ( $($arg: expr),* $(,)* ) ) => {{
        #[allow(unused_unsafe)]
        let res = unsafe { libc::$fn($($arg, )*) };
        if res == -1 {
            Err(std::io::Error::last_os_error())
        } else {
            Ok(res)
        }
    }};
}

Central syscall! macro: wraps any libc::$fn(...) call in an unsafe block and translates the C convention (return value -1 = error, errno set) into a Rust io::Result. Used ~190 times across this file. Plus the per-call-site unsafe blocks for sockaddr casting and MaybeUninit::assume_init. Justifies uses-unsafe.

src/sys/unix.rs, line 929-931

pub(crate) type Socket = std::os::fd::OwnedFd;
pub(crate) type RawSocket = c_int;

Socket = std::os::fd::OwnedFd. The OwnedFd newtype owns the file descriptor and closes it on drop via libc::close. socket2 inherits this lifetime management instead of implementing it; the same applies to OwnedSocket on Windows (src/sys/windows.rs:291).

src/sys/windows.rs

src/sys/windows.rs, line 149-159

macro_rules! syscall {
    ($fn: ident ( $($arg: expr),* $(,)* ), $err_test: path, $err_value: expr) => {{
        #[allow(unused_unsafe)]
        let res = unsafe { windows_sys::Win32::Networking::WinSock::$fn($($arg, )*) };
        if $err_test(&res, &$err_value) {
            Err(io::Error::last_os_error())
        } else {
            Ok(res)
        }
    }};
}

Windows variant of the syscall! macro, generalised to take a fail predicate and sentinel (different WinSock APIs use different error sentinels — INVALID_SOCKET, SOCKET_ERROR, etc.). Same pattern of unsafe { winsock_api::$fn(...) } wrapped in error handling.

src/sys/windows.rs, line 280-289

fn init() {
    static INIT: Once = Once::new();

    INIT.call_once(|| {
        // Initialize winsock through the standard library by just creating a
        // dummy socket. Whether this is successful or not we drop the result as
        // libstd will be sure to have initialized winsock.
        let _ = net::UdpSocket::bind("127.0.0.1:34254");
    });
}

Winsock initialisation via std::sync::Once and a dummy std::net::UdpSocket::bind call, which triggers libstd's WSAStartup. Not a concurrency abstraction the crate implements; it borrows std's one-shot initializer and std's WinSock startup.