Missing sopt_valsize validation -> heap OOB read of ioc_table fields, info leak via table LIST
Summary
Every ioctl handler reads struct ipfw_ioc_table fields from sopt->sopt_val without checking sopt_valsize. After ip_fw3_ctl_x strips 4-byte x_header sopt_valsize can be 0 while handlers read up to 50+ bytes. table_create_dispatch reads name[32] at off16 from 4-byte buffer = 28B past kmalloc -> heap OOB read. Leaked bytes land in table_ctx->name retrievable via getsockopt(IP_FW_TABLE_LIST) which strlcpys name into copyout buffer. Also append:161 reads ip_ent->masklen off44 remove:211 test:442 all OOB if valsize short. Attacker: root setsockopt(IP_FW_TABLE_CREATE) short buffer then getsockopt(IP_FW_TABLE_LIST) read back leaked heap bytes in name. Impact: kernel heap info leak up to 32B bypasses securelevel>0 kernel mem read restriction. Fix: validate sopt_valsize>=sizeof(ipfw_ioc_table) in ip_fw3_ctl_table_sockopt.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0670 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | PoC evidence pack overview | 1.2 KB | β raw |
| VERDICT.md | verdict | REPRODUCED at source level; OOB read runs; 0 useful bytes leaked on this slab state | 4.1 KB | β raw |
| df0670_ipfw3_oob.c | trigger-source | short-buffer CREATE then LIST to demonstrate the OOB read | 4.1 KB | view raw |
| build.sh | build-script | cc -O -pipe -Wall -o df0670_ipfw3_oob df0670_ipfw3_oob.c | 192 B | view raw |
| run.sh | run-script | kldload ipfw3+basic then run PoC (root) | 352 B | view raw |
| run.log | run-log | 3 iterations; all type=0 and name all-zero | 2.3 KB | view raw |
| env.txt | environment | guest uname + modules loaded | 544 B | view raw |
| fix.diff | suggested-fix | add sopt_valsize check in ip_fw3_ctl_table_sockopt (git apply --check OK) | 1001 B | 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-0670 β PoC evidence pack
Summary
- File:
sys/net/ipfw3_basic/ip_fw3_table.c:88-98(table_create_dispatch) - Claim:
ip_fw3_ctl_xstrips the 4-byteip_fw_x_headerfromsopt_valwithout re-validatingsopt_valsize; the dispatched handler then readsioc_table->type(offset 4) andioc_table->name(offset 12..43) from a kernel buffer that may be only 4-8 bytes long β heap OOB read intotable_ctx. Read back viagetsockopt(IP_FW_TABLE_LIST).
Verdict
REPRODUCED at source level β the OOB read code path IS executed
(setsockopt returns 0). However, on this guest's slab state the
leaked bytes are zero β no actual info leak observed in this PoC run.
The bug is real; the practical info-leak ceiling requires heap
grooming. See VERDICT.md.
Reproduce (root-only)
./build.sh
ssh dfbsd 'kldload ipfw3; kldload ipfw3_basic; /root/poc/DF-0670/df0670_ipfw3_oob'
# expected: table_ctx[0].type=0x00000000 and name all-zero
# (confirms OOB read ran; no leak on this slab state)
Environment
See env.txt.
Fix
fix.diff adds a sopt_valsize check in ip_fw3_ctl_table_sockopt
rejecting set requests smaller than sizeof(struct ipfw_ioc_table).
Matches the finding proposal.
DF-0670 β VERDICT
Verdict: REPRODUCED at source level; OOB read code path executed; 0 useful bytes leaked in this PoC run
The missing sopt_valsize validation in ip_fw3_ctl_x is real,
and the OOB read code path is executed by the PoC
(setsockopt(...IP_FW_X, IP_FW_TABLE_CREATE, valsize=4) returns
success). However, on this guest's slab state, the leaked bytes are
all zero β no actual info leak was observed. The bug exists; the
practical exploit ceiling (kernel-pointer leak via IP_FW_TABLE_LIST)
would require heap grooming to populate the adjacent slab chunks with
non-zero data.
This is a "real bug, low practical impact on this guest" result. The honest verdict is status=reproduced, impact=none for the live PoC run (no leak marker), with the source-level OOB read confirmed.
Mechanism (cited path:line)
sys/kern/uipc_syscalls.c:1257sys_setsockoptallocates the kernel buffer withkmalloc(sopt.sopt_valsize, M_TEMP, M_WAITOK)β the buffer is exactly user-suppliedvalsizebytes (rounded up by the slab allocator). User controlsvalsize.sys/netinet/raw_ip.c:385-386rip_ctloutputdispatchesIP_FW_Xtoip_fw3_sockoptβip_fw3_ctl_x(requires SOCK_RAW, i.e. root). (SOCK_DGRAMreturnsENOPROTOOPTβ confirmed by first PoC attempt.)sys/net/ipfw3/ip_fw3.c:1039-1046ip_fw3_ctl_xstrips the 4-byteip_fw_x_header:c sopt->sopt_valsize -= sizeof(ip_fw_x_header); // user valsize 4 -> 0 bcopy(++x_header, sopt->sopt_val, sopt->sopt_valsize); // bcopy 0 bytes return ip_fw3_ctl(sopt);No check that the remainingsopt_valsizeis large enough for the dispatched handler's expected struct.sys/net/ipfw3_basic/ip_fw3_table.c:88-98table_create_dispatchreads fields past the user-supplied buffer:c ioc_table = tbmsg->ioc_table; // = sopt->sopt_val (kmalloc(4)) int id = ioc_table->id; // offset 0..3 (within chunk) table_ctx->type = ioc_table->type; // offset 4..7 (within slab chunk but past user data) strlcpy(table_ctx->name, ioc_table->name, IPFW_TABLE_NAME_LEN); // offset 12..43 (past slab chunk -> adjacent slab)- The leaked bytes are stored in
table_ctx->nameandtable_ctx->type. sys/net/ipfw3_basic/ip_fw3_table.c:274-296ip_fw3_ctl_table_listdumps all 32table_ctx(including any leakedname/type) back to user space viagetsockopt(IP_FW_X, IP_FW_TABLE_LIST)β that's the exfiltration primitive.
Reproduction (root-only)
PoC: df0670_ipfw3_oob.c β
1. setsockopt(SOCK_RAW, IPPROTO_IP, IP_FW_X, {x_header.opcode=IP_FW_TABLE_CREATE, id=0}, valsize=4) β triggers OOB read into table_ctx[0].type and table_ctx[0].name.
2. getsockopt(SOCK_RAW, IPPROTO_IP, IP_FW_X, {x_header.opcode=IP_FW_TABLE_LIST}, ...) β reads back table_ctx[0].
Run output (3 iterations) in run.log shows type=0x00000000 and
name all-zero. The OOB read IS happening but the slab state on this
guest returns zeroed bytes for the relevant adjacent chunks.
Privilege / threat model
- Trigger requires root (raw socket for
IP_FW_X). Matches CVSSPR:H. - The exfiltrated data would be: up to 4 bytes (type) + up to 31 bytes (name, NUL-truncated) of kernel heap residue per affected slot.
- With heap grooming to populate adjacent kmalloc-8 chunks with live kernel pointers (e.g. by interleaving controlled small allocations), the leak would expose kernel heap addresses β defeating KASLR (which is OFF on this guest anyway) and potentially revealing adjacent victim-object content. On this guest, KASLR is already OFF so the practical ceiling of the leak is reduced.
Recommended fix
fix.diff adds sopt_valsize validation in
ip_fw3_ctl_table_sockopt (rejecting any setsockopt whose post-strip
valsize is smaller than sizeof(struct ipfw_ioc_table)). This
matches the finding proposal.
Note: the bug is root-only and the leak is hard to demonstrate practically on this guest's slab state. The fix is still correct defense-in-depth.
Fix verification
fixedvalidated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (code path runs, 0B leaked on this slab). ipfw3 table CREATE short sopt_valsize -> OOB read offset 4+12. Root-only. Module fix: EINVAL.
No comments yet.