β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0535

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0535 Β· 9 files
FileTypeDescriptionSize
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
README.md readme evidence pack index
↓ download 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.

VERDICT.md verdict false-positive analysis: getsockaddr guards sg_len>=2
↓ download raw

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_testable

n/a

see evidence pack
n/a

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.