DF-3319 / verdict.json
{ "finding_id": "DF-3319", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "REPRODUCED. The AF_INET SIOCDLIFADDR path in in_lifaddr_ioctl() bcopys ifaddr sockaddls into a 64-byte on-stack struct in_aliasreq using unvalidated sin_len values as lengths (in.c:949-956). Because the walk filter at in.c:911 selects AF_INET6 ifaddrs in the IPv4 handler (DF-0490) and :923 casts an in6_ifaddr as in_ifaddr, ia_sockmask.sin_len reads in6_ifaddr offset 344 = prefixmask.sin6_addr byte 12, which is 0xff for any IPv6 prefix >= /104 -- including lo0s stock ::1/128 -- giving a deterministic 255-byte bcopy (223 bytes past the stack object) that smashes the return path on the netisr0 dispatch thread. Observed: Fatal trap 12, supervisor write at 0xe4, instruction pointer = 0xfffff8004f308f46 (heap address -- the smashed return address contained a kernel heap pointer), CPU stopped executing heap garbage, guest wedged in DDB (panic.txt). Privileged trigger (SIOCDLIFADDR requires SYSCAP_RESTRICTEDROOT, in.c:437-441); deterministic kernel stack control-flow corruption. Fix validated on rebuilt kernel #3: ioctl returns EADDRNOTAVAIL, lo0 intact, guest stays up, and v4 SIOCGLIFADDR becomes functional (127.0.0.1 returned) as regression proof.", "exploit_chain": "root: socket(AF_INET,SOCK_DGRAM) -> ioctl(SIOCDLIFADDR, {lo0, addr=0.248.255.255/32}) -> in_control dispatch to netisr0 (in.c:230-248) -> in_lifaddr_ioctl walk: filter != AF_INET6 (in.c:911, DF-0490 typo) skips AF_INET, selects ::1s in6_ifaddr via the pointer-byte candidate match (in.c:915-916) -> cast to in_ifaddr (in.c:923) -> bcopy(&ia->ia_sockmask, &ifra.ifra_dstaddr, ia_sockmask.sin_len) with len=0xff read from prefixmask.sin6_addr[12] of the /128 mask (in.c:955-956) -> 255 bytes written into the 64-byte stack in_aliasreq at +32 -> return address overwritten with in6_ifaddr-tail/adjacent-slab content -> RIP follows kernel heap pointer -> Fatal trap 12, DDB wedge, DoS.", "evidence": [ "panic.txt: Fatal trap 12: page fault while in kernel mode, fault virtual address = 0xe4, instruction pointer = 0x8:0xfffff8004f308f46, current process = Idle, Stopped at -0x7ffb0cf70ba: addb %al,(%rax)", "precheck.log / run.log: stock-kernel SIOCGLIFADDR(lo0) returns family=0 len=0 addr=0.0.0.0 (the DF-0490 confusion feeding these bcopy lengths)", "smash.log: ssh session hangs on the ioctl; serial console shows the trap", "fix_run.log: patched kernel #3 -- smash => EADDRNOTAVAIL, 127.0.0.1 intact, getlif returns 127.0.0.1 len=16 prefixlen=8, guest up", "fix.diff: AF_INET filter fix (:911), candidate deref fix (:915-916), consume-side clamps (alen/dlen/mlen <= sizeof(sockaddr_in)), set-side sin_len validation", "fix_build.log: >>> Kernel build for X86_64_GENERIC completed, 0 warnings" ], "kernel_refs": [ "sys/netinet/in.c:911", "sys/netinet/in.c:915-916", "sys/netinet/in.c:923", "sys/netinet/in.c:941-959", "sys/netinet/in.c:949-950", "sys/netinet/in.c:955-956", "sys/netinet/in.c:230-248", "sys/netinet/in.c:437-441", "sys/netinet/in.c:602", "sys/netinet/in.c:668", "sys/netinet6/in6_var.h:116-132" ], "poc_changes": "Derived the walk match address: the candidate expression at in.c:915-916 reads through &ifa->ifa_addr (address of the pointer field), so candidate is always the pointers high 4 bytes (0xfffff800 on this guest) -> query 0.248.255.255; first attempt with INADDR_ANY returned EADDRNOTAVAIL. Added arpa/inet.h include and corrected the printf string.", "attempts": 2, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 420, "guest_dirty": 0, "build_cmd": "cc -O -o smash smash.c && cc -O -o getlif getlif.c", "run_cmd": "./getlif lo0; ./smash lo0 (as root; expect Fatal trap 12 on stock, EADDRNOTAVAIL on patched)", "code_hash": "b415a7aae03cab2a07508239fc9ee34951cedbae597d0cefed10123148402ad8", "notes": "DF-0490s filed claim covers only the unprivileged read at in.c:927; this is the missed privileged stack-smash side of the same confusion (:949-956), plus the DF-3318-twin persistence route (in.c:602/:668 poison ia_dstaddr/ia_sockmask.sin_len, live directly once :911 is fixed without length validation). An intermediate kernel with clamps only (candidate hunk missing) did not panic but the pointer-byte match deleted 127.0.0.1 -- recorded as evidence that the full fix is required.", "recommended_fix": "in.c:911 AF_INET6->AF_INET; in.c:915-916 dereference ifa->ifa_addr; clamp all sin_len bcopy lengths in in_lifaddr_ioctl to sizeof(struct sockaddr_in); reject sin_len>sizeof(struct sockaddr_in) on SIOCAIFADDR/SIOCSIFDSTADDR/SIOCSIFBRDADDR and in in_ifinit().", "fix_status": "fixed", "fix_kernel_uname": "DragonFly 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", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff applied in-guest to /usr/src/sys/netinet/in.c, make -j4 nativekernel (0 warnings), installkernel, reboot into kernel #3. Baseline stock kernel: Fatal trap 12 / DDB wedge. Patched: ./smash lo0 => EADDRNOTAVAIL, no panic, lo0 keeps 127.0.0.1 (no bogus delete), guest stays up; SIOCGLIFADDR(lo0) now returns 127.0.0.1 (v4 LIFADDR functional, regression proof); normal alias add/remove unaffected. Bad behavior gone, correct error returned.", "fix_evidence": "fix_run.log (full validation matrix), fix_build.log (kernel #3 build), fix.diff (git-apply-able, applied with patch in guest)" } |