Type confusion in in_lifaddr_ioctl: AF_INET6 check matches IPv6 addresses cast to in_ifaddr β unpriv heap OOB read/info leak via SIOCGLIFADDR
Summary
in_lifaddr_ioctl(:911): if(ifa->ifa_addr->sa_family != AF_INET6) continue β uses AF_INET6 in IPv4 handler, should be AF_INET. Skips all IPv4 addrs, matches only IPv6. Line :923 casts in6_ifaddr as in_ifaddr. Struct layout mismatch: in_ifaddr.ia_addr at ~72 bytes from start, in6_ifaddr at different offset. Line :927 bcopy(&ia->ia_addr, &iflr->addr, ia->ia_addr.sin_len) reads sin_len byte from wrong offset (middle of in6 dstaddr) β arbitrary byte value controls bcopy length -> heap OOB read up to 255 bytes past allocation. SIOCGLIFADDR has NO privilege check: :443 case SIOCGLIFADDR falls through from SIOCALIFADDR/SIOCDLIFADDR priv gate at :437-441 without check. Any local user calls SIOCGLIFADDR on lo0 (has ::1 by default) -> matches first AF_INET6 -> heap leak copyout to caller. KASLR bypass. Large sin_len crossing unmapped page -> panic. DragonFly-specific typo (FreeBSD uses AF_INET). Fix: change AF_INET6->AF_INET at :911; validate sin_len==sizeof(sockaddr_in).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0490 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | summary + reproduce instructions | 1.4 KB | β raw |
| poc_df0490.c | trigger-source | SIOCGLIFADDR exerciser; unprivileged, dumps returned addr + cross-checks SIOCGIFADDR | 3.1 KB | view raw |
| build.sh | build-script | cc -o poc_df0490 poc_df0490.c | 158 B | view raw |
| run.sh | run-script | runs poc_df0490 <ifname> as unprivileged | 440 B | view raw |
| run.log | run-log | BASELINE run on unpatched #0 kernel: ss_len=0 all-zeros (type confusion) | 1.5 KB | view raw |
| fix_run.log | run-log | PATCHED #1 kernel: decoded IPv4 addr 10.0.2.15 (fix confirmed) | 683 B | view raw |
| fix.diff | suggested-fix | in.c:911 AF_INET6 -> AF_INET (git-apply-able, validated) | 379 B | view raw |
| fix_build.log | build-log | single-fix nativekernel build, NK_DONE rc=0 | 5.6 MB | β download |
| VERDICT.md | verdict | full analysis: mechanism, reachability, offset math, evidence | 4.8 KB | β raw |
| env.txt | environment | uname, cc version, vtnet0 addresses | 413 B | view 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 |
DF-0490 β Type confusion in in_lifaddr_ioctl (AF_INET6 typo) β REPRODUCED
Result
REPRODUCED. sys/netinet/in.c:911 uses AF_INET6 inside the IPv4 handler
in_lifaddr_ioctl instead of AF_INET. The loop skips IPv4 addresses, matches
IPv6 addresses, and casts an in6_ifaddr * to in_ifaddr * (line 923) β a
genuine type confusion. ia_addr is then read from offset 312 (in_ifaddr
layout) when the actual in6_ifaddr has it at offset 240.
SIOCGLIFADDR is reachable by an unprivileged user (no privilege check at
in.c:443-446). On the default GENERIC config the demonstrated effect is a logic
error (SIOCGLIFADDR returns all-zero/wrong data instead of the IPv4 address);
0 bytes of kernel memory leak because the mis-typed sin_len position reads 0.
Reproduce
./build.sh && ./run.sh vtnet0
- BUGGY (#0 kernel):
SIOCGLIFADDRreturnsss_family=-1, ss_len=0, all-zeros. - FIXED (#1 kernel): returns
decoded IPv4 addr: 10.0.2.15.
Fix
sys/netinet/in.c:911: change AF_INET6 β AF_INET. See fix.diff
(git-apply-able, validated by single-fix kernel build + boot + re-run).
Matches the finding markdown's recommended fix.
Validation
- Baseline (
#0): type-confused empty output. - Patched (
#1, sha25664bbd4f7β¦): correct IPv4 address; deterministic over 2 runs.
See VERDICT.md for the full mechanism, offset math, reachability, and evidence.
DF-0490 β Type confusion in in_lifaddr_ioctl (AF_INET6 typo in IPv4 handler)
Verdict: REPRODUCED (logic/type-confusion bug; 0 bytes leaked on default config)
The bug is real and confirmed: sys/netinet/in.c:911 uses AF_INET6 inside
the IPv4 address-family handler in_lifaddr_ioctl, when it must use AF_INET.
This skips every IPv4 interface address and matches IPv6 addresses, then casts an
in6_ifaddr * to an in_ifaddr * at line 923 β a genuine type confusion.
The fix (AF_INET6 β AF_INET) is validated by building a single-fix kernel
and confirming SIOCGLIFADDR returns the correct IPv4 address.
Reachability (the unprivileged path matters)
in_lifaddr_ioctlis reached fromin_control(in.c:434-446).SIOCALIFADDRandSIOCDLIFADDRrequireSYSCAP_RESTRICTEDROOT(in.c:437-441) β root only.SIOCGLIFADDRhas NO privilege check β it falls straight through (in.c:443-446) toin_lifaddr_ioctl. Reachable by an unprivileged user.
Mechanism (trigger β primitive β effect)
-
Trigger: an unprivileged user issues
ioctl(s, SIOCGLIFADDR, &iflr)on anAF_INETdatagram socket for an interface that has at least one IPv6 address (any interface with a link-localfe80::qualifies β every DragonFly Ethernet interface has one by default). -
Primitive β wrong-family filter:
in.c:911readsif (ifa->ifa_addr->sa_family != AF_INET6) continue;. Because the check usesAF_INET6(=28) instead ofAF_INET(=2), the loop skips the IPv4 address (10.0.2.15, family 2) and matches the IPv6 address (fe80::5054:ff:fe12:3456, family 28). For the plain (non-IFLR_PREFIX)SIOCGLIFADDR,cmp=0, so it breaks on that first IPv6 match. -
Type confusion: line 923
ia = (struct in_ifaddr *)(ifac->ifa)casts the matchedin6_ifaddr *toin_ifaddr *. The two structs layia_addrat different offsets (measured on this build): -struct in_ifaddr:ia_addrat offset 312 (afteria_net,ia_netmask,ia_subnet,ia_subnetmask,ia_netbroadcast,ia_pad1,ia_pad2). -struct in6_ifaddr:ia_addrat offset 240 (immediately afterifaddr). -
Effect β wrong-offset read: line 927
bcopy(&ia->ia_addr, &iflr->addr, ia->ia_addr.sin_len)readsia_addr.sin_lenfrom offset 312 β which lands insidein6_ifaddr.ia_dstaddr(asockaddr_in6spanning offsets 296β324). On the default config that interface is not point-to-point, soia_dstaddris zeroed andsin_lenreads 0 βbcopycopies 0 bytes β the caller's buffer stays all-zero. The returnedaddr.ss_familyis therefore-1(0 interpreted, no valid family) andss_lenis 0.
Leak ceiling: if the byte at the sin_len position were non-zero (a
point-to-point IPv6 interface whose ia_dstaddr.sin6_addr has a non-zero byte
16), bcopy would copy that many bytes of kernel heap (the struct region
312..) to userspace β an information leak. On the default GENERIC config the
demonstrated leak is 0 bytes; the observed effect is a logic error
(SIOCGLIFADDR returns empty/wrong data instead of the IPv4 address).
Evidence
Baseline (unpatched #0 kernel, kern.version = 6.5-DEVELOPMENT #0):
=== SIOCGLIFADDR on vtnet0 (cmp=0 path) === SIOCGLIFADDR OK addr.ss_family = -1 (AF_INET=2 AF_INET6=28) addr.ss_len = 0 addr raw: all zeros (128 bytes) [reference] SIOCGIFADDR vtnet0 -> 10.0.2.15 (the REAL IPv4 addr)
Patched (#1 single-fix kernel, kern.version = 6.5-DEVELOPMENT #1):
=== SIOCGLIFADDR on vtnet0 (cmp=0 path) === SIOCGLIFADDR OK addr.ss_family = 2 (AF_INET=2 AF_INET6=28) addr.ss_len = 16 decoded IPv4 addr: 10.0.2.15 [reference] SIOCGIFADDR vtnet0 -> 10.0.2.15
(lo0 likewise returns 127.0.0.1 after the fix; determinism confirmed over 2 runs.)
Fix
One-line change at sys/netinet/in.c:911: AF_INET6 β AF_INET.
See fix.diff (git-apply-able; validated by build + boot + re-run).
This matches the finding markdown's ## Recommended fix proposal (same one-line
change).
Impact assessment
- Class: logic bug + type confusion; reachable unprivileged via
SIOCGLIFADDR. - Demonstrated on default GENERIC: SIOCGLIFADDR returns wrong/empty data
(logic error). 0 bytes of kernel memory leak on the default config
(
sin_lenreads 0 at the mis-typed offset). - Ceiling: configuration-dependent information leak if the type-confused
sin_lenposition holds a non-zero value (point-to-point IPv6ia_dstaddr). - Not memory corruption (the
SIOCGLIFADDRpath is read-only; the root-onlySIOCDLIFADDRpath could in principle delete/alter the wrong address via the same confusion, but that requires root so it is not a privilege escalation). - Not a DoS / panic / privesc.
Fix verification
fixedVALIDATED: baseline all-zeros; patched returns 10.0.2.15.
BEFORE: all-zeros. AFTER: 10.0.2.15 AF_INET.
Confirmed kernel references
Detail
Exploit chain
none -- read-only GET path logic error. Config-dependent info-leak ceiling.
Evidence (decisive lines)
BEFORE: ss_family=-1 all-zeros. AFTER: ss_family=2 AF_INET, 10.0.2.15.
PoC changes
poc_df0490.c (SIOCGLIFADDR), fix.diff (AF_INET6->AF_INET), VERDICT.md, manifest.json.
Verified recommended fix
Change AF_INET6 to AF_INET at in.c:911. One-line typo fix. Matches finding. Full diff in findings/poc/DF-0490/fix.diff.
Verdict
REPRODUCED. in.c:911 AF_INET6 in IPv4 handler should be AF_INET. TAILQ_FOREACH skips all IPv4 addrs, matches IPv6 -> type confusion in6_ifaddr cast to in_ifaddr at :923. SIOCGLIFADDR unprivileged. Baseline: all-zeros. Fix: returns 10.0.2.15.
No comments yet.