⬢ DragonFlyBSD Kernel Audit
DF-2343 / adjacency_codegen_addendum.txt
← back to finding ↓ download raw
DF-2343 codegen evidence addendum (recorded from the -O0 harness kernel,
/usr/obj/usr/src/sys/UMCSH/kernel.debug, build of Sun Oct 18 21:2x UTC;
the object was later overwritten by the fixed builds -- this transcript
is the preserved record; the panic RIPs in panic.txt match it exactly).

umcs7840_calc_baudrate @ 0xffffffff80a85636 (verbatim unit, -O0):
  +0x7b: 0f b6 45 ff          movzbl -0x1(%rbp),%eax     ; i
  +0x7f: 83 c0 01             add    $0x1,%eax           ; i + 1
  +0x82: 48 98                cltq
  +0x84: 8b 04 85 c0 a2 d9 80 mov    -0x7f265d40(,%rax,4),%eax
                                ^^^^^^^^^^^^^ = 0xffffffff80d99240
                                = &umcs7840_baudrate_divisors[0]
                                -> with i==8 this loads divisors[9]:
                                   the 1-ELEMENT OOB READ (umcs.c:1066)
  +0x8b: ba 00 00 00 00       mov    $0x0,%edx
  +0x90: f7 75 ec             divl   -0x14(%rbp)          ; divide by rate
                                -> rate==0: #DE, Fatal trap 18
                                   (panics 1/2/3 RIP = base+0x90 = ...56c6)
  +0x93: 89 c2                mov    %eax,%edx           ; *divisor = quotient

adjacency.txt (from the stock -O2 build of the same source) shows the
.rodata file image: divisors[0..8] = 0,115200,230400,403200,460800,
806400,921600,1572864,3145728 at 0xffffffff80d99240, and the 4 bytes at
+36 (divisors[9]) = 0x00000000 followed by zero padding -- the OOB load
reads 0 in this link, and the runtime instrumentation (scenario 1,
oob_dword=0x00000000) confirms the same value in the booted kernel.

Stock -O2 codegen (adjacency.txt, disassembly of the gcc8-generated
umcs7840_calc_baudrate.part.0): the loop-exhausted path (rate==0) is
compiled to
  +0x33: xor %eax,%eax              ; return 0 (success)
  +0x35: mov $0xffffff80,%r8d       ; clk = 8<<4
  +0x3b: mov %ax,(%rsi)             ; *divisor = 0
  ...   mov %r8b,(%r9)              ; *clk = 0x80
  ret
with NEITHER the divisors[9] load NOR the division executed: gcc8
exploited the divide-by-zero UB.  The only `div %edi` sits in the
bucket-found path where rate > divisors[i] >= 0, i.e. rate != 0.
Consequence on the shipping binary: pre_param(0) -> calc "succeeds"
with *divisor=0 -> `|| !divisor` -> EINVAL; no panic (run1.log,
two independent -O2 builds).