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

db_disasm Grp7 mod==3 dispatch: inner rm-field index OOB-reads past the 2/4/5/7-entry db_Grp7_11_* sub-tables (no REX needed)

Summary

For 0f 01 /r mod==3 the inner rm index (0..7) is unbounded but the sub-tables have 5/4/7/2 entries: rm past each length reads an adjacent-.rodata const char* which db_printf %s prints - guest-verified 3/3 via KLD driving db_disasm (0f 01 ea -> 'xgetbv', 0f 01 ef -> 'xend' - entries of the ADJACENT table, nm-proven adjacency; plus (null)/empty padding reads). Distinct index+trigger from DF-1032/1033/2360 (no REX, no outer f_reg abuse) - fixes bounding only f_reg leave every vector here live. DDB-gated (x/i + panic auto-disassembly of attacker-influenced memory): adjacent-.rodata disclosure to the debugger console + layout-dependent wild %s deref. Pad-to-8 + bound-and-NULL-check fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5336 Β· 16 files
FileTypeDescriptionSize
df5336_oob.c β€” 2.6 KB view raw
Makefile β€” 60 B ↓ download
build.sh β€” 102 B view raw
run.sh β€” 135 B view raw
build.log β€” 836 B view raw
run.log β€” 2.8 KB view raw
run.2.log β€” 944 B view raw
run.3.log β€” 1.4 KB view raw
run.patched.log β€” 844 B view raw
fix.diff β€” 1.1 KB view raw
fix_build.log β€” 5.6 KB view raw
env.txt β€” 400 B view raw
README.md β€” 3.9 KB ↓ raw
VERDICT.md β€” 5.4 KB ↓ raw
manifest.json β€” 1.4 KB view raw
verdict.json β€” 3.7 KB view raw

DF-5336 β€” db_disasm Grp7_11 sub-table inner-index OOB read

What

sys/cpu/x86_64/misc/db_disasm.c:1275:

i_name = db_Grp7_11[f_reg(rex, regmodrm)][regmodrm &0x7];

When disassembling 0f 01 /r with mod==3 (regmodrm >= 0xC0), the inner index (regmodrm & 0x7, the rm field, 0..7) selects from the sub-tables db_Grp7_11_{000,001,010,111} which have only 5, 4, 7 and 2 entries (db_disasm.c:174-211). rm values beyond each table's length read a const char * from adjacent kernel .rodata, which is then printed by db_printf("%s", i_name) at db_disasm.c:1303.

No REX prefix is required β€” unlike the known DF-1033 β€” and the defect is on a different index than DF-1032 (outer f_reg into the 6-entry db_Grp7_11 array) and DF-2360 (NULL db_Grp7_11[3]/[4] + REX wild derefs). A fix that only bounds f_reg and NULL-checks (the known findings) leaves this bug fully reachable.

Trigger encodings (mod=3)

bytes reg rm table hit len result on stock kernel
0f 01 c5/c7 0 5/7 _000[5],_000[7] 5 (null) β€” reads 0 alignment padding past the array
0f 01 cc 1 4 _001[4] 4 "" β€” reads _000[0] (pointer to "")
0f 01 d7 2 7 _010[7] 7 (null) β€” reads padding
0f 01 ea 5 2 _111[2] 2 xgetbv β€” reads db_Grp7_11_010[0], a different table
0f 01 ef 5 7 _111[7] 2 xend β€” reads db_Grp7_11_010[5]

Adjacency proof (guest nm -n /boot/kernel/kernel):

ffffffff80d9d330 r db_Grp7_11_111   (+16 = 0x...d340)
ffffffff80d9d340 r db_Grp7_11_010   <- _111[2] lands exactly here

db_Grp7_11_111[2] == the qword at 0xffffffff80d9d340 == db_Grp7_11_010[0] == pointer to "xgetbv" β€” byte-for-byte what the disassembler printed.

Reachability / threat model

DDB disassembles kernel-memory content: x/i at the db> prompt (db_examine.c:178) and automatic instruction printing in panic backtraces (db_print_loc_and_inst, db_examine.c:259). Any path that disassembles attacker-influenced kernel memory (corrupted buffers, injected bytes, a jump into data) with the encodings above performs the OOB read. Impact: deterministic disclosure of adjacent kernel .rodata content on the DDB console (cross-table reads; with pointer-valued adjacency this discloses .rodata addresses β€” pointer-KASLR-relevant), and layout-dependent potential for db_printf("%s") to dereference a non-pointer qword (wild deref, debuger DoS). DDB access requires console/serial or an in-kernel Debugger() event, hence Low severity β€” consistent with DF-1032/DF-2360.

Build / run (on the DragonFly guest as root)

cd /root/df5336
export SYSDIR=/usr/src/sys
make            # builds df5336_oob.ko via bsd.kmod.mk
kldload ./df5336_oob.ko     # prints the test matrix to the console
kldunload df5336_oob

Expected (stock kernel): the 5 CTRL lines print real mnemonics (vmxoff/monitor/xtest/swapgs/rdtscp); the 6 OOB lines print (null), empty, xgetbv, xend β€” content that provably is not in the indexed sub-table.

Fix validation

fix.diff pads all four sub-tables to 8 entries and bounds the outer index (reg > 5 / NULL β†’ ""). After quickkernel + installkernel + reboot, the same PoC prints: controls unchanged, all 6 OOB lines empty (run.patched.log). The fix also incidentally hardens the outer-index cases of DF-1032/DF-2360 on this line.

Files

  • df5336_oob.c / Makefile / build.sh / run.sh β€” the PoC
  • run.log, run.2.log, run.3.log β€” stock kernel, 3 identical runs
  • run.patched.log β€” fixed kernel
  • fix.diff β€” git-apply-able fix (validated in-guest)
  • fix_build.log, build.log β€” kernel + KLD build output
  • env.txt β€” guest uname, cc, osreldate, symbol-table excerpt
  • VERDICT.md, manifest.json, verdict.json
VERDICT.md
↓ download raw

DF-5336 β€” VERDICT

Status: reproduced (3/3 deterministic runs, stock kernel) β€” fixed by fix.diff (validated in-guest). Impact: leak (deterministic OOB read of adjacent kernel .rodata, cross-table content + padding disclosed on the DDB console; layout-dependent wild-deref potential via db_printf("%s")). Not a crash on this build; not privilege escalation (DDB-gated attack surface).

Root cause (line-accurate)

sys/cpu/x86_64/misc/db_disasm.c:1273-1277:

else if (ip->i_extra == db_Grp7) {
     if((regmodrm & 0xC0) == 0xC0) {
        i_name = db_Grp7_11[f_reg(rex, regmodrm)][regmodrm &0x7];   /* :1275 */
        i_mode = 0;
     }

The inner index regmodrm & 0x7 is the modrm rm field, 0..7, with no bound. The sub-tables it indexes (db_disasm.c:174-211) have 5 (db_Grp7_11_000), 4 (_001), 7 (_010) and 2 (_111) entries. For reg ∈ {0,1,2,5} (non-NULL rows of db_Grp7_11) and rm past the table length, the compiler emits a plain out-of-bounds mov reading a const char * from whatever follows each array in .rodata. The stolen pointer is printed at db_disasm.c:1303 (db_printf("%s", i_name)).

Distinct from the known findings: DF-1032 is the outer index (f_regβ†’7 into the 6-entry db_Grp7_11); DF-1033 is REX.R-dependent f_reg 8..15; DF-2360 is the NULL rows 3/4 plus REX wild derefs. This finding needs no REX and no outer-index abuse: plain 0f 01 c5, 0f 01 cc, 0f 01 d7, 0f 01 ea … ef suffice. Fixing only the known findings (bound f_reg, NULL-check) leaves every vector here live.

Reproduction

KLD df5336_oob.c feeds crafted byte sequences directly to the in-kernel db_disasm() (global, declared ddb/ddb.h:93) on module load and prints banners around each call. Stock-kernel output (serial console, captured by vm.sh log), identical across 3 loads (run.log, run.2.log, run.3.log):

CTRL 0f 01 c4 -> vmxoff      (in-bounds _000[4], correct)
CTRL 0f 01 c8 -> monitor     (in-bounds _001[0], correct)
CTRL 0f 01 d6 -> xtest       (in-bounds _010[6], correct)
CTRL 0f 01 e8 -> swapgs      (in-bounds _111[0], correct)
CTRL 0f 01 e9 -> rdtscp      (in-bounds _111[1], correct)
OOB  0f 01 c5 -> (null)      (_000[5]: zero alignment padding past array)
OOB  0f 01 c7 -> (null)      (_000[7]: padding)
OOB  0f 01 cc -> <empty>     (_001[4]: pointer to "" β€” db_Grp7_11_000[0])
OOB  0f 01 d7 -> (null)      (_010[7]: padding past 7 entries)
OOB  0f 01 ea -> xgetbv      (_111[2]: db_Grp7_11_010[0] β€” WRONG TABLE)
OOB  0f 01 ef -> xend        (_111[7]: db_Grp7_11_010[5] β€” WRONG TABLE)

Adjacency proven from the guest symbol table (env.txt):

ffffffff80d9d330 r db_Grp7_11_111   (2*8 = 16 bytes -> ends 0x...d340)
ffffffff80d9d340 r db_Grp7_11_010   <- _111[2]/[7] read 0/40 bytes into this
ffffffff80d9d380 r db_Grp7_11_001   (4*8 = 32 -> ends 0x...d3a0)
ffffffff80d9d3a0 r db_Grp7_11_000   <- _001[4] reads _000[0] (pointer to "")

Every OOB output is explained exactly by the adjacent-memory read; no in-bounds entry could ever produce (null), xgetbv for a reg=5 encoding, or xend. The (null) prints show DragonFly kvprintf renders a NULL %s argument safely, which is why the padding reads do not crash on this build.

Why "leak" and not "panic"

On this toolchain the qwords past each sub-table are either 0 (padding) or valid .rodata string pointers, so the observable is disclosure of adjacent static-data content (and, when the adjacent qword is itself a table pointer, the printed bytes are raw kernel pointers). The wild-deref variant (adjacent non-pointer qword interpreted as char *) is real but layout-dependent β€” with a different linker order/toolchain the same code path dereferences arbitrary adjacent bytes. Either way the class is an unbounded kernel-memory OOB read driven purely by disassembled bytes.

Fix validation

fix.diff (generated mechanically; git apply verified on the host copy of sys/, then applied inside the guest's /usr/src):

  1. Pads db_Grp7_11_{000,001,010,111} to 8 entries with "".
  2. Bounds the outer index: reg > 5 || db_Grp7_11[reg] == NULL β†’ "" (also closes DF-1032's outer OOB and DF-2360's NULL rows on this line).

Flow: vm.sh reset with-src β†’ git apply fix.diff in /usr/src β†’ make quickkernel KERNCONF=X86_64_GENERIC && make installkernel (fix_build.log, KERNEL_FIX_BUILD_OK) β†’ reboot β†’ rebuild + reload the identical PoC (build.log).

Patched output (run.patched.log): all 5 CTRL lines unchanged (vmxoff/monitor/xtest/swapgs/rdtscp β€” no regression), all 6 OOB lines now print the in-table "" filler. Baseline bad behavior gone; 0f 01 ea flipping from xgetbv to empty on identical input also proves the patched kernel was actually running. Guest reset to the clean with-src snapshot afterwards.

Attempt log

  1. KLD build on stock guest β€” clean build, first try.
  2. kldload run 1 β€” all 6 OOB vectors exhibited, 5/5 controls correct.
  3. Runs 2 and 3 β€” byte-identical (determinism for the leak claim).
  4. Fix kernel build/boot + patched re-run β€” bug gone, controls intact.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

fixed

run.patched.log: after git apply fix.diff + quickkernel + installkernel + reboot, the identical PoC prints empty for all 6 OOB vectors (baseline xgetbv/xend/(null)/empty gone) while all 5 in-bounds controls (vmxoff/monitor/xtest/swapgs/rdtscp) are unchanged
↓ fix.diffper-fix-DF-5336

Confirmed kernel references

Detail

Exploit chain

attacker-influenced kernel memory containing 0f 01 ea..ef (or c5-c7/cc-cf/d7) reaches db_disasm via panic-backtrace auto-disassembly (db_print_loc_and_inst) or x/i at the db> prompt -> mod==3 Grp7 dispatch -> rm-field index past sub-table -> OOB const char* read from adjacent .rodata -> db_printf %s discloses its content (cross-table entries / raw pointer bytes / padding) on the console

Evidence (decisive lines)

run.log run.2.log run.3.log (stock kernel, 3 identical runs: OOB lines show '(null)', empty, xgetbv, xend; CTRL lines correct); env.txt (nm -n excerpt: db_Grp7_11_111 @0xffffffff80d9d330 +16 == db_Grp7_11_010 @0xffffffff80d9d340, byte-explaining xgetbv/xend); run.patched.log (all OOB vectors empty, controls unchanged); fix_build.log (KERNEL_FIX_BUILD_OK)

PoC changes

No seed existed; KLD authored fresh (calls global db_disasm() declared in ddb/ddb.h:93 on static byte vectors with kprintf banners; db_printf output observed on serial console via vm.sh log)

Verified recommended fix

Pad db_Grp7_11_{000,001,010,111} to 8 entries and bound/NULL-check the outer index in the mod==3 Grp7 dispatch (see fix.diff)

Verdict

Reproduced 3/3 on the stock guest kernel: db_disasm's Grp7 mod==3 dispatch (db_disasm.c:1275) indexes the 2/4/5/7-entry db_Grp7_11_ sub-tables with the unbounded rm field (regmodrm & 0x7), reading a const char from adjacent kernel .rodata and printing it via db_printf %s (:1303). Plain 0f 01 c5/cc/d7/ea/ef (no REX) printed (null)-padding, a foreign-table pointer, and the WRONG-TABLE mnemonics xgetbv/xend (db_Grp7_11_010[0]/[5], adjacency proven by nm: _111 ends exactly where _010 begins). Deterministic disclosure of adjacent .rodata to the DDB console; layout-dependent wild-deref potential if the adjacent qword is a non-pointer. Distinct index and trigger from known DF-1032 (outer f_reg), DF-1033 (REX.R), DF-2360 (NULL rows/REX wild). fix.diff (pad sub-tables to 8 + bound outer index/NULL-check) validated in-guest: quickkernel+installkernel+reboot, identical PoC prints empty for all OOB vectors with controls unchanged.