Soundness: Out-of-bounds pointer arithmetic in `CMSG_NXTHDR` causes UB
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 68/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Tranquilo
- Stack tecnológico
- rust
- Área
- operating-systems
Línea de trabajo
Start in src/lib.rs at cmsg_macros::CMSG_NXTHDR, especially lines 141-161, and run the minimal reproduction under Miri to observe the reported undefined behavior. Done means the terminating control-message case no longer performs out-of-bounds pointer arithmetic and the reproduction passes without a Miri error.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
[!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 function CMSG_NXTHDR performs out-of-bounds pointer arithmetic when iterating over socket control message headers in a control buffer: https://github.com/sunfishcode/linux-raw-sys/blob/0e2918cf3e366d9c923d4ca05f169b49d826db56/src/lib.rs#L141-L161
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;
fn main() {
// Allocate a buffer representing socket control message buffer (`msg_control`).
// We allocate exactly enough space for a single `cmsghdr` without trailing padding.
let mut 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 *mut cmsghdr;
unsafe {
(*hdr_ptr).cmsg_len = size_of::<cmsghdr>();
(*hdr_ptr).cmsg_level = 0;
(*hdr_ptr).cmsg_type = 0;
}
let mut 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) 🔴 🚨
-
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:let cmsg_len = (*cmsg).cmsg_len; let next_cmsg = (cmsg as *mut u8).add(CMSG_ALIGN(cmsg_len as _) as usize) as *mut cmsghdr;It then validates whether
next_cmsgfits within the control buffer (max = msg_control + msg_controllen) by performing pointer arithmetic onnext_cmsg:if next_cmsg.add(1) as usize > max || next_cmsg as usize + CMSG_ALIGN((*next_cmsg).cmsg_len as _) as usize > max { return ptr::null_mut(); } -
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:
// 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
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) 🟡 🤦
- Severity: 🟡 Low
- Threat Vector: 🤦 Accidental Misuse
- Bug Type: Invalid Transmute
- Location:
src/lib.rs:208-214(signal_macros::sig_ign) - Description: To represent C's
SIG_IGNmacro (((__sighandler_t) 1)),sig_ign()transmutes the literal integer1into anOption<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 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.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.)
1. src/lib.rs:116 (CMSG_ALIGN) 🔴
-
Missing Documentation:
pub const unsafe fn CMSG_ALIGN(len: c_uint) -> c_uint -
Proposed Documentation:
/// # 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_DATAand 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. pub const unsafe fn CMSG_DATA(cmsg: *const cmsghdr) -> *mut c_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 *mut c_uchar).add(size_of::<cmsghdr>()) }
3. src/lib.rs:125-127 (CMSG_SPACE) 🔴
-
Missing Documentation & Comment:
pub const unsafe fn CMSG_SPACEand call toCMSG_ALIGN. -
Proposed Proof:
/// # Safety /// This function performs pure integer arithmetic with no memory safety preconditions. pub const unsafe fn CMSG_SPACE(len: c_uint) -> c_uint { // SAFETY: `CMSG_ALIGN` performs pure arithmetic with no safety preconditions. size_of::<cmsghdr>() as c_uint + CMSG_ALIGN(len) }
4. src/lib.rs:129-131 (CMSG_LEN) 🔴
-
Missing Documentation:
pub const unsafe fn CMSG_LEN -
Proposed Documentation:
/// # 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_FIRSTHDRand raw pointer dereference*mhdr. -
Proposed Proof:
/// # Safety /// `mhdr` must be a valid, aligned pointer valid for reads of `msghdr`. pub const unsafe fn CMSG_FIRSTHDR(mhdr: *const msghdr) -> *mut cmsghdr { // 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. pub unsafe fn CMSG_NXTHDR(mhdr: *const msghdr, cmsg: *const cmsghdr) -> *mut cmsghdr { // 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 *mut u8).add(CMSG_ALIGN(cmsg_len as _) as usize) as *mut cmsghdr; // SAFETY: By caller contract, `mhdr` is valid for reads of `msghdr`. let max = ((*mhdr).msg_control as usize) + ((*mhdr).msg_controllen as usize);
7. src/lib.rs:170-175 (FD_CLR) 🔴
-
Missing Documentation & Comment:
pub unsafe fn FD_CLRand 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. pub unsafe fn FD_CLR(fd: c_int, set: *mut __kernel_fd_set) { let bytes = set as *mut u8; 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) as usize) &= !(1 << (fd % 8)); } }
8. src/lib.rs:177-182 (FD_SET) 🔴
- Missing Documentation & Comment:
pub unsafe fn FD_SETand 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_ISSETand 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_ZEROand call toptr::write_bytes. -
Proposed Proof:
/// # Safety /// `set` must be a valid, aligned pointer valid for writes of `size_of::<__kernel_fd_set>()` bytes. pub unsafe fn FD_ZERO(set: *mut __kernel_fd_set) { let bytes = set as *mut u8; // 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).
- Lenguaje dominante
- Rust
- Estrellas
- 71
- Forks
- 63
- Métricas de merge de PR
- Sin PR fusionados en 30 d
Guía de contribución
No hay ninguna guía de contribución indexada para este repositorio
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de sunfishcode/linux-raw-sys
-
add OPENAT2_REGULAR Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 67/100
sunfishcode/linux-raw-sys#194 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
sunfishcode/linux-raw-sys#192 · 1 comentario ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
sunfishcode/linux-raw-sys#188 ·
-
Dificultad 3/5 1-2 días Aptitud para principiantes 45/100
sunfishcode/linux-raw-sys#179 ·
-
Dificultad 3/5 1-2 días Aptitud para principiantes 50/100
sunfishcode/linux-raw-sys#173 · 17 comentarios ·
Todos los issues de sunfishcode/linux-raw-sys
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
Eynzof/Hermes-CN-Desktop#610 ·
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
gitbutlerapp/gitbutler#15998 · 1 comentario ·
-
bug triage:deciding
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
open-telemetry/otel-arrow#4132 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100