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)
PoC verification
Evidence pack
findings/poc/DF-3452 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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:
- The socket-option path is dead:
ip_fw3_ctl_sync_ptr(the function pointer that routes IP_FW_SYNC_* socket options toip_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 inip_fw3_ctl()(ip_fw3.c:1124-1128) checks it and silently does nothing. Contrastip_fw3_ctl_state_ptr(assigned at ip_fw3_state.c:699) andip_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. - The send path is dead:
ipfw_sync_send_state_prtis 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.
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
- 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) andip_fw3_ctl_table_ptr(ip_fw3_table.c:608) ARE assigned by their modevent hooks β sync is dead by omission. - Send path dead (source-grep):
ipfw_sync_send_state_prtis 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_testableConfirmed 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.
No comments yet.