IPv4 address-set ioctls persist user-supplied sin_len verbatim into live in_ifaddr (ia_addr/ia_dstaddr/ia_broadaddr/ia_sockmask) β unprivileged kernel heap pointer disclosure via SIOCGIFCONF (ifconf sa_len copyout) and route sockets; the live IPv4 twin of DF-3318
Summary
in_control_internal/in_ifinit copy user sockaddrs into the live struct in_ifaddr (360-byte heap object) with zero sin_len validation: in_ifinit 'ia->ia_addr = *sin', SIOCSIFDSTADDR, SIOCSIFBRDADDR, SIOCAIFADDR mask, plus the loopback copy ia_dstaddr = ia_addr - and export them as ifa->ifa_addr/ifa_dstaddr/ifa_netmask. Unprivileged consumers trust sa_len as a copy length: ifconf() (SIOCGIFCONF, no privilege check) does copyout(sa, ..., sa->sa_len), and rtsock copies ifa sockaddrs with RT_ROUNDUP(sa->sa_len) into route messages. With sin_len=200 the unpriv copyout reads 152 bytes past the in_ifaddr allocation - kernel heap pointers of the adjacent in_ifaddr disclosed to any local user after one privileged misconfiguration. VERIFIED on stock guest: root SIOCAIFADDR with sin_len=200 + filler aliases, then as nobody SIOCGIFCONF x3 -> 'lo0 sa_len=200' entries whose bytes [48..199] are past the object end, containing three kernel heap pointers (0xfffff8004f2e79d8/e8/f8 - the adjacent in_ifaddr's ifa pointers), identical across 3 runs. No unpriv write primitive - ceiling is the unpriv kernel-heap info leak (KASLR defeat + slab-layout disclosure), matching DF-3318's rating. Fix validated (reject sin_len > sizeof(sockaddr_in) on all address-set paths): setup fails EINVAL, leak finds no entry, normal alias add/remove unaffected.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3320 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.5 KB | β raw | |
| VERDICT.md | β | 4.4 KB | β raw | |
| setup.c | β | 3.2 KB | view raw | |
| leak.c | β | 2.9 KB | view raw | |
| build.sh | β | 93 B | view raw | |
| run.sh | β | 143 B | view raw | |
| run.log | β | 2.9 KB | view raw | |
| leak_sample.txt | β | 839 B | view raw | |
| env.txt | β | 406 B | view raw | |
| fix.diff | β | 3.7 KB | view raw | |
| fix_run.log | β | 847 B | view raw | |
| fix_build.log | β | 5.6 MB | β download | |
| verdict.json | β | 5.1 KB | view raw | |
| manifest.json | β | 1.3 KB | view raw |
DF-3320 β IPv4 sin_len persisted verbatim into in_ifaddr β unprivileged kernel heap leak (SIOCGIFCONF / route sockets)
Status: REPRODUCED (unprivileged disclosure of adjacent-slab kernel heap pointers, 3/3 identical runs) + fix validated (setup rejected EINVAL, leak gone, no regression).
What
The IPv4 address-set ioctls persist user-supplied sin_len verbatim into
the live struct in_ifaddr (a 360-byte heap object with ia_addr at offset
312) and export it as ifa->ifa_addr:
in_ifinit():ia->ia_addr = *sin;βsys/netinet/in.c:998(SIOCSIFADDR / SIOCAIFADDR, privileged)SIOCSIFDSTADDR:ia->ia_dstaddr = *(struct sockaddr_in *)&ifr->ifr_dstaddr;βin.c:602(also becomesifa_dstaddr)SIOCAIFADDRmask:ia->ia_sockmask = ifra->ifra_mask;βin.c:668SIOCSIFBRDADDR:ia->ia_broadaddr = *(struct sockaddr_in *)&ifr->ifr_broadaddr;βin.c:625; and for loopbackia_dstaddr = ia_addrβin.c:1062-1063
There is no sin_len validation anywhere on these paths (the in6 twin was
DF-3318). Unprivileged consumers then trust sa_len as a copy length:
ifconf()(SIOCGIFCONF, no privilege check βsys/net/if.c:1996-1997, used byifconfig -a):c copyout(sa, &ifrp->ifr_addr, sa->sa_len); /* sys/net/if.c:2590-2592 */- route sockets (
RTM_NEWADDR/RTM_DELADDR/dump messages):dlen = RT_ROUNDUP(sa->sa_len)copies ofifa_addr/ifa_dstaddr/ifa_netmaskβsys/net/rtsock.c:1140,1175,1202(readable unprivileged).
With sin_len = 200, SIOCGIFCONF copies 200 bytes starting at &ia->ia_addr
(object offset 312) β 152 bytes past the end of the allocation β straight
to userspace.
This is the exact IPv4 twin of DF-3318's model: one privileged misconfiguration (root, or a buggy config/routing tool feeding root an unpadded sockaddr) plants the length; every local user can then read adjacent kernel heap memory.
How to reproduce
cc -O -o setup setup.c && cc -O -o leak leak.c # build.sh ./setup lo0 # as root (one time) su -m nobody -c "./leak 127.200.0.200" # unprivileged, repeat 3x
Expected (stock INVARIANTS kernel #0)
entry: lo0 sa_len=200 family=2 addr=127.200.0.200
0: c8 02 00 00 7f c8 00 c8 ... ia_addr (poisoned len 200)
16: c8 02 00 00 7f c8 00 c8 ... ia_dstaddr (loopback copy, len 200)
32: 05 02 00 00 ff 00 00 00 ... ia_sockmask (/8, trimmed len 5)
48: .................. <- object ends here (360 = 312+48)
72: d8 79 2e 4f 00 f8 ff ff 0xfffff8004f2e79d8 \
80: e8 79 2e 4f 00 f8 ff ff 0xfffff8004f2e79e8 > adjacent
88: f8 79 2e 4f 00 f8 ff ff 0xfffff8004f2e79f8 / in_ifaddr's
ifa_addr/ifa_dstaddr/ifa_netmask
Three kernel heap pointers (the next in_ifaddr in the 384-byte bucket β
added as a filler alias by setup), identical across 3 consecutive runs
(run.log, leak_sample.txt). Baseline before setup: no such entry.
Fix validation (fix.diff, kernel #3 Thu Sep 10 19:51:37 UTC 2026)
- stock #0: setup OK β leak shows 200-byte entry with heap pointers.
- patched #3:
./setup lo0βSIOCAIFADDR(lo0, len=200): Invalid argument(rejected at the door); unpriv leak β no entry; a normalifconfig lo0 alias 127.200.0.200/8still succeeds and can be removed β no regression (fix_run.log).
See DF-3319's pack for the shared fix.diff (set-side validation is the
DF-3320 half; in_ifinit() and the SIOCAIFADDR/SIOCSIFDSTADDR/SIOCSIFBRDADDR
guards).
DF-3320 VERDICT
Status: reproduced β unprivileged kernel heap pointer disclosure after a one-time privileged misconfiguration (DF-3318's IPv4 twin, live today through SIOCGIFCONF and route sockets). Fix validated on a rebuilt kernel.
The bug (set side, privileged)
in_control_internal()/in_ifinit() copy user sockaddrs into the live
struct in_ifaddr verbatim, sin_len included β no length check exists on
any of these paths (sys/netinet/in.c):
:998ia->ia_addr = *sin;(SIOCSIFADDR/SIOCAIFADDR βin_ifinit), exported asifa->ifa_addr(:543); for lo0 additionally:1062-1063ia_dstaddr = ia_addr(βifa_dstaddr);:602SIOCSIFDSTADDR βia_dstaddr;:625SIOCSIFBRDADDR βia_broadaddr(=ifa_dstaddr);:668SIOCAIFADDRifra_maskβia_sockmask(βifa_netmask,:545).
The bug (consume side, unprivileged)
ifconf()βSIOCGIFCONF, no privilege check (sys/net/if.c:1996-1997) β doescopyout(sa, &ifrp->ifr_addr, sa->sa_len)(sys/net/if.c:2590-2592) and advances bysa_len.saisifa->ifa_addr=&ia->ia_addr(object offset 312 of 360):sin_len = 200β 152 bytes read past the end of the heap object, copied straight to any local user (plainifconfig -aexercises the same path).sys/net/rtsock.c:1140,1175,1202βRT_ROUNDUP(sa->sa_len)-sized copies ofifa_addr/ifa_dstaddr/ifa_netmaskinto route-socket messages (RTM_NEWADDR/RTM_DELADDR broadcast right after the setup ioctl; readable unprivileged).- (The in-file consumers
sys/netinet/in.c:927/:930-951are the same pattern, but on the current tree they are gated behind the DF-0490 filter and only ever see the type-confused in6_ifaddr β that manifestation is DF-3319. SIOCGIFADDR/SIOCGIFNETMASK/SIOCGIFDSTADDR/SIOCGIFALIAS handlers use fixed-size struct assigns and are safe.)
Reproduction (stock INVARIANTS kernel #0, dfbsd 6.5-DEVELOPMENT)
- root:
./setup lo0β SIOCAIFADDR withifra_addr.sin_len=200(127.200.0.200/8) + five normal filler aliases (deterministic slab neighbours). The ioctl succeeds;ifconfig lo0shows the alias. - nobody:
./leak 127.200.0.200β SIOCGIFCONF, 65536-byte buffer; the packed entry for the poisoned address carries 200 bytes:[0..15] ia_addr (len 200),[16..31] ia_dstaddr (len 200),[32..47] ia_sockmask,[48..199] out-of-bounds. At offsets 72/80/88:0xfffff8004f2e79d8 / ...e8 / ...f8β the adjacent in_ifaddr'sifa_addr/ifa_dstaddr/ifa_netmaskpointers (16-byte spacing = the sockaddr_in layout inside in_ifaddr). Identical across 3 runs (run.log;leak_sample.txt), no-KASLR guest. - Baseline before setup: no
sa_len>16entry exists (also inrun.log).
Impact ceiling: kernel-heap address disclosure (KASLR defeat + slab layout revelation) to unprivileged users after one privileged misconfiguration β the same class/rating DF-3318 received. Not a write primitive: no unprivileged-only path to the OOB write was found (the in-file write sinks are privileged or filter-gated).
Fix validation
fix.diff adds set-side sin_len > sizeof(struct sockaddr_in) rejection on
SIOCAIFADDR (addr/mask/dstaddr/broadaddr), SIOCSIFDSTADDR, SIOCSIFBRDADDR,
and in in_ifinit() before :998 (plus the DF-3319 hunks). Rebuilt in-guest
(kernel #3, 0 warnings):
- stock #0: setup OK, leak shows pointers (3/3).
- patched #3:
./setup lo0β EINVAL (SIOCAIFADDR(lo0, len=200): Invalid argument);./leakβ no entry; normalifconfig lo0 alias 127.200.0.200 netmask 255.0.0.0succeeds and removes cleanly β no functional regression (fix_run.log).
Lines cited
sys/netinet/in.c:998β verbatimia_addr = *sin(the persistence)sys/netinet/in.c:543-545β ia_addr/ia_dstaddr/ia_netmask exported as ifaddr sockaddrssys/netinet/in.c:602, :625, :668, :1062-1063β further verbatim sin_len copiessys/net/if.c:1996-1997, :2578-2592β unpriv SIOCGIFCONF β sa_len-sized copyoutsys/net/rtsock.c:1140, :1175, :1202β sa_len-sized route message copiessys/kern/sys_generic.c:668-680β ioctl scratch sizing (context)
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
fixedfix.diff applied in-guest, kernel rebuilt (0 warnings) and installed as #3. Baseline stock: setup OK, unpriv leak shows 200-byte entry with 3 kernel heap pointers (3/3 runs). Patched: setup => EINVAL at the SIOCAIFADDR door; leak => no sa_len>16 entry; normal 'ifconfig lo0 alias 127.200.0.200 netmask 255.0.0.0' succeeds and removes cleanly (no regression).
fix_run.log (validation matrix incl. DF-3320 steps 4-6), fix_build.log (kernel #3 build), fix.diff
Confirmed kernel references
Detail
Exploit chain
root (one-time misconfiguration, e.g. buggy config tool): SIOCAIFADDR(lo0, addr=127.200.0.200 sin_len=200, mask /8) -> in_control_internal -> in_ifinit (in.c:998) persists sin_len=200 into ia_addr (and ia_dstaddr via the loopback copy at :1062-1063) -> any local user: socket+ioctl(SIOCGIFCONF) -> ifconf() copyout(sa, &ifrp->ifr_addr, 200) reads 152 bytes past the in_ifaddr heap object -> userspace receives kernel heap pointers of the adjacent in_ifaddr (KASLR defeat + slab layout disclosure). Route sockets (rtsock.c RTM_NEWADDR broadcast on the same setup ioctl) leak the same via ifa_dstaddr/ifa_netmask lengths.
Evidence (decisive lines)
["run.log: baseline 'no entry' before setup; after setup, 3/3 identical runs as nobody showing 'entry: lo0 sa_len=200 family=2 addr=127.200.0.200' with kernel pointers at leaked offsets 72/80/88", 'leak_sample.txt: hexdump of the 200-byte leaked sockaddr -- [0..15] ia_addr (c8 02 = len 200, family 2), [16..31] ia_dstaddr, [32..47] ia_sockmask (05 02 ... ff = /8 trimmed), [48..] out-of-bounds region containing 0xfffff8004f2e79d8/e8/f8', 'env.txt: guest uname, cc 8.3, lo0 state after setup', "fix_run.log: patched kernel #3 -- setup => 'SIOCAIFADDR(lo0, len=200): Invalid argument', leak => no entry, normal ifconfig alias add/remove works", 'fix.diff: set-side sin_len>sizeof(struct sockaddr_in) rejection (SIOCAIFADDR/SIOCSIFDSTADDR/SIOCSIFBRDADDR/in_ifinit) + DF-3319 hunks']
PoC changes
Fixed the pointer-scan filter in leak.c (v & 0xf == 0 rejected the 0x...8-ending kernel pointers; changed to (v & 0x7) == 0); added five filler aliases in setup.c for deterministic adjacent-slab content.
Verified recommended fix
Reject sin_len > sizeof(struct sockaddr_in) on all IPv4 address-set ioctls (SIOCAIFADDR/SIOCSIFADDR/SIOCSIFDSTADDR/SIOCSIFBRDADDR, and in in_ifinit before :998) so unvalidated lengths are never persisted into live ifaddrs consumed by ifconf()/rtsock.
Verdict
REPRODUCED. The IPv4 address-set ioctls persist user-supplied sin_len verbatim into the live struct in_ifaddr (in.c:998 ia_addr = *sin via SIOCSIFADDR/SIOCAIFADDR->in_ifinit; :602 ia_dstaddr via SIOCSIFDSTADDR; :625 ia_broadaddr via SIOCSIFBRDADDR; :668 ia_sockmask via SIOCAIFADDR) and export them as ifa->ifa_addr/ifa_dstaddr/ifa_netmask with no validation -- the IPv4 twin of DF-3318. Unprivileged consumers then trust sa_len as a copy length: ifconf() (SIOCGIFCONF, no privilege check, sys/net/if.c:1996-1997) does copyout(sa, &ifrp->ifr_addr, sa->sa_len) (sys/net/if.c:2590-2592), and rtsock copies ifa sockaddrs with RT_ROUNDUP(sa->sa_len) into route messages (sys/net/rtsock.c:1140,1175,1202). After a one-time privileged SIOCAIFADDR with sin_len=200 on lo0 (152 bytes of the copy read past the 360-byte in_ifaddr allocation), an unprivileged user reading SIOCGIFCONF obtained three adjacent-slab kernel heap pointers -- 0xfffff8004f2e79d8 / ...e8 / ...f8, the neighbouring in_ifaddr's ifa_addr/ifa_dstaddr/ifa_netmask -- identical across 3/3 runs as nobody. Fix validated on rebuilt kernel #3: setup rejected with EINVAL, leak finds no entry, normal alias add/remove unaffected.
No comments yet.