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

Uninitialized kernel stack transmitted in ipfw3 sync datagrams: ip_fw3_sync_send_state fills 3 of 9 fields of a stack struct and ships all of it over UDP (LATENT β€” subsystem dead by omission)

Summary

ip_fw3_sync_send_state() declares struct cmd_send_state cmd on the kernel stack, initializes only cmd.type/cmd.cpu/cmd.hash, then memcpy's the ENTIRE struct into an mbuf and sosend's it to every configured sync edge. The uninitialized remainder - struct ipfw_flow_id flow (dst_ip,src_ip,dst_port,src_port,proto,flags), expiry, lifetime, rulenum (~28 bytes + padding) - is kernel stack garbage disclosed to the UDP peer on every synced state entry. Currently unreachable (dead twice over: ip_fw3_ctl_sync_ptr never assigned - verified on-guest as silent rc=0 no-op; send_state_prt has no caller). Becomes a remote kernel-stack info disclosure (KASLR/canary-defeat aid) the moment anyone wires the pointer up - ipfw3_basic.ko ships in /boot/kernel. Same LATENT family as DF-0700..0706; distinct vulnerability class (info leak) and distinct fix. Not runtime-triggerable (dead code - proven); deadness check captured in pack. Fix: bzero before populating (better: populate the real fields).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3452 Β· 6 files
FileTypeDescriptionSize
df3452_dead.c β€” 1.8 KB view raw
README.md β€” 2.6 KB ↓ raw
VERDICT.md β€” 2.1 KB ↓ raw
manifest.json β€” 1.0 KB view raw
verdict.json β€” 3.5 KB view raw
fix.diff β€” 341 B view raw

DF-3452 β€” Uninitialized kernel stack transmitted in ipfw3 sync datagrams (ip_fw3_sync_send_state)

What

sys/net/ipfw3_basic/ip_fw3_sync.c:437-467 β€” ip_fw3_sync_send_state() declares struct cmd_send_state cmd; on the kernel stack and initializes ONLY cmd.type, cmd.cpu, cmd.hash (lines 448-450). The remaining fields β€” struct ipfw_flow_id flow (14 bytes: dst_ip, src_ip, dst_port, src_port, proto, flags), uint32_t expiry, uint16_t lifetime, int rulenum β€” are uninitialized stack garbage. The full struct is then memcpy'd into an mbuf (memcpy(m->m_data, &cmd, len), line 452) and sent to every configured sync edge over UDP (so_pru_sosend(fw3_sync_ctx.centre_socks[i], ...), lines 458-465).

If the ipfw3 state-sync sender were wired up, every synced state entry would disclose ~28 bytes of kernel stack to the UDP peer(s) configured in fw3_sync_ctx.edges β€” a remote kernel-stack info disclosure (CWE-908 / CWE-209), reusable for KASLR-style defeat of stack canaries / pointer obfuscation on the syncing host.

Reachability (why this is latent)

The function is currently DEAD CODE, twice over:

  1. The socket-option path is dead: ip_fw3_ctl_sync_ptr (the function pointer that routes IP_FW_SYNC_* socket options to ip_fw3_ctl_sync_sockopt) is declared NULL in sys/net/ipfw3/ip_fw3.c:133 and is NEVER assigned anywhere in the tree β€” the sync case in ip_fw3_ctl() (ip_fw3.c:1124-1128) checks it and silently does nothing. Contrast ip_fw3_ctl_state_ptr (assigned at ip_fw3_state.c:699) and ip_fw3_ctl_table_ptr (ip_fw3_table.c:608), which ARE assigned by the same module family β€” sync is dead by omission, not by design.
  2. The send path is dead: ipfw_sync_send_state_prt is assigned at MOD_LOAD (ip_fw3_sync.c:474) but has no caller anywhere in sys/.

So nothing can reach ip_fw3_sync_send_state() today. The defect matters the moment anyone wires the pointer up (the module exists and is shipped in /boot/kernel/ipfw3_basic.ko). This matches the LATENT classification of the pass-1 findings DF-0700..DF-0706 for the same file.

Fix

fix.diff in this directory zeroes the struct before filling the three intended fields.

Verification

Code-level: certain (the initialization gap is directly visible at ip_fw3_sync.c:443-452). Runtime: not reproducible β€” the code is dead (see VERDICT.md); the on-guest check df3452_dead.c demonstrates the sockopt no-op. No QEMU runtime verification is meaningful for unreachable code; status not_reproduced with impact none (latent). Per contract, Low/LATENT findings skip Phase V.

VERDICT.md
↓ download raw

DF-3452 VERDICT β€” uninitialized stack in ip_fw3_sync_send_state (LATENT)

Reproduced? No β€” dead code (verified), defect certain at code level

The initialization gap is directly visible at ip_fw3_sync.c:443-452: struct cmd_send_state cmd; on the stack; only .type (448), .cpu (449), .hash (450) assigned; memcpy(m->m_data, &cmd, len) with len = sizeof(struct cmd_send_state) (~48 bytes incl. flow 14B, expiry 4B, lifetime 2B, rulenum 4B + padding) ships the rest as kernel stack garbage to every sync peer over UDP (ip_fw3_sync.c:458-465).

Reachability β€” dead, proven two ways

  1. Sockopt path dead (on-guest verified): ip_fw3_ctl_sync_ptr (sys/net/ipfw3/ip_fw3.c:133) is never assigned anywhere in the tree; the IP_FW_SYNC_* case in ip_fw3_ctl() (ip_fw3.c:1124-1128) checks the pointer and does nothing. On-guest: setsockopt(IPPROTO_IP, IP_FW_X, {opcode=IP_FW_SYNC_SHOW_STATUS}) and {opcode=IP_FW_SYNC_CENTRE_TEST} both returned rc=0 with no dmesg output and no state change β€” a live handler would have kprintf'd "ipfw3sync: centre not running" (ip_fw3_sync.c:324-326). Contrast: ip_fw3_ctl_state_ptr (ip_fw3_state.c:699) and ip_fw3_ctl_table_ptr (ip_fw3_table.c:608) ARE assigned by their modevent hooks β€” sync is dead by omission.
  2. Send path dead (source-grep): ipfw_sync_send_state_prt is assigned at MOD_LOAD (ip_fw3_sync.c:474) and called nowhere.

Impact if wired

Each synced state entry leaks ~28 bytes of kernel stack over UDP to the configured sync edges β€” remote kernel-stack info disclosure to the sync peer (CWE-908/CWE-209). Same LATENT family as pass-1 DF-0700..DF-0706.

Fix

fix.diff: one-line bzero(&cmd, sizeof(cmd)) before populating the intended fields. Runtime fix validation meaningless (dead code).

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-3452

Confirmed kernel references

Detail

Evidence (decisive lines)

['ip_fw3_sync.c:443-452: struct cmd_send_state cmd on stack, three fields set, memcpy of the whole struct into the outgoing mbuf', 'ip_fw3_sync.c:458-465: datagram sent to every fw3_sync_ctx centre socket', 'ip_fw3_sync.h:93-102: struct cmd_send_state layout (flow: struct ipfw_flow_id, expiry, lifetime, rulenum left uninitialized)', 'ip_fw3.c:133 + :1124-1128: ip_fw3_ctl_sync_ptr never assigned - sync sockopts dead; contrast ip_fw3_state.c:699 / ip_fw3_table.c:608 which do assign their pointers', 'df3452_dead.c + on-guest output: SET SYNC_SHOW_STATUS rc=0, SET SYNC_CENTRE_TEST rc=0, no kernel messages - silent no-op, dead path confirmed']

PoC changes

df3452_dead.c written to prove the sockopt deadness on-guest (setsockopt IP_FW_X with sync opcodes returns success while doing nothing).

Verified recommended fix

bzero cmd before populating the three fields (fix.diff), or populate flow/expiry/lifetime/rulenum from the synced state

Verdict

Code-level defect is certain and directly visible: ip_fw3_sync_send_state() (sys/net/ipfw3_basic/ip_fw3_sync.c:437-467) initializes only cmd.type/cmd.cpu/cmd.hash of the on-stack struct cmd_send_state (:448-450) and then memcpy's the entire struct - including the uninitialized struct ipfw_flow_id flow (14B), expiry, lifetime and rulenum - into an mbuf that is sent as a UDP datagram to every sync edge (:452, :458-465): ~28 bytes of kernel stack per synced state entry to a network peer (CWE-908). NOT reproduced at runtime because the code is dead twice over, verified both in-source and on the guest: (1) ip_fw3_ctl_sync_ptr (ip_fw3.c:133) is never assigned, so all IP_FW_SYNC_* sockopts are silent no-ops - on-guest setsockopt(IP_FW_X, SYNC_SHOW_STATUS) and (SYNC_CENTRE_TEST) both returned rc=0 with no dmesg output and no state change, while a live handler would kprintf ('centre not running', ip_fw3_sync.c:325); (2) ipfw_sync_send_state_prt is assigned at MOD_LOAD (:474) but has no caller anywhere in sys/. Latent, same class as the pass-1 DF-0700..DF-0706 LATENT findings for this dead subsystem; becomes a live remote kernel-stack disclosure the moment anyone wires the pointer up. Low severity (latent), Phase V runtime skipped per contract.