Soundness: Out-of-bounds pointer arithmetic in `CMSG_NXTHDR` causes UB
还没有人认领这个 Issue。
评估
- 难度
- 3/5
- 预计耗时
- 1-2 天
- 新手友好度
- 68/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 冷清
- 技术栈
- rust
调研方向
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.
由索引模型根据 Issue 内容生成。
描述
[!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).
- 主要语言
- Rust
- 星标
- 71
- 派生
- 63
- PR 合并指标
- 30 天内没有已合并 PR
贡献指南
这个仓库没有索引到贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
sunfishcode/linux-raw-sys 的其他 Issue
-
难度 2/5 1-3 小时 新手友好度 67/100
sunfishcode/linux-raw-sys#194 ·
-
难度 2/5 1-3 小时 新手友好度 88/100
sunfishcode/linux-raw-sys#192 · 1 条评论 ·
-
难度 2/5 1-3 小时 新手友好度 72/100
sunfishcode/linux-raw-sys#188 ·
-
难度 3/5 1-2 天 新手友好度 45/100
sunfishcode/linux-raw-sys#179 ·
-
难度 3/5 1-2 天 新手友好度 50/100
sunfishcode/linux-raw-sys#173 · 17 条评论 ·
查看 sunfishcode/linux-raw-sys 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 84/100
Eynzof/Hermes-CN-Desktop#610 ·
-
bug
难度 2/5 1-3 小时 新手友好度 68/100
gitbutlerapp/gitbutler#15998 · 1 条评论 ·
-
bug triage:deciding
难度 1/5 1 小时以内 新手友好度 88/100
open-telemetry/otel-arrow#4132 ·
-
难度 2/5 1-3 小时 新手友好度 88/100
-
难度 2/5 1-3 小时 新手友好度 84/100