div_bind() writes sin_addr = INADDR_ANY at offset 4..8 of the user bind sockaddr before any length validation (OOB write lands in own-chunk slab slack)
Summary
div_bind() zeroes the caller-supplied sockaddr's sin_addr (4 bytes at offset 4..8) as soon as sa_family==AF_INET, before in_pcbbind() validates sa_len. With the 4-byte minimum bind sockaddr the write is 4 bytes past the requested size; the slab allocator rounds sub-128-byte requests to 8-byte multiples so it lands inside the chunk's own slack - no adjacent-object corruption, no security impact, but a language-level OOB write on a validated-less path (same family as DF-3440). Demonstrated: bind(4B AF_INET sockaddr) returns EINVAL from the later check (not EAFNOSUPPORT from the earlier family gate), proving the write branch executed. Root-only. Fix: check sa_len first.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3441 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| div3441.c | β | 1.7 KB | view raw | |
| README.md | β | 1.1 KB | β raw | |
| verdict.json | β | 1.7 KB | view raw | |
| manifest.json | β | 531 B | view raw |
DF-3441 β div_bind() writes into the user bind sockaddr before validation
div_bind() (sys/netinet/ip_divert.c:456-479) executes
((struct sockaddr_in *)nam)->sin_addr.s_addr = INADDR_ANY; at offset 4..8
of the caller-supplied sockaddr BEFORE any sa_len validation (in_pcbbind
checks sa_len != sizeof *sin only afterwards). With a 4-byte bind sockaddr
(getsockaddr minimum) the 4-byte zero write lands past the requested size β
inside the 8-byte slab chunk's slack (zoneindex: sub-128B allocations round
to 8-byte multiples, kern_slaballoc.c:638-645), so no adjacent-object
corruption occurs on this allocator. Correctness/UB bug, no demonstrated
security impact.
Build / run (root: divert socket)
cc -O -o div3441 div3441.c && ./div3441
Observed
bind(4B AF_INET sockaddr) = -1 errno=22 (Invalid argument)
EINVAL (not EAFNOSUPPORT) proves div_bind's else-branch β the write line β executed, with in_pcbbind rejecting the length afterwards. Note a fixed div_bind (early sa_len check) returns the same EINVAL, so this marker proves path execution on the current kernel, not a behavioral difference.
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
['findings/poc/DF-3441/README.md β observed errno=22 (EINVAL) for bind(4B AF_INET sockaddr)']
PoC changes
none (first-shot PoC ran clean)
Verified recommended fix
Validate nam->sa_len == sizeof(struct sockaddr_in) (and family) at the top of div_bind before zeroing sin_addr.
Verdict
div_bind()'s unvalidated 4-byte zero write at offset 4..8 of a 4-byte user bind sockaddr provably executes on the guest (bind returns EINVAL from in_pcbbind's later sa_len check, not EAFNOSUPPORT from div_bind's family gate β the write line's branch ran). The write lands inside the 8-byte slab chunk's own slack (DF slab allocator rounds sub-128B requests to 8-byte multiples), so there is no adjacent-object corruption and no security impact; this is a correctness/UB hardening defect. A fixed kernel returns the same EINVAL, so the marker demonstrates path execution, not a behavioral difference.
No comments yet.