sockaddr address fields read before sa_len validation in tcp_usr_bind/connect/preconnect and tcp6_usr_bind/connect (DF-3357 family): heap read of recycled/uninitialized bytes past the user-copied length (v6: up to 16 bytes past the allocation)
Summary
getsockaddr kmallocs exactly namlen (>=2) bytes and copies only that many; tcp_usr_bind (:316-321), tcp_usr_connect (:597-602) and tcp_usr_preconnect (:965-968) then read sin_addr (offsets 4..7) and the v6 twins read sin6_addr (offsets 8..23) plus pass the short buffer to prison_remote_ip - all BEFORE the sa_len gates in in_pcbbind/in_pcbladdr_find/in6_pcbbind/in6_pcbladdr. For namelen 2..4 (v4) the multicast decision consumes fully uncopied bytes; for namlen 8 (v6) sin6_addr is read 16 bytes past the 8-byte slab chunk into neighboring chunks. VERIFIED the read-before-validate exists live (5-byte sockaddr with byte4=224 returns EAFNOSUPPORT, proving the handler consumes the full sin_addr despite sa_len<16) but ceiling is a 1-bit error-code oracle - no data leaves the kernel; on the stock slab allocator the recycled-byte oracle could not be flipped (~600k groomed attempts: cross-CPU frees go to z_RChunks and the v6 neighbor byte is structurally sa_len=8) - honest not_reproduced/impact-none. Fix: validate sa_len >= sizeof(sockaddr_in/in6) before any field read at the five sites (diff in pack, inspection-correct).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3372 Β· 16 files| File | Type | Description | Size | |
|---|---|---|---|---|
| oob_oracle.c | β | 3.3 KB | view raw | |
| probe.c | β | 2.2 KB | view raw | |
| probe_affinity.c | β | 1.0 KB | view raw | |
| probe_hammer.c | β | 945 B | view raw | |
| probe_fleet.c | β | 1017 B | view raw | |
| run.log | β | 1.2 KB | view raw | |
| probe.log | β | 2.4 KB | view raw | |
| probe2.log | β | 1.0 KB | view raw | |
| probe_matrix.log | β | 1.0 KB | view raw | |
| probe3.log | β | 73 B | view raw | |
| VERDICT.md | β | 3.4 KB | β raw | |
| README.md | β | 3.0 KB | β raw | |
| run.sh | β | 93 B | view raw | |
| fix.diff | β | 1.2 KB | view raw | |
| verdict.json | β | 3.5 KB | view raw | |
| manifest.json | β | 1.4 KB | view raw |
DF-3372 β sockaddr fields read before sa_len validation in TCP bind/connect
What
tcp_usr_bind (sys/netinet/tcp_usrreq.c:316-321), tcp_usr_connect
(:597-602), tcp_usr_preconnect (:965-968), tcp6_usr_bind (:406-411) and
tcp6_usr_connect (:640-656, incl. the prison_remote_ip call) cast the
user-supplied sockaddr to sockaddr_in/sockaddr_in6 and read
sin_addr (offsets 4..7) / sin6_addr (offsets 8..23) before any length
validation. The deep validators (in_pcbbind in_pcb.c:648,
in_pcbladdr_find in_pcb.c:954, in6_pcbbind in6_pcb.c:129,
in6_pcbladdr in6_pcb.c:282) all check sa_len first β but they run
after these pre-checks.
getsockaddr() (sys/kern/uipc_syscalls.c:1513-1532) kmallocs exactly
namelen bytes (2..255) and copies only that many user bytes, so:
- v4,
namelen2..4:sin_addris read entirely beyond the copied data (recycled, uninitialized 8-byte slab chunk contents;zoneindex()maps sizes 2..8 to the 8-byte class). - v6,
namelen2..23:sin6_addr(offsets 8..23) is read up to 16 bytes past an 8-byte allocation (cross-object read into neighboring slab chunks);prison_remote_ip(kern_jail.c:469-472) reads the same bytes with no length check of its own.
Same defect family as DF-3357 (udp_usrreq.c), where the UDP code used
the OOB address as a packet destination. In TCP the bytes only steer the
multicast rejection (EAFNOSUPPORT vs the later EINVAL from the length
check) β a 1-bit oracle.
PoC
oob_oracle.cβ grooms the M_SONAME 8-byte slab class with a controlledsin_addrvia UDP bind(2) (which allocates+frees the same chunk), then binds/connects a TCP socket with a 2-byte sockaddr. Success criterion: errno flips 22 (EINVAL) β 47 (EAFNOSUPPORT) when the groom address is multicast.- Probes:
probe.c-series (probe*.log) sweep groom vectors (bind/sendto, 8/16-byte, 2..256 reps, 48-process fleet).
Result (honest)
The read is certain from the code path and the multicast decision is
live (a 5-byte sockaddr whose copied byte 4 = 224 deterministically
returns EAFNOSUPPORT; = 8 returns EINVAL β proving the handler consumes
sin_addr regardless of sa_len < 16). However the recycled-byte oracle
could not be flipped on the stock kernel across ~600k groomed attempts:
DF's slab allocator frees cross-CPU chunks to the owning CPU's z_RChunks
(kern_slaballoc.c:1491-1547), which a userland-thread allocation never
immediately reuses, and the v6 cross-object neighbor byte is structurally
the next chunk's sa_len (=8, never 0xff). So: defect real, observable
impact on this system: none demonstrated β verdict not_reproduced
(reproduced=0, impact=none).
Build / run
cc -O2 -include unistd.h -o oob_oracle oob_oracle.c ./oob_oracle
Fix sketch
Validate length (and family) before touching address fields in each of the
five sites, e.g. in tcp_usr_bind/tcp_usr_connect/tcp_usr_preconnect:
if (nam->sa_len < sizeof(struct sockaddr_in))
return EINVAL; /* or goto out; as appropriate */
DF-3372 VERDICT β sockaddr read-before-validate in TCP bind/connect paths
Reproduced?
No observable manifestation β not_reproduced (defect itself certain by
code path).
What was verified
-
The read is real (by construction).
getsockaddr(sys/kern/uipc_syscalls.c:1523) kmallocs exactlynamelenbytes (β₯2, β€255) and copies only those;tcp_usr_bind(sys/netinet/tcp_usrreq.c:316-321) /tcp_usr_connect(:597-602) /tcp_usr_preconnect(:965-968) then readsin_familyandsin_addr.s_addr(offsets 4..7) beforein_pcbbind'ssa_len == 16gate (sys/netinet/in_pcb.c:648). The v6 twins (tcp6_usr_bind:406-411,tcp6_usr_connect:640-656) readsin6_addr(offsets 8..23) β fornamelen8 this is 16 bytes fully past the 8-byte slab chunk β and additionally pass the short buffer toprison_remote_ip(sys/kern/kern_jail.c:469-472), which reads address fields with no length check of its own. -
The multicast decision consumes
sin_addrregardless of sa_len (guest-verified, deterministic): bind with a 5-byte sockaddr (sa_len=5, copied byte 4 = 224) βEAFNOSUPPORT; byte 4 = 8 βEINVAL(see probe2.log / probe_matrix.log). This proves the handler bases its decision on asockaddr_inwhose declared length (5) is smaller than the fields it reads (8 bytes of sin_addr); fornamelenβ€ 4 the decision bytes are entirely uncopied. -
The recycled-byte oracle does not flip on the stock allocator (the honest negative): ~600k groomed attempts (8-byte and 16-byte M_SONAME grooms via UDP bind and sendto, 2..256 reps, single- and 48-process fleets, 200k-iteration hammers β probe.log, probe2.log, probe3.log) produced zero EAFNOSOURCE flips for
namelenβ€ 4. Root cause of the resilience: DF's slab allocator puts cross-CPU frees on the owning CPU'sz_RChunksremote list (sys/kern/kern_slaballoc.c:1491-1547) which user-thread allocations do not immediately reuse, and the v6 cross-object byte the multicast test samples is the neighbouring chunk'ssa_lenbyte (structurally 8, never 0xff). Affinity pinning (usched_set) is privileged, so the owner-CPU LIFO reuse could not be forced unprivileged.
Impact ceiling
1-bit error-code oracle (EAFNOSUPPORT vs EINVAL) on kernel heap contents beyond the user-copied length, per bind/connect attempt β but not reliably groomable on the stock kernel (see 3). No data leaves the kernel; no write primitive. This is the DF-3357 family with a much lower ceiling because TCP never uses the OOB address (UDP did).
Classification
status=not_reproduced, reproduced=0, impact=none, confidence=certain (that the OOB read occurs), severity Low, CWE-125.
Fix
Validate sa_len (and family) before any field read at the five sites,
e.g. tcp_usr_bind:
if (nam->sa_len < sizeof(struct sockaddr_in)) {
error = EINVAL;
goto out;
}
mirrored for connect/preconnect (sizeof(sockaddr_in)) and the v6 twins
(sizeof(sockaddr_in6)), plus a length guard inside prison_remote_ip
callers or in prison_remote_ip itself.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testableFix not kernel-validated: the defect has no observable manifestation on the stock system (oracle unflippable), so a patched kernel cannot be differentiated by this PoC; the length-check fix is trivially correct by inspection.
fix.diff (sa_len guards at the five sites)
Confirmed kernel references
Detail
Evidence (decisive lines)
['probe_matrix.log: len=5 sa[4]=224 -> errno 47 vs sa[4]=8 -> errno 22 (decision consumes sin_addr with sa_len<16); len<=4 always 22 across 64x grooms', 'probe3.log: 200k-iteration hammer + 48-process fleet: zero EAFNOSUPPORT flips', 'VERDICT.md: full code-path proof and allocator-topology analysis']
PoC changes
PoC authored for this run: grooms M_SONAME size classes via UDP bind/sendto with controlled sockaddr content, then TCP bind/connect with 2-8 byte sockaddrs; probes swept groom size (8/16), vector (bind/sendto), repetitions (2..256), namelen (2..5), affinity (usched_set denied), fleet (48 procs) and 200k-iteration hammers.
Verified recommended fix
Validate nam->sa_len >= sizeof(struct sockaddr_in)/sin6 before reading address fields at the five tcp_usrreq.c sites (and length-check inside prison_remote_ip).
Verdict
The out-of-bounds/uninitialized read is certain from the code path (getsockaddr kmallocs exactly namelen bytes and tcp_usr_bind/tcp_usr_connect/tcp_usr_preconnect/tcp6_usr_bind/tcp6_usr_connect read sin_addr/sin6_addr before any sa_len validation), and the multicast decision is live on partially-copied addresses (5-byte sockaddr with byte4=224 deterministically returns EAFNOSUPPORT vs EINVAL, proving the handler consumes the full sin_addr regardless of sa_len). However the observable oracle (groomed recycled bytes flipping EAFNOSUPPORT for namelen<=4) never fired across ~600k attempts (8/16-byte M_SONAME grooms via UDP bind and sendto, single/fleet/hammer variants): DF's slab allocator routes cross-CPU frees to the owning CPU's z_RChunks list which user-thread allocations do not reuse, affinity pinning (usched_set) is privileged, and the v6 cross-object byte the multicast test samples is structurally the neighbor chunk's sa_len (=8, never 0xff). Defect real (CWE-125, same family as DF-3357), demonstrated impact on stock system: none.
No comments yet.