Integer underflow in ngc_send path-length math: sg_len<2 -> bcopy with SIZE_MAX -> kernel heap smash
Summary
ngc_send(:245-248): len=sap->sg_len-2 NO lower-bound on sg_len. ngc_bind validates sg_len(:822) but ngc_send does NOT. sg_len==0 -> len=-2 kmalloc((size_t)-1 M_WAITOK) panic/hang. sg_len==1 -> len=-1 kmalloc(0) succeeds bcopy(sg_data,path,(size_t)-1)(:247) multi-exabyte memcpy -> immediate page fault panic. bcopy length is size_t(unsigned) so signed negative silently reinterpreted as enormous. Requires control socket(privileged). Fix: if(sg_len<2) return EINVAL before :245.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0535 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df0535.c | trigger-source | attempts sg_len=1 control-socket send to provoke the underflow | 3.1 KB | view raw |
| build.sh | build-script | cc -O2 -o df0535 df0535.c | 85 B | view raw |
| run.sh | run-script | root invocation; expects EDOM | 202 B | view raw |
| run.log | run-log | sg_len sweep 0..4: 0/1 -> EDOM, no panic | 873 B | view raw |
| env.txt | environment | uname, cc | 402 B | view raw |
| VERDICT.md | verdict | false-positive analysis: getsockaddr guards sg_len>=2 | 3.2 KB | β raw |
| README.md | readme | evidence pack index | 1.3 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0535 β Integer underflow in ngc_send path-length math: FALSE POSITIVE
Verdict: FALSE POSITIVE (not reachable). No fix required.
Why
ngc_send computes len = sap->sg_len - 2. The finding claims sg_len of 0 or
1 underflows len to -2/-1, causing kmalloc((size_t)-1) / bcopy(...,(size_t)-1)
β panic.
The sockaddr never arrives at ngc_send with sg_len < 2. The syscall layer's
getsockaddr() (sys/kern/uipc_syscalls.c:1513) rejects len < offsetof(struct
sockaddr, sa_data[0]) (== 2) with EDOM at line 1521-1522 before the
protocol's pru_send runs, and line 1528 forces sa->sa_len = len so the user
cannot inject a smaller sg_len field. Hence sap->sg_len - 2 >= 0 always.
Empirical proof (pristine shipped module, root)
| sg_len | sendmsg result |
|---|---|
| 0 | errno=33 (EDOM β getsockaddr rejects) |
| 1 | errno=33 (EDOM β getsockaddr rejects) |
| 2 | success (len=0; no underflow) |
| 3, 4 | errno=2 (ENOENT β normal path lookup) |
No panic at the values the finding predicts. The guard is at
sys/kern/uipc_syscalls.c:1521-1522. No fix.diff authored (the underflow is
already prevented upstream).
See VERDICT.md for the full trace.
DF-0535 β Integer underflow in ngc_send path-length math: FALSE POSITIVE
Verdict: FALSE POSITIVE (not reachable). No code change needed.
CLAIM (from the finding)
ngc_send (sys/netgraph7/socket/ng_socket.c:245-248): len = sap->sg_len - 2; path = kmalloc(len + 1, M_NETGRAPH_PATH, M_WAITOK); bcopy(sap->sg_data, path, len); path[len] = '\0'; with NO lower bound on sg_len. sg_len==0 -> len=-2 -> kmalloc((size_t)-1) hang; sg_len==1 -> len=-1 -> kmalloc(0) ok, bcopy(..., (size_t)-1) -> multi-exabyte memcpy -> page-fault panic. (Same pattern in v4 at sys/netgraph/socket/ng_socket.c:241.)
WHY IT IS NOT REACHABLE β the upstream guard the reviewer missed
The sockaddr never reaches ngc_send with sg_len < 2. The syscall layer enforces a minimum sockaddr length in getsockaddr(), which runs BEFORE the protocol's pru_send:
sys/kern/uipc_syscalls.c:1513-1532 getsockaddr(): 1519: if (len > SOCK_MAXADDRLEN) 1520: return ENAMETOOLONG; 1521: if (len < offsetof(struct sockaddr, sa_data[0])) / == 2 / 1522: return EDOM; 1523: sa = kmalloc(len, M_SONAME, M_WAITOK); 1524: error = copyin(uaddr, sa, len); ... 1528: sa->sa_len = len; / <-- sa_len is FORCED to the syscall length /
offsetof(struct sockaddr, sa_data[0]) == 2 on DragonFly (sa_len u_char + sa_family
u_char = 2 bytes; verified on-guest). So the syscall rejects any user sockaddr
with total length < 2, and β critically β line 1528 OVERWRITES sa->sa_len with
that syscall length, so the user cannot inject a sg_len field smaller than the
allocation. In ngc_send, sap->sg_len (= sa_len) is therefore always >= 2, and
len = sap->sg_len - 2 is always >= 0. The signed underflow the finding
describes (len = -1 or -2) CANNOT occur via the syscall path.
EMPIRICAL CONFIRMATION (pristine shipped module, as root)
sg_len=0 -> sendmsg errno=33 (EDOM) [getsockaddr rejects: len < 2] sg_len=1 -> sendmsg errno=33 (EDOM) [getsockaddr rejects: len < 2] sg_len=2 -> sendmsg returns 1 [len=0, no underflow; empty path] sg_len=3 -> sendmsg errno=2 (ENOENT) [len=1; normal path lookup fails] sg_len=4 -> sendmsg errno=2 (ENOENT)
No panic at sg_len=0 or sg_len=1 (the values the finding claims panic); both are rejected with EDOM before ngc_send is entered. The kernel never survives long enough to be killed because it is never in danger.
CONCLUSION
The integer underflow in len = sap->sg_len - 2 is real as C arithmetic, but it
is guarded upstream by getsockaddr()'s minimum-sockaddr-length check
(uipc_syscalls.c:1521-1522) plus the sa_len-forcing at :1528. The bug is not
triggerable from userspace. A defense-in-depth if (sap->sg_len < 2) return EINVAL;
in ngc_send would be harmless but is redundant; no fix.diff is authored.
(For completeness: ngc_bind ALREADY validates sg_len at sys/netgraph7/socket/ng_socket.c:822 / v4 :718, and getsockaddr guards the syscall surface for every caller, so neither path can pass sg_len < 2.)
Fix verification
not_testablen/a
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
FALSE POSITIVE. getsockaddr enforces sg_len>=2 upstream (uipc_syscalls.c:1521). Underflow unreachable.
No comments yet.