You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This finding was identified during an agentic unsafe Rust code review performed by Gemini AI, followed by human review and verification.
The Issue
In src/lib.rs, the public unsafe function CMSG_NXTHDR performs out-of-bounds pointer arithmetic when iterating over socket control message headers in a control buffer:
// We convert from raw pointers to usize here, which may not be sound in a
// future version of Rust. Once the provenance rules are set in stone,
// it will be a good idea to give this function a once-over.
let cmsg_len = (*cmsg).cmsg_len;
let next_cmsg = (cmsg as*mutu8).add(CMSG_ALIGN(cmsg_len as_)asusize)as*mutcmsghdr;
let max = ((*mhdr).msg_controlasusize) + ((*mhdr).msg_controllenasusize);
if cmsg_len < size_of::<cmsghdr>()as_{
return ptr::null_mut();
}
if next_cmsg.add(1)asusize > max
|| next_cmsg asusize + CMSG_ALIGN((*next_cmsg).cmsg_lenas_)asusize > max
{
return ptr::null_mut();
}
next_cmsg
}
Specifically, CMSG_NXTHDR computes a candidate pointer next_cmsg for the upcoming control header. It then validates whether this candidate header fits within the remaining control buffer (max) by performing pointer arithmetic on next_cmsg.
Under Rust pointer semantics for pointer::add(count), both the starting pointer and the resulting pointer must be either within bounds or at most one byte past the end of the same allocated object.
When iterating control messages in a received packet where the final cmsghdr ends exactly at the end of the allocated buffer (msg_control + msg_controllen), next_cmsg points exactly one byte past the end of the allocated object. On this terminating loop iteration, calling next_cmsg.add(1) advances this one-past-the-end pointer by size_of::<cmsghdr>() bytes (16 bytes on 64-bit platforms). Offsetting a pointer beyond one byte past the end of its underlying allocation violates the core validity conditions of pointer::add and triggers immediate Undefined Behavior.
Minimal Reproduction (Miri)
use linux_raw_sys::cmsg_macros::{CMSG_FIRSTHDR,CMSG_NXTHDR};use linux_raw_sys::net::{cmsghdr, msghdr};use core::mem::size_of;fnmain(){// Allocate a buffer representing socket control message buffer (`msg_control`).// We allocate exactly enough space for a single `cmsghdr` without trailing padding.letmut control_buf = [0u64;2];// 16 bytes on 64-bit platforms, 8-byte aligned// Initialize the `cmsghdr` at the start of the control buffer.// The length is set to exactly `size_of::<cmsghdr>()` (16 bytes).let hdr_ptr = control_buf.as_mut_ptr()as*mutcmsghdr;unsafe{(*hdr_ptr).cmsg_len = size_of::<cmsghdr>();(*hdr_ptr).cmsg_level = 0;(*hdr_ptr).cmsg_type = 0;}letmut mhdr:msghdr = unsafe{ core::mem::zeroed()};
mhdr.msg_control = control_buf.as_mut_ptr()as*mut core::ffi::c_void;
mhdr.msg_controllen = size_of::<cmsghdr>();unsafe{// CMSG_FIRSTHDR returns a pointer to the first header.let first = CMSG_FIRSTHDR(&mhdr);assert!(!first.is_null());// CMSG_NXTHDR attempts to find the next header in the buffer.// Because the first header ends exactly at the buffer boundary, `next_cmsg`// points exactly 1 byte past the end of the `control_buf` allocation.// CMSG_NXTHDR then executes `next_cmsg.add(1)`, which advances a one-past-the-end// pointer by 16 bytes, triggering immediate out-of-bounds Undefined Behavior.let _next = CMSG_NXTHDR(&mhdr, first);}}
error: Undefined Behavior: in-bounds pointer arithmetic failed: attempting to offset pointer by 16 bytes, but got alloc108+0x10 which is at or beyond the end of the allocation of size 16 bytes
--> /usr/local/google/home/manishearth/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/linux-raw-sys-0.12.1/src/lib.rs:154:12
|
154 | if next_cmsg.add(1) as usize > max
| ^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
help: alloc108 was allocated here:
--> src/bin/repro1.rs:8:9
|
8 | let mut control_buf = [0u64; 2]; // 16 bytes on 64-bit platforms, 8...
| ^^^^^^^^^^^^^^^
= note: stack backtrace:
0: linux_raw_sys::cmsg_macros::CMSG_NXTHDR
at /usr/local/google/home/manishearth/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/linux-raw-sys-0.12.1/src/lib.rs:154:12: 154:28
1: main
at src/bin/repro1.rs:33:21: 33:46
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
Note
The full audit report below also contains additional minor findings (such as missing safety comments or undocumented FFI assumptions) that are probably worth fixing as well but not the primary goal of this issue. The audit report has not been human-reviewed, it may contain misleading claims.
Full Gemini Codebase Audit Report Appendix
Unsafe Rust Review: linux_raw_sys (v0_12)
Overall Safety Assessment
linux_raw_sys provides generated FFI bindings and raw declarations for the Linux userspace API (UAPI), including kernel structs, unions, constants, and syscall numbers. The vast majority of the crate consists of offline bindgen-generated definitions separated by architecture and header feature modules (general, errno, net, ioctl, etc.).
In addition to the generated bindings, src/lib.rs contains human-authored macro helper modules (cmsg_macros, select_macros, signal_macros) that provide low-level manipulation of socket control messages, fd_set bitsets, and signal handler constants.
Audit of the human-authored codebase revealed a critical soundness vulnerability in src/lib.rs (cmsg_macros::CMSG_NXTHDR). The macro performs out-of-bounds pointer arithmetic (next_cmsg.add(1)) on *mut cmsghdr pointers when checking whether upcoming control headers fit within the socket control buffer. On terminating loop iterations where a control header ends exactly at the buffer boundary, this arithmetic offsets a pointer beyond one byte past the end of its allocated object, violating LLVM getelementptr inbounds rules and triggering immediate Undefined Behavior.
Furthermore, all human-authored pub unsafe fn declarations and internal unsafe calls in src/lib.rs completely lack # Safety documentation comments and // SAFETY: proof obligations.
Critical Findings
Out-of-Bounds inbounds Pointer Arithmetic in CMSG_NXTHDR (src/lib.rs:141-161) 🔴 🚨
Description:CMSG_NXTHDR advances a socket control message pointer (cmsg: *const cmsghdr) to the next header in a control buffer (mhdr: *const msghdr). It computes the candidate next header pointer next_cmsg via byte offset:
let cmsg_len = (*cmsg).cmsg_len;let next_cmsg = (cmsg as*mutu8).add(CMSG_ALIGN(cmsg_len as_)asusize)as*mutcmsghdr;
It then validates whether next_cmsg fits within the control buffer (max = msg_control + msg_controllen) by performing pointer arithmetic on next_cmsg:
if next_cmsg.add(1)asusize > max
|| next_cmsg asusize + CMSG_ALIGN((*next_cmsg).cmsg_lenas_)asusize > max
{return ptr::null_mut();}
Soundness Violation: Under authoritative Rust pointer semantics and standard library contracts for pointer::add(count) (backed by LLVM getelementptr inbounds instructions), both the starting pointer and the resulting pointer must be either in bounds or at most one byte past the end of the same allocated object.
In standard networking code, CMSG_NXTHDR is called repeatedly in a loop until it returns null. When the final cmsghdr in a received packet ends exactly at the end of the allocated control buffer (msg_control + msg_controllen), next_cmsg points exactly one byte past the end of the msg_control allocated object. On this final loop check, calling next_cmsg.add(1) offsets this one-past-the-end pointer by size_of::<cmsghdr>() bytes (16 bytes on 64-bit platforms). Offsetting a pointer beyond one byte past the end of its underlying allocation violates the validity conditions of pointer::add and triggers immediate Undefined Behavior.
Remediation: Replace pointer arithmetic next_cmsg.add(1) as usize with pure integer arithmetic (next_cmsg as usize) + size_of::<cmsghdr>() > max (or .wrapping_add(1) / byte slice calculations) to prevent generating out-of-bounds inbounds GEP instructions.
Fishy Findings
1. Raw Pointer Address Casts for Strict Bounds Checks in CMSG_NXTHDR (src/lib.rs:142-148) 🟡 🤦
Description: The author includes an explicit inline comment acknowledging uncertainty surrounding pointer-to-integer casts:
// We convert from raw pointers to usize here, which may not be sound in a// future version of Rust. Once the provenance rules are set in stone,// it will be a good idea to give this function a once-over.
Analysis: While casting pointers to usize via as and comparing integer addresses via > is valid under Rust's current operational semantics (expose_provenance / ptr::addr()), relying on raw integer comparisons across distinct pointer allocations rather than safe slice APIs or pointer offset methods is fragile under strict pointer provenance models.
2. Constructing Dangling Function Pointers via Transmute in sig_ign (src/lib.rs:208-214) 🟡 🤦
Description: To represent C's SIG_IGN macro (((__sighandler_t) 1)), sig_ign() transmutes the literal integer 1 into an Option<unsafe extern "C" fn(c_int)>.
Analysis: Under Rust's validity invariants for function pointers, function pointer types must be non-null. Because Option<fn()> uses niche optimization for None (0), any non-null integer address (such as 0x1) is a structurally valid bit pattern. However, constructing a fake dangling function pointer to an unmapped address relies entirely on OS kernel syscall conventions intercepting the literal value 1 during signal or sigaction registration. While sound in this FFI context, it represents an unusual boundary pattern.
Missing Safety Comments
The human-authored helper modules in src/lib.rs lack safety documentation (/// # Safety) on public unsafe fn items and internal // SAFETY: proof comments on unsafe operations.
(Note: We do not flag auto-generated unsafe blocks in bindgen architecture modules.)
/// # Safety/// This function performs pure integer arithmetic and has no memory safety preconditions./// It is marked `unsafe` solely for FFI macro parity with C headers.
2. src/lib.rs:121-123 (CMSG_DATA) 🔴
Missing Documentation & Comment:pub const unsafe fn CMSG_DATA and raw pointer .add(...).
Proposed Proof:
/// # Safety/// `cmsg` must be a valid pointer to an allocated `cmsghdr` object containing at least/// `size_of::<cmsghdr>()` initialized bytes.pubconstunsafefnCMSG_DATA(cmsg:*constcmsghdr) -> *mutc_uchar{// SAFETY:// By caller contract, `cmsg` points to an allocated object of at least `size_of::<cmsghdr>()`// bytes. Offsetting by `size_of::<cmsghdr>()` remains within or exactly one byte past the end// of the allocated object and does not overflow `isize`.(cmsg as*mutc_uchar).add(size_of::<cmsghdr>())}
3. src/lib.rs:125-127 (CMSG_SPACE) 🔴
Missing Documentation & Comment:pub const unsafe fn CMSG_SPACE and call to CMSG_ALIGN.
Proposed Proof:
/// # Safety/// This function performs pure integer arithmetic with no memory safety preconditions.pubconstunsafefnCMSG_SPACE(len:c_uint) -> c_uint{// SAFETY: `CMSG_ALIGN` performs pure arithmetic with no safety preconditions.size_of::<cmsghdr>()asc_uint + CMSG_ALIGN(len)}
/// # Safety/// This function performs pure integer arithmetic with no memory safety preconditions.
5. src/lib.rs:133-139 (CMSG_FIRSTHDR) 🔴
Missing Documentation & Comment:pub const unsafe fn CMSG_FIRSTHDR and raw pointer dereference *mhdr.
Proposed Proof:
/// # Safety/// `mhdr` must be a valid, aligned pointer valid for reads of `msghdr`.pubconstunsafefnCMSG_FIRSTHDR(mhdr:*constmsghdr) -> *mutcmsghdr{// SAFETY: By caller contract, `mhdr` is non-null, aligned, and valid for reads of `msghdr`.if(*mhdr).msg_controllen < size_of::<cmsghdr>()as_{
6. src/lib.rs:141-161 (CMSG_NXTHDR) 🔴
Missing Documentation & Comments:pub unsafe fn CMSG_NXTHDR, raw pointer dereferences (*cmsg, *mhdr, *next_cmsg), and pointer .add(...) calls.
Proposed Proof:
/// # Safety/// `mhdr` must be a valid pointer to a readable `msghdr` whose `msg_control` buffer is valid/// for `msg_controllen` bytes. `cmsg` must point to a valid `cmsghdr` within that buffer.pubunsafefnCMSG_NXTHDR(mhdr:*constmsghdr,cmsg:*constcmsghdr) -> *mutcmsghdr{// SAFETY: By caller contract, `cmsg` is valid for reads of `cmsghdr`.let cmsg_len = (*cmsg).cmsg_len;// SAFETY: `cmsg` points into `msg_control`. Assuming `cmsg_len` is uncorrupted, the offset// remains within the allocated control buffer object.let next_cmsg = (cmsg as*mutu8).add(CMSG_ALIGN(cmsg_len as_)asusize)as*mutcmsghdr;// SAFETY: By caller contract, `mhdr` is valid for reads of `msghdr`.let max = ((*mhdr).msg_controlasusize) + ((*mhdr).msg_controllenasusize);
7. src/lib.rs:170-175 (FD_CLR) 🔴
Missing Documentation & Comment:pub unsafe fn FD_CLR and raw pointer arithmetic/dereference.
Proposed Proof:
/// # Safety/// `fd` must satisfy `0 <= fd < FD_SETSIZE` (typically 1024), and `set` must be a valid,/// aligned pointer to a mutable `__kernel_fd_set` allocation.pubunsafefnFD_CLR(fd:c_int,set:*mut__kernel_fd_set){let bytes = set as*mutu8;if fd >= 0{// SAFETY:// By caller contract, `fd < 1024`, so byte index `fd / 8 < 128`. `set` points to an// allocated `__kernel_fd_set` (128 bytes). Offsetting and mutating within these bounds// is valid and aligned for `u8`.*bytes.add((fd / 8)asusize) &= !(1 << (fd % 8));}}
8. src/lib.rs:177-182 (FD_SET) 🔴
Missing Documentation & Comment:pub unsafe fn FD_SET and raw pointer arithmetic/dereference.
Proposed Proof: (Identical safety contract and proof obligations as FD_CLR).
9. src/lib.rs:184-191 (FD_ISSET) 🔴
Missing Documentation & Comment:pub unsafe fn FD_ISSET and raw pointer arithmetic/dereference.
Proposed Proof: (Identical safety contract and proof obligations as FD_CLR).
10. src/lib.rs:193-196 (FD_ZERO) 🔴
Missing Documentation & Comment:pub unsafe fn FD_ZERO and call to ptr::write_bytes.
Proposed Proof:
/// # Safety/// `set` must be a valid, aligned pointer valid for writes of `size_of::<__kernel_fd_set>()` bytes.pubunsafefnFD_ZERO(set:*mut__kernel_fd_set){let bytes = set as*mutu8;// SAFETY: By caller contract, `bytes` is valid for writes of `size_of::<__kernel_fd_set>()` bytes.
core::ptr::write_bytes(bytes,0,size_of::<__kernel_fd_set>());}
11. src/lib.rs:209-213 (sig_ign) 🟡
Improper Comment Formatting:sig_ign() contains an informal // Safety: comment.
Proposed Proof:
// SAFETY:// Constructing an arbitrary non-null pointer address (`0x1`) via `transmute` satisfies the// non-null validity invariant of function pointers (`Option<fn()>` uses null pointer optimization).
Note
This finding was identified during an agentic unsafe Rust code review performed by Gemini AI, followed by human review and verification.
The Issue
In
src/lib.rs, the public unsafe functionCMSG_NXTHDRperforms out-of-bounds pointer arithmetic when iterating over socket control message headers in a control buffer:linux-raw-sys/src/lib.rs
Lines 141 to 161 in 0e2918c
Specifically,
CMSG_NXTHDRcomputes a candidate pointernext_cmsgfor the upcoming control header. It then validates whether this candidate header fits within the remaining control buffer (max) by performing pointer arithmetic onnext_cmsg.Under Rust pointer semantics for
pointer::add(count), both the starting pointer and the resulting pointer must be either within bounds or at most one byte past the end of the same allocated object.When iterating control messages in a received packet where the final
cmsghdrends exactly at the end of the allocated buffer (msg_control + msg_controllen),next_cmsgpoints exactly one byte past the end of the allocated object. On this terminating loop iteration, callingnext_cmsg.add(1)advances this one-past-the-end pointer bysize_of::<cmsghdr>()bytes (16 bytes on 64-bit platforms). Offsetting a pointer beyond one byte past the end of its underlying allocation violates the core validity conditions ofpointer::addand triggers immediate Undefined Behavior.Minimal Reproduction (Miri)
Note
The full audit report below also contains additional minor findings (such as missing safety comments or undocumented FFI assumptions) that are probably worth fixing as well but not the primary goal of this issue. The audit report has not been human-reviewed, it may contain misleading claims.
Full Gemini Codebase Audit Report Appendix
Unsafe Rust Review:
linux_raw_sys(v0_12)Overall Safety Assessment
linux_raw_sysprovides generated FFI bindings and raw declarations for the Linux userspace API (UAPI), including kernel structs, unions, constants, and syscall numbers. The vast majority of the crate consists of offlinebindgen-generated definitions separated by architecture and header feature modules (general,errno,net,ioctl, etc.).In addition to the generated bindings,
src/lib.rscontains human-authored macro helper modules (cmsg_macros,select_macros,signal_macros) that provide low-level manipulation of socket control messages,fd_setbitsets, and signal handler constants.Audit of the human-authored codebase revealed a critical soundness vulnerability in
src/lib.rs(cmsg_macros::CMSG_NXTHDR). The macro performs out-of-bounds pointer arithmetic (next_cmsg.add(1)) on*mut cmsghdrpointers when checking whether upcoming control headers fit within the socket control buffer. On terminating loop iterations where a control header ends exactly at the buffer boundary, this arithmetic offsets a pointer beyond one byte past the end of its allocated object, violating LLVMgetelementptr inboundsrules and triggering immediate Undefined Behavior.Furthermore, all human-authored
pub unsafe fndeclarations and internalunsafecalls insrc/lib.rscompletely lack# Safetydocumentation comments and// SAFETY:proof obligations.Critical Findings
Out-of-Bounds
inboundsPointer Arithmetic inCMSG_NXTHDR(src/lib.rs:141-161) 🔴 🚨Severity: 🔴 High
Threat Vector: 🚨 Untrusted Input
Bug Type: Out-of-Bounds Pointer Arithmetic
Location:
src/lib.rs:141-161(cmsg_macros::CMSG_NXTHDR)Description:
CMSG_NXTHDRadvances a socket control message pointer (cmsg: *const cmsghdr) to the next header in a control buffer (mhdr: *const msghdr). It computes the candidate next header pointernext_cmsgvia byte offset:It then validates whether
next_cmsgfits within the control buffer (max = msg_control + msg_controllen) by performing pointer arithmetic onnext_cmsg:Soundness Violation: Under authoritative Rust pointer semantics and standard library contracts for
pointer::add(count)(backed by LLVMgetelementptr inboundsinstructions), both the starting pointer and the resulting pointer must be either in bounds or at most one byte past the end of the same allocated object.In standard networking code,
CMSG_NXTHDRis called repeatedly in a loop until it returns null. When the finalcmsghdrin a received packet ends exactly at the end of the allocated control buffer (msg_control + msg_controllen),next_cmsgpoints exactly one byte past the end of themsg_controlallocated object. On this final loop check, callingnext_cmsg.add(1)offsets this one-past-the-end pointer bysize_of::<cmsghdr>()bytes (16 bytes on 64-bit platforms). Offsetting a pointer beyond one byte past the end of its underlying allocation violates the validity conditions ofpointer::addand triggers immediate Undefined Behavior.Remediation: Replace pointer arithmetic
next_cmsg.add(1) as usizewith pure integer arithmetic(next_cmsg as usize) + size_of::<cmsghdr>() > max(or.wrapping_add(1)/ byte slice calculations) to prevent generating out-of-boundsinbounds GEPinstructions.Fishy Findings
1. Raw Pointer Address Casts for Strict Bounds Checks in
CMSG_NXTHDR(src/lib.rs:142-148) 🟡 🤦Severity: 🟡 Low
Threat Vector: 🤦 Accidental Misuse
Bug Type: Pointer Provenance
Location:
src/lib.rs:142-148(cmsg_macros::CMSG_NXTHDR)Description: The author includes an explicit inline comment acknowledging uncertainty surrounding pointer-to-integer casts:
Analysis: While casting pointers to
usizeviaasand comparing integer addresses via>is valid under Rust's current operational semantics (expose_provenance/ptr::addr()), relying on raw integer comparisons across distinct pointer allocations rather than safe slice APIs or pointer offset methods is fragile under strict pointer provenance models.2. Constructing Dangling Function Pointers via Transmute in
sig_ign(src/lib.rs:208-214) 🟡 🤦src/lib.rs:208-214(signal_macros::sig_ign)SIG_IGNmacro (((__sighandler_t) 1)),sig_ign()transmutes the literal integer1into anOption<unsafe extern "C" fn(c_int)>.Option<fn()>uses niche optimization forNone(0), any non-null integer address (such as0x1) is a structurally valid bit pattern. However, constructing a fake dangling function pointer to an unmapped address relies entirely on OS kernel syscall conventions intercepting the literal value1duringsignalorsigactionregistration. While sound in this FFI context, it represents an unusual boundary pattern.Missing Safety Comments
The human-authored helper modules in
src/lib.rslack safety documentation (/// # Safety) on publicunsafe fnitems and internal// SAFETY:proof comments on unsafe operations.(Note: We do not flag auto-generated
unsafeblocks inbindgenarchitecture modules.)1.
src/lib.rs:116(CMSG_ALIGN) 🔴Missing Documentation:
pub const unsafe fn CMSG_ALIGN(len: c_uint) -> c_uintProposed Documentation:
2.
src/lib.rs:121-123(CMSG_DATA) 🔴Missing Documentation & Comment:
pub const unsafe fn CMSG_DATAand raw pointer.add(...).Proposed Proof:
3.
src/lib.rs:125-127(CMSG_SPACE) 🔴Missing Documentation & Comment:
pub const unsafe fn CMSG_SPACEand call toCMSG_ALIGN.Proposed Proof:
4.
src/lib.rs:129-131(CMSG_LEN) 🔴Missing Documentation:
pub const unsafe fn CMSG_LENProposed Documentation:
5.
src/lib.rs:133-139(CMSG_FIRSTHDR) 🔴Missing Documentation & Comment:
pub const unsafe fn CMSG_FIRSTHDRand raw pointer dereference*mhdr.Proposed Proof:
6.
src/lib.rs:141-161(CMSG_NXTHDR) 🔴Missing Documentation & Comments:
pub unsafe fn CMSG_NXTHDR, raw pointer dereferences (*cmsg,*mhdr,*next_cmsg), and pointer.add(...)calls.Proposed Proof:
7.
src/lib.rs:170-175(FD_CLR) 🔴Missing Documentation & Comment:
pub unsafe fn FD_CLRand raw pointer arithmetic/dereference.Proposed Proof:
8.
src/lib.rs:177-182(FD_SET) 🔴pub unsafe fn FD_SETand raw pointer arithmetic/dereference.FD_CLR).9.
src/lib.rs:184-191(FD_ISSET) 🔴pub unsafe fn FD_ISSETand raw pointer arithmetic/dereference.FD_CLR).10.
src/lib.rs:193-196(FD_ZERO) 🔴Missing Documentation & Comment:
pub unsafe fn FD_ZEROand call toptr::write_bytes.Proposed Proof:
11.
src/lib.rs:209-213(sig_ign) 🟡Improper Comment Formatting:
sig_ign()contains an informal// Safety:comment.Proposed Proof: