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)
PoC verification
Evidence pack
findings/poc/DF-3506 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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
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
- sys/netgraph/fec/ng_fec.c:1172
- sys/netgraph/fec/ng_fec.c:1177
- sys/netgraph/fec/ng_fec.c:345
- sys/netgraph/fec/ng_fec.c:348
- sys/netgraph/fec/ng_fec.c:272
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
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).
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_testableConfirmed 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.
No comments yet.