{
  "finding_id": "DF-2253",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "none",
  "confidence": "certain",
  "verdict": "With sep == NULL the loop at sys/libkern/hexncpy.c:56 still drains outlen by 3 per iteration but only writes 2 chars (:57-58), and the terminator *--outb = 0 at :62 backs over the last written digit instead of following it. Verified through the kernel-compiled REAL function (runtime kldload GPFs this guest; harness compiled into sys/libkern/hexncpy.c via make nativekernel): C5 with the exact if_ether.c:643 call shape (inlen=2, outlen=5, sep=NULL, ar_hrd bytes 12 34) returns the string 1 - one digit, the 2 clobbered by the misplaced NUL; C4 (inlen=6, outlen=HEX_NCPYLEN(6)=18) returns deadbeef123 instead of deadbeef1234; C6 (mly.c:1344 shape, inlen=4 outlen=12) returns deadbee instead of deadbeef; the sep!=NULL control is unaffected (de-ad-be-ef). End-to-end on the live stock kernel: a crafted ethernet frame with ar_hrd=0x1234 written to /dev/tap0 is injected by tapwrite into ifp->if_input (sys/net/tap/if_tap.c:981) -> ether_input -> NETISR_ARP -> arpintr (sys/netinet/if_ether.c:630-648) -> the :643 hexncpy call, and the console logs arp: unknown hardware address format (0x1) - one digit where 0x1234 belongs (arp_e2e.log, reproduced twice with identical output). Any unauthenticated LAN peer can trigger this path with a single frame; the guest injector needs root only because /dev/tap0 is root-only. Memory safety is NOT violated: hexstr is char[6] (if_ether.c:632) and the function writes at most hexstr[0..1] here - the impact ceiling is a wrong operator-facing diagnostic (top nibble of the first hrd byte only), so impact=none beyond correctness. fix.diff (drain sep?3:2, terminate after the last digit for sep==NULL, keep trailing-separator replacement for sep!=NULL) validated A/B on the rebuilt kernel #2: C5 -> 1234, C4 -> deadbeef1234, C6 -> deadbeef, live ARP log now (0x1234), sep!=NULL output byte-identical, guest up.",
  "exploit_chain": "",
  "evidence": "run.log (baseline matrix: C5 out=[1], C4 deadbeef123, C6 deadbee, C1 control clean); arp_e2e.log (stock kernel, tap injection: arp: unknown hardware address format (0x1) for a frame carrying ar_hrd=0x1234, twice); arp_e2e_fixed.log (patched kernel: (0x1234)); fix_run.log (patched matrix C4/C5/C6 correct, sep!=NULL identical); fix_build.log (nativekernel BUILD2_RC=0); arp_inject.c sha256 18d55cc12e5cb93fc7439c11acc73eaeb5ed4852eb07c230935e1555e2d1d98d",
  "kernel_refs": [
    "sys/libkern/hexncpy.c:56",
    "sys/libkern/hexncpy.c:59",
    "sys/libkern/hexncpy.c:62",
    "sys/netinet/if_ether.c:632",
    "sys/netinet/if_ether.c:643",
    "sys/net/tap/if_tap.c:981",
    "sys/dev/raid/mly/mly.c:1344"
  ],
  "poc_changes": "Authored fresh (no seeded pack on disk). Rig compiled into the kernel (kldload GPFs this guest): shared harness with DF-2252 for the function-level matrix, plus arp_inject.c writing a crafted ARP frame to /dev/tap0 (tap pseudo-device added to the guest X86_64_GENERIC) to reach the real arpintr path on the live kernel. Guest source verified byte-identical to the audit tree before patching.",
  "attempts": 3,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly v6.5.0.1712.g89e6a-DEVELOPMENT #1: Sat Oct 17 16:14:33 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline, INVARIANTS); #2: Sat Oct 17 16:28:39 UTC 2026 (single-fix kernel)",
  "runtime_sec": 1600,
  "guest_dirty": 0,
  "build_cmd": "sh build.sh (guest: harness + pseudo-device tap into kernel; cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel; reboot; cc -O2 -o /root/arp_inject arp_inject.c)",
  "run_cmd": "sh run.sh (guest: sysctl kern.df_hexncpy=1; ifconfig tap0 create; ifconfig tap0 up; /root/arp_inject; dmesg | grep arp)",
  "code_hash": "18d55cc12e5cb93fc7439c11acc73eaeb5ed4852eb07c230935e1555e2d1d98d",
  "notes": "Impact none: purely a wrong diagnostic string (low nibble of each last byte dropped, ~1/3 of input elided); no OOB read or write in this callers buffer (char hexstr[6], writes at [0..1]). The bug class matters because the same helper feeds security-relevant logs (arpintr unknown-hardware-format line is what an operator consults when fuzzed ARP shows up). Guest reset with-src after validation.",
  "recommended_fix": "Only write separator-then-replace-terminator when sep != NULL: drain (sep ? 3 : 2) per iteration and terminate right after the last digit for sep == NULL (see fix.diff, shared with DF-2252).",
  "fix_status": "fixed",
  "fix_baseline_reproduced": true,
  "fix_patched_reproduced": false,
  "fix_verdict": "fix.diff applied to guest /usr/src, incremental make nativekernel (BUILD2_RC=0), rebooted into kernel #2 Sat Oct 17 16:28:39 UTC 2026. Identical matrix and identical tap injection: baseline C5 [1] / C4 deadbeef123 / C6 deadbee and live ARP log (0x1) become C5 1234 / C4 deadbeef1234 / C6 deadbeef and live ARP log (0x1234); sep!=NULL control de-ad-be-ef byte-identical to stock; the loop still requires outlen >= 3 before each group so the NUL always lands at index <= outlen-1 (no overflow introduced; checked against if_ether hexstr[6] and mly outlen=12 shapes). fix_patched_reproduced=false means the BAD behavior is gone, which is the success criterion.",
  "fix_evidence": "arp_e2e.log vs arp_e2e_fixed.log (packet-level A/B: (0x1) -> (0x1234)); fix_run.log (patched matrix); fix_build.log (BUILD2_RC=0); fix.diff sha256 745fdae526ade23b4c34f118af06eeec2be0f312dd087f8859fce09270bfacd3 (git apply --check clean against sys/)"
}
