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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0490 Β· 12 files
FileTypeDescriptionSize
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
README.md readme summary + reproduce instructions
↓ download 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): SIOCGLIFADDR returns ss_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, sha256 64bbd4f7…): correct IPv4 address; deterministic over 2 runs.

See VERDICT.md for the full mechanism, offset math, reachability, and evidence.

VERDICT.md verdict full analysis: mechanism, reachability, offset math, evidence
↓ download raw

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_ioctl is reached from in_control (in.c:434-446).
  • SIOCALIFADDR and SIOCDLIFADDR require SYSCAP_RESTRICTEDROOT (in.c:437-441) β€” root only.
  • SIOCGLIFADDR has NO privilege check β€” it falls straight through (in.c:443-446) to in_lifaddr_ioctl. Reachable by an unprivileged user.

Mechanism (trigger β†’ primitive β†’ effect)

  1. Trigger: an unprivileged user issues ioctl(s, SIOCGLIFADDR, &iflr) on an AF_INET datagram socket for an interface that has at least one IPv6 address (any interface with a link-local fe80:: qualifies β€” every DragonFly Ethernet interface has one by default).

  2. Primitive β€” wrong-family filter: in.c:911 reads if (ifa->ifa_addr->sa_family != AF_INET6) continue;. Because the check uses AF_INET6 (=28) instead of AF_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.

  3. Type confusion: line 923 ia = (struct in_ifaddr *)(ifac->ifa) casts the matched in6_ifaddr * to in_ifaddr *. The two structs lay ia_addr at different offsets (measured on this build): - struct in_ifaddr: ia_addr at offset 312 (after ia_net, ia_netmask, ia_subnet, ia_subnetmask, ia_netbroadcast, ia_pad1, ia_pad2). - struct in6_ifaddr: ia_addr at offset 240 (immediately after ifaddr).

  4. Effect β€” wrong-offset read: line 927 bcopy(&ia->ia_addr, &iflr->addr, ia->ia_addr.sin_len) reads ia_addr.sin_len from offset 312 β€” which lands inside in6_ifaddr.ia_dstaddr (a sockaddr_in6 spanning offsets 296–324). On the default config that interface is not point-to-point, so ia_dstaddr is zeroed and sin_len reads 0 β†’ bcopy copies 0 bytes β†’ the caller's buffer stays all-zero. The returned addr.ss_family is therefore -1 (0 interpreted, no valid family) and ss_len is 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_len reads 0 at the mis-typed offset).
  • Ceiling: configuration-dependent information leak if the type-confused sin_len position holds a non-zero value (point-to-point IPv6 ia_dstaddr).
  • Not memory corruption (the SIOCGLIFADDR path is read-only; the root-only SIOCDLIFADDR path 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

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: baseline all-zeros; patched returns 10.0.2.15.

BEFORE: all-zeros. AFTER: 10.0.2.15 AF_INET.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Thu Jul 16 18:11:00 UTC 2026

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.