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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3320 Β· 14 files
FileTypeDescriptionSize
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 becomes ifa_dstaddr)
  • SIOCAIFADDR mask: ia->ia_sockmask = ifra->ifra_mask; β€” in.c:668
  • SIOCSIFBRDADDR: ia->ia_broadaddr = *(struct sockaddr_in *)&ifr->ifr_broadaddr; β€” in.c:625; and for loopback ia_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 by ifconfig -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 of ifa_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 normal ifconfig lo0 alias 127.200.0.200/8 still 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).

VERDICT.md
↓ download raw

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):

  • :998 ia->ia_addr = *sin; (SIOCSIFADDR/SIOCAIFADDR β†’ in_ifinit), exported as ifa->ifa_addr (:543); for lo0 additionally :1062-1063 ia_dstaddr = ia_addr (β†’ ifa_dstaddr);
  • :602 SIOCSIFDSTADDR β†’ ia_dstaddr;
  • :625 SIOCSIFBRDADDR β†’ ia_broadaddr (= ifa_dstaddr);
  • :668 SIOCAIFADDR ifra_mask β†’ ia_sockmask (β†’ ifa_netmask, :545).

The bug (consume side, unprivileged)

  • 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 advances by sa_len. sa is ifa->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 (plain ifconfig -a exercises the same path).
  • sys/net/rtsock.c:1140,1175,1202 β€” RT_ROUNDUP(sa->sa_len)-sized copies of ifa_addr/ifa_dstaddr/ifa_netmask into 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-951 are 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)

  1. root: ./setup lo0 β€” SIOCAIFADDR with ifra_addr.sin_len=200 (127.200.0.200/8) + five normal filler aliases (deterministic slab neighbours). The ioctl succeeds; ifconfig lo0 shows the alias.
  2. 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's ifa_addr/ifa_dstaddr/ifa_netmask pointers (16-byte spacing = the sockaddr_in layout inside in_ifaddr). Identical across 3 runs (run.log; leak_sample.txt), no-KASLR guest.
  3. Baseline before setup: no sa_len>16 entry exists (also in run.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; normal ifconfig lo0 alias 127.200.0.200 netmask 255.0.0.0 succeeds and removes cleanly β€” no functional regression (fix_run.log).

Lines cited

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

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.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
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #3: Thu Sep 10 19:51:37 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

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.