NULL-pointer dereference in rn_flush on never-created tables -> kernel panic via delete/flush ioctl and module unload
Summary
table_delete_dispatch:128-130 and table_flush_dispatch:245-248 call rn_flush(table_ctx->node,...) WITHOUT NULL check. table_ctx M_ZERO :570 so node=NULL until IP_FW_TABLE_CREATE type1|2 calls rn_inithead. Calling DELETE/FLUSH on in-bounds id never created -> rn_flush(NULL) -> deref head->rnh_walktree at radix.c:1341 -> panic. ip_fw3_table_fini_dispatch:580-586 loops all 32 tables rn_flush each -> kldunload ipfw3_basic after normal use (most slots never created) panics deterministically. Attacker: root setsockopt(IP_FW_TABLE_DELETE/FLUSH) in-bounds id never created. Impact: reliable kernel panic DoS. No mem corruption. Fix: if(node!=NULL) rn_flush.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0669 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | PoC evidence pack overview | 1.3 KB | β raw |
| VERDICT.md | verdict | REPRODUCED β kernel panic on never-created ipfw3 table; root-only trigger | 3.6 KB | β raw |
| df0669_ipfw3_null.c | trigger-source | setsockopt(IP_FW_X, IP_FW_TABLE_DELETE) on never-created id=0 | 2.4 KB | view raw |
| build.sh | build-script | cc -O -pipe -Wall -o df0669_ipfw3_null df0669_ipfw3_null.c | 180 B | view raw |
| run.sh | run-script | kldload ipfw3+basic then run PoC (root) | 554 B | view raw |
| run.log | run-log | test wrapper output incl panic signature | 1.9 KB | view raw |
| panic.txt | panic-signature | Fatal trap 12 page fault @ VA=0x28 = rnh_walktree offset, head=NULL | 1.5 KB | view raw |
| env.txt | environment | guest uname + kern.version + cc + sysctl + modules loaded | 781 B | view raw |
| fix.diff | suggested-fix | add NULL guards before rn_flush in 3 sites (git apply --check OK) | 1.2 KB | view raw |
| fix_build.log | build-log | single-fix ipfw3_basic.ko module build output (rc=0) | 1.7 KB | view raw |
| fix_run.log | run-log | patched-module PoC re-run: no panic / EINVAL returned | 1.8 KB | 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 |
DF-0669 β PoC evidence pack
Summary
- File:
sys/net/ipfw3_basic/ip_fw3_table.c:130(also:248,:583) - Claim:
rn_flush(table_ctx->node, ...)is called without a NULL check;table_ctx->nodeis NULL (M_ZERO init) untilIP_FW_TABLE_CREATErunsrn_inithead. Calling DELETE/FLUSH on an in-bounds table id that was never created, orkldunloadingipfw3_basicafter normal use, panics inrn_flushathead->rnh_walktreewithhead == NULL.
Verdict
REPRODUCED β kernel panic (root-only trigger). See VERDICT.md
and panic.txt.
Reproduce
./build.sh # builds df0669_ipfw3_null
# Run as root, e.g. via vm.sh (ipfw3 default-deny severs ssh, so the
# test runs locally and writes its output to /dev/console):
ssh dfbsd 'kldload ipfw3; kldload ipfw3_basic; /root/poc/DF-0669/df0669_ipfw3_null 74'
# Expected: Fatal trap 12 page fault @ VA=0x28; guest reboots (debugger_on_panic=0)
# or drops into DDB (debugger_on_panic=1).
Environment
See env.txt.
Fix
fix.diff adds if (table_ctx->node != NULL) guards before each of
the three vulnerable rn_flush calls (in table_delete_dispatch,
table_flush_dispatch, and the ip_fw3_table_fini_dispatch unload
loop). git apply --check passes. Matches the finding proposal.
DF-0669 β VERDICT
Verdict: REPRODUCED (kernel panic, root-only trigger)
The NULL-deref in rn_flush(table_ctx->node, ...) on a never-created
ipfw3 table is real and reproduces deterministically on the default
GENERIC kernel (#0 master DEV build, INVARIANTS ON). The trigger is
root-only (SOCK_RAW required to reach rip_ctloutput's IP_FW_X
case), matching the finding's PR:H CVSS vector β a root-loaded ipfw3
module can be panicked by a root setsockopt. With ipfw3 loaded at boot
(a realistic firewall config), any process holding a raw socket (root
in the default jail-less configuration) can panic the kernel.
Mechanism (cited path:line)
sys/net/ipfw3/ip_fw3.c:1038-1046ip_fw3_ctl_xstrips the 4-byteip_fw_x_headerfrom the setsockopt value viasopt_valsize -= sizeof(ip_fw_x_header); bcopy(++x_header, sopt_val, ...)and dispatches onx_header->opcode. Nosopt_valsizevalidation.sys/netinet/raw_ip.c:385-386routesIP_FW_Xsetsockopt on aSOCK_RAWsocket toip_fw3_sockoptβip_fw3_ctl_x. (Only raw sockets handleIP_FW_XβSOCK_DGRAMreturnsENOPROTOOPT.)sys/net/ipfw3_basic/ip_fw3_table.c:566-572ip_fw3_table_init_dispatchallocates the per-CPU table context withM_ZERO, so everytable_ctx->nodeisNULLuntilIP_FW_TABLE_CREATEcallsrn_initheadon it.sys/net/ipfw3_basic/ip_fw3_table.c:118-134table_delete_dispatchdoes no NULL check beforern_flush(table_ctx->node, flush_table_entry)at line 130.sys/net/radix.c:1341rn_flushderefshead->rnh_walktree. Withhead == NULLthis is a NULL+offset deref β page fault. (table_flush_dispatch:248has the same bug;ip_fw3_table_fini_dispatch:583loops all 32 tables throughrn_flush, so kldunloadingipfw3_basicafter normal use β when most slots are still NULL β panics deterministically too.)
Reproduction
PoC: df0669_ipfw3_null.c β issues
setsockopt(SOCK_RAW, IPPROTO_IP, IP_FW_X, {x_header.opcode=IP_FW_TABLE_DELETE, ioc_table.id=0}, valsize=48)
on a fresh-loaded ipfw3 with no tables created.
Run as root via test.sh (loads ipfw3+ipfw3_basic, then runs the PoC;
output and panic land in dfbsd-qemu/boot.log via /dev/console
because ipfw3's default-deny severs networking the moment it loads):
Fatal trap 12: page fault while in kernel mode cpuid = 0; lapic id = 0 fault virtual address = 0x28 fault code = supervisor read data, page not present instruction pointer = 0x8:0xffffffff8074946d stack pointer = 0x10:0xfffff8008d1f8960 frame pointer = 0x10:0xfffff8008d1f8980 current process = Idle panic: page fault
The fault virtual address 0x28 is the byte offset of
rnh_walktree within struct radix_node_head, confirming the panic
is at head->rnh_walktree with head == NULL (i.e. inside
rn_flush(NULL) from table_delete_dispatch:130).
The full untrimmed panic signature is in panic.txt.
Privilege / threat model
- Trigger requires root (raw socket). With ipfw3 enabled at boot by
the administrator (a realistic firewall config), any local process
with
PRIV_NETCTRL/root can DoS the kernel. - No memory corruption β pure NULL deref. Impact is reliable kernel
panic (DoS), not escalation. The CVSS vector
AV:L/AC:L/PR:H/...A:Hmatches: it is a root-only local DoS.
Recommended fix
fix.diff adds if (table_ctx->node != NULL) guards in
table_delete_dispatch, table_flush_dispatch, and the loop in
ip_fw3_table_fini_dispatch. The finding's proposed fix matches this.
Matches the finding proposal.
Fix verification
fixedvalidated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (live panic). rn_flush(NULL->node) on never-created ipfw3 table. Root-only SOCK_RAW. Module fix: NULL guards before rn_flush.
No comments yet.