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

v1 twin of DF-3496: NGM_FEC_ADD_IFACE/DEL_IFACE treat msg->data as a NUL-terminated interface name with no arglen validation -> ifunit() strcmp and kprintf %s walk past the ng_mesg allocation

Summary

rcvmsg copies msg->data to ifname with zero arglen/NUL checks; ifunit() compares via strcmp over the whole data area and the error paths kprintf %s - with a non-NUL-terminated allocation both read unboundedly past the ng_mesg heap allocation. Bounded-by-first-mismatch heap OOB read from a control message (root-gated). Fix: arglen>=2 && memchr NUL check before use. [VERIFIED pass2: reproduced on ctor-repaired harness - arglen 8 prints 4 groomed bytes past the allocation; stock module cannot be tested kfree NULL ctor panic from gcc-UB-deleted guard with disassembly and instrumented proof; EINVAL fix A/B]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3506 Β· 11 files
FileTypeDescriptionSize
README.md β€” 663 B ↓ raw
VERDICT.md β€” 3.1 KB ↓ raw
env.txt β€” 309 B view raw
fec-sequence.log β€” 5.7 KB view raw
fechg.c β€” 37.0 KB view raw
fix.diff β€” 1.1 KB view raw
manifest.json β€” 952 B view raw
ng_fec.harness.c β€” 30.6 KB view raw
panic.txt β€” 13.4 KB view raw
run.log β€” 910 B view raw
verdict.json β€” 4.0 KB view raw

DF-3506 - v1 ng_fec rcvmsg OOB iface-name read (+ stock-module ctor panic)

Build: stage fechg.c as /usr/src/sys/netgraph/fec/ng_fec.c; make; kldload ./ng_fec.ko Run: sysctl kern.fechg.op=create; sysctl kern.fechg.op='oob 8 4141414141414141' Expected (observed): dmesg 'fec0: tried to add iface AAAAAAAAXXXX' - the %s read 4 bytes past the 8-byte message data into groomed adjacent heap. STOCK MODULE: panics 'trying to free NULL pointer' at node creation (2/2 boots) - gcc deleted the ng_fec_units NULL guard (bcopy(NULL,..,0) UB).

Kernel: DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

VERDICT.md
↓ download raw

DF-3506 - VERDICT

status: reproduced | reproduced: 1 | impact: leak | confidence: certain

REACHABILITY DISCOVERY THAT GATES ALL FOUR v1-fec FINDINGS: the shipped stock ng_fec.ko cannot create a node at all - ng_fec_constructor deterministically panics 'trying to free NULL pointer' in _kfree on the FIRST node creation (reproduced 2/2 fresh boots, boot.log evidence). Root cause (disassembly + instrumented rebuild on the guest): gcc deletes the 'if (ng_fec_units != NULL)' guard before kfree() in ng_fec_get_unit() because the preceding bcopy(ng_fec_units, ..., 0) passes NULL as a pointer argument - undefined behavior that lets the compiler prove the pointer non-NULL; the instrumented module printed 'FECDBG: ctor enter ng_fec_units=0 len=0' followed by 'FECDBG: get_unit freeing old units 0' - the guard taken on a NULL value, proving the compiler-eliminated check. This panic is not INVARIANTS-gated (plain NULL check in _kfree), so the module is dead on ANY kernel config; the reported rcvmsg/addport/delport/tick paths are unreachable on stock bits. VERIFICATION ON A MINIMALLY-REPAIRED HARNESS MODULE (only fix: skip the len==0 bcopy; rcvmsg/addport/ifunit verbatim, driven from a sysctl context because control messages additionally hit the INVARIANTS-only 'ifnet lock in netisr' assert): with arglen=8 and data 'AAAAAAAA' (no NUL) the kernel log shows 'fec0: tried to add iface AAAAAAAAXXXX' - the %s/strcmp walk read FOUR BYTES past the 8-byte message data into the groomed adjacent heap (same-handler groom of 12 'X' bytes for deterministic magazine reuse). With arglen=4 'CCCC' the read of the NUL terminator itself is provably out of bounds (in-bounds bytes are all 'C'). Bounded-by-first-zero heap over-read, root-gated; fault-grade only at slab/page boundaries; dmesg disclosure of adjacent heap content. FIX VALIDATED A/B on the stock kernel: with fix.diff applied (arglen>=2 + NUL required, same ctor repair), the identical attack with CONFIRMED chunk reuse (oob groom=...msg=... same=1 - the message landed on the groomed chunk) returns EINVAL(22) and prints NO 'tried to add' line at all - the string walk never starts; baseline prints AAAAAAAAXXXX. panic.txt (fresh clean boot) captures the stock-module constructor panic.

Kernel references

Evidence

fec-sequence.log line 'tried to add iface AAAAAAAAXXXX' (4-byte overread visible), fec-boot-final.log (stock-module ctor panic), fechg.c (harness: verbatim rcvmsg/addport + ctor-UB fix, disclosed), FECDBG instrumented prints

validate arglen>=2 and memchr(msg->data,'\0',arglen) before treating msg->data as a string in NGM_FEC_ADD_IFACE/DEL_IFACE (also fix the get_unit bcopy UB so the module can build).

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

not_testable
↓ fix.diffper-fix-DF-3506

Confirmed kernel references

Detail

Evidence (decisive lines)

fec-sequence.log line 'tried to add iface AAAAAAAAXXXX' (4-byte overread visible), fec-boot-final.log (stock-module ctor panic), fechg.c (harness: verbatim rcvmsg/addport + ctor-UB fix, disclosed), FECDBG instrumented prints

PoC changes

harness module built from pristine ng_fec.c with (a) constructor UB fix (skip len==0 bcopy) so a node can exist at all, (b) appended sysctl driver calling verbatim ng_fec_rcvmsg from a non-netisr context (INVARIANTS ifnet_lock assert otherwise panics first), (c) same-op groom for deterministic OOB visibility

Verified recommended fix

validate arglen>=2 and memchr(msg->data,'\0',arglen) before treating msg->data as a string in NGM_FEC_ADD_IFACE/DEL_IFACE (also fix the get_unit bcopy UB so the module can build).

Verdict

REACHABILITY DISCOVERY THAT GATES ALL FOUR v1-fec FINDINGS: the shipped stock ng_fec.ko cannot create a node at all - ng_fec_constructor deterministically panics 'trying to free NULL pointer' in _kfree on the FIRST node creation (reproduced 2/2 fresh boots, boot.log evidence). Root cause (disassembly + instrumented rebuild on the guest): gcc deletes the 'if (ng_fec_units != NULL)' guard before kfree() in ng_fec_get_unit() because the preceding bcopy(ng_fec_units, ..., 0) passes NULL as a pointer argument - undefined behavior that lets the compiler prove the pointer non-NULL; the instrumented module printed 'FECDBG: ctor enter ng_fec_units=0 len=0' followed by 'FECDBG: get_unit freeing old units 0' - the guard taken on a NULL value, proving the compiler-eliminated check. This panic is not INVARIANTS-gated (plain NULL check in _kfree), so the module is dead on ANY kernel config; the reported rcvmsg/addport/delport/tick paths are unreachable on stock bits. VERIFICATION ON A MINIMALLY-REPAIRED HARNESS MODULE (only fix: skip the len==0 bcopy; rcvmsg/addport/ifunit verbatim, driven from a sysctl context because control messages additionally hit the INVARIANTS-only 'ifnet lock in netisr' assert): with arglen=8 and data 'AAAAAAAA' (no NUL) the kernel log shows 'fec0: tried to add iface AAAAAAAAXXXX' - the %s/strcmp walk read FOUR BYTES past the 8-byte message data into the groomed adjacent heap (same-handler groom of 12 'X' bytes for deterministic magazine reuse). With arglen=4 'CCCC' the read of the NUL terminator itself is provably out of bounds (in-bounds bytes are all 'C'). Bounded-by-first-zero heap over-read, root-gated; fault-grade only at slab/page boundaries; dmesg disclosure of adjacent heap content. FIX VALIDATED A/B on the stock kernel: with fix.diff applied (arglen>=2 + NUL required, same ctor repair), the identical attack with CONFIRMED chunk reuse (oob groom=...msg=... same=1 - the message landed on the groomed chunk) returns EINVAL(22) and prints NO 'tried to add' line at all - the string walk never starts; baseline prints AAAAAAAAXXXX. panic.txt (fresh clean boot) captures the stock-module constructor panic.