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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0670 Β· 12 files
FileTypeDescriptionSize
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
README.md readme PoC evidence pack overview
↓ download 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_x strips the 4-byte ip_fw_x_header from sopt_val without re-validating sopt_valsize; the dispatched handler then reads ioc_table->type (offset 4) and ioc_table->name (offset 12..43) from a kernel buffer that may be only 4-8 bytes long β†’ heap OOB read into table_ctx. Read back via getsockopt(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.

VERDICT.md verdict REPRODUCED at source level; OOB read runs; 0 useful bytes leaked on this slab state
↓ download raw

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)

  1. sys/kern/uipc_syscalls.c:1257 sys_setsockopt allocates the kernel buffer with kmalloc(sopt.sopt_valsize, M_TEMP, M_WAITOK) β€” the buffer is exactly user-supplied valsize bytes (rounded up by the slab allocator). User controls valsize.
  2. sys/netinet/raw_ip.c:385-386 rip_ctloutput dispatches IP_FW_X to ip_fw3_sockopt β†’ ip_fw3_ctl_x (requires SOCK_RAW, i.e. root). (SOCK_DGRAM returns ENOPROTOOPT β€” confirmed by first PoC attempt.)
  3. sys/net/ipfw3/ip_fw3.c:1039-1046 ip_fw3_ctl_x strips the 4-byte ip_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 remaining sopt_valsize is large enough for the dispatched handler's expected struct.
  4. sys/net/ipfw3_basic/ip_fw3_table.c:88-98 table_create_dispatch reads 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)
  5. The leaked bytes are stored in table_ctx->name and table_ctx->type.
  6. sys/net/ipfw3_basic/ip_fw3_table.c:274-296 ip_fw3_ctl_table_list dumps all 32 table_ctx (including any leaked name/type) back to user space via getsockopt(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 CVSS PR: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.

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

fixed

validated

see evidence pack
↓ fix.diffn/a (module-level)

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.