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)
PoC verification
Evidence pack
findings/poc/DF-5336 Β· 16 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 PoCrun.log,run.2.log,run.3.logβ stock kernel, 3 identical runsrun.patched.logβ fixed kernelfix.diffβ git-apply-able fix (validated in-guest)fix_build.log,build.logβ kernel + KLD build outputenv.txtβ guest uname, cc, osreldate, symbol-table excerptVERDICT.md,manifest.json,verdict.json
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):
- Pads
db_Grp7_11_{000,001,010,111}to 8 entries with"". - 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
- KLD build on stock guest β clean build, first try.
kldloadrun 1 β all 6 OOB vectors exhibited, 5/5 controls correct.- Runs 2 and 3 β byte-identical (determinism for the leak claim).
- 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
fixedfixed
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
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.
No comments yet.