DF-3360 / disassembly-analysis.txt
Frame layout of pf_normalize_tcpopt in the SHIPPED /boot/kernel/pf.ko
(DragonFly 6.5-DEVELOPMENT #0, INVARIANTS, gcc80 -O2):
push rbp; mov rsp,rbp; push r15..rbx; sub $0x58,rsp
opts[] = rbp-0x58 .. rbp-0x30 (40 bytes, filled by pf_pull_hdr
via `lea -0x58(%rbp),%rdx`)
rbp-0x30..rbp-0x28 = 8-byte ALIGNMENT PADDING (no instruction in the
function references -0x30(%rbp))
rbp-0x28..rbp = saved rbx, r12, r13, r14, r15
The unbounded access is compiled in with no guard:
270a5: cmp $0x2,%edx ; opt == TCPOPT_MAXSEG
2708a: movzbl 0x1(%r15),%r11d ; optlen = optp[1]
2709c: cmp %ebx,%r11d ; jg break ; optlen > cnt -> break (2 <= 2 passes)
270aa: movzwl 0x2(%r15),%esi ; *mss <-- OOB READ when r15 = opts+38
...
270f2: mov %ax,0x2(%r15) ; *mss = htons(max_mss) <-- OOB WRITE
With optp == opts+38 (38 bytes of filler + MSS option with declared
length 2), mss == opts+40 == rbp-0x30: the 2-byte read/write lands in
the padding slot. On this exact binary the OOB is therefore inert
(reads stale stack padding; conditional write hits padding only).
Any compiler/optimization change that places a live local or spill at
rbp-0x30 (or opts at a different offset) turns this into a genuine
2-byte stack overwrite adjacent to saved registers.
Source-level reachability: cnt == 40 requires th_off == 15; the tile
[38 bytes filler][0x02 0x02] passes every check in the walk
(optlen=2 is >= 2 and <= cnt=2), so the case body executes with
mss one-past-the-end. Verified at runtime: 300k crafted packets through
`scrub in on lo0 all max-mss {64,1}` (rule evaluations climbed to
416k+, guest stable) -- the path runs on every packet; only the
frame layout prevents observable corruption in this build.