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

parse_dynamic accepts DT_REL/DT_RELA/DT_JMPREL/DT_HASH without DT_SYMTAB/DT_STRTAB β†’ NULL-base wild reads in symbol_name()/elf_lookup()/link_elf_lookup_symbol()

Field Value
ID DF-2810
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H
CWE CWE-476 (missing required-tag validation, CWE-20)
File sys/kern/link_elf.c
Lines 232-314 (consumers :697-698, :818, :824, :1016)
Area kern
Confidence certain
Discovered 2026-08-31
Pass 2 (GLM 5.3 second pass)
Bucket memcorrupt
Reported pending
Known CVE none
CVE match novel

Summary

parse_dynamic stores each DT_ tag independently with no cross-validation that the tables its consumers dereference unconditionally are present. A module supplying DT_REL/DT_RELSZ but no SYMTAB/STRTAB/HASH reaches relocate_file's failure path, which calls symbol_name() β†’ ref = ef->symtab(NULL) + symidx β†’ read at VA sizeof(Elf64_Sym)symidx. The DT_HASH-without-DT_SYMTAB variant reaches the same class via link_elf_lookup_symbol. The presence dimension left uncovered by the bounds fixes DF-0060/DF-0062. VERIFIED on the stock kernel: Fatal trap 12 ... fault virtual address = 0x18 ... Stopped at relocate_file+0x7b (0x18 == sizeof(Elf64_Sym)1). Patched: "Missing DT_STRTAB/DT_SYMTAB for relocations" ENOEXEC.

Post-loop presence check in parse_dynamic: if any of rel/rela/pltrel/pltrela/buckets is set while strtab or symtab is NULL, return ENOEXEC (fix.diff hunk 6).

Timeline

  • 2026-08-31 Discovered during pass-2 audit of link_elf.c (GLM 5.3); wild read reproduced + fix validated same run.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2810 Β· 10 files
FileTypeDescriptionSize
README.md β€” 1.9 KB ↓ raw
VERDICT.md β€” 1.8 KB ↓ raw
gen_exec_ko.py β€” 6.3 KB view raw
build.sh β€” 60 B view raw
run.sh β€” 335 B view raw
run.log β€” 601 B view raw
panic.txt β€” 2.1 KB view raw
env.txt β€” 512 B view raw
fix.diff β€” 4.1 KB view raw
run_fix.log β€” 1.4 KB view raw

DF-2810 β€” parse_dynamic accepts relocation/hash tags without DT_SYMTAB/DT_STRTAB β†’ NULL-base wild reads in symbol_name()/elf_lookup()

Build

python3 gen_exec_ko.py nulldyn-rel.ko nulldyn-rel

Run

scp -F dfbsd-qemu/config nulldyn-rel.ko dfbsd:/tmp/
dfbsd-qemu/vm.sh run_root 'kldload /tmp/nulldyn-rel.ko; echo RC=$?'
dfbsd-qemu/vm.sh log 30

Expected (stock kernel, verified 2026-09-01)

Fatal trap 12: page fault while in kernel mode
fault virtual address = 0x18
fault code      = supervisor read data, page not present
Stopped at      relocate_file+0x7b:     movl    (%rax),%esi

Expected (patched kernel)

kldload: Missing DT_STRTAB/DT_SYMTAB for relocations, RC=1, guest stays up

The module supplies DT_REL/DT_RELSZ (one R_X86_64_64 entry referencing symbol index 1) but NO DT_SYMTAB, DT_STRTAB, or DT_HASH. parse_dynamic (sys/kern/link_elf.c:232-314) stores each tag independently and never requires the tables that its consumers dereference unconditionally:

691:  symbol_name(elf_file_t ef, Elf_Size r_info)
697:      ref = ef->symtab + ELF_R_SYM(r_info);   /* NULL + 1 */
698:      return ef->strtab + ref->st_name;       /* read at 0x18 -> fault */

relocate_file (714-724) β†’ elf_reloc β†’ elf_lookup fails (symidx >= nchains==0, sys/kern/link_elf.c:994) β†’ the error path at :719 calls symbol_name() which dereferences the NULL symtab. Fault VA 0x18 = sizeof(Elf64_Sym)*1 + offsetof(Elf64_Sym, st_name) β€” matches exactly.

The sibling loaders validate this: link_elf_obj.c's elf_obj_lookup checks symidx >= ef->ddbsymcnt against the section-derived table and its relocate_file skips entries whose symbol is undefined; FreeBSD's rtld requires the tag set. Same class reachable via DT_HASH without DT_SYMTAB (link_elf_lookup_symbol:818 symp = ef->symtab + symnum with symtab==NULL once a bucket yields symnum != STN_UNDEF).

VERDICT.md
↓ download raw

DF-2810 VERDICT β€” reproduced (NULL-symtab wild read, panic at VA 0x18)

Status: reproduced. Stock guest kernel DragonFly 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026 X86_64_GENERIC, fresh vm.sh reset with-src before the run.

Evidence

Fatal trap 12: page fault while in kernel mode
cpuid = 5; lapic id = 5
fault virtual address  = 0x18
fault code             = supervisor read data, page not present
instruction pointer    = 0x8:0xffffffff80626deb
current process        = 833
Stopped at      relocate_file+0x7b:      movl    (%rax),%esi

(run.log, panic.txt)

0x18 is exactly sizeof(Elf64_Sym) * symidx(1) β€” the load of ref->st_name from ref = ef->symtab + ELF_R_SYM(r_info) with ef->symtab == NULL in symbol_name() (sys/kern/link_elf.c:696-698), called from the relocation-failure path of relocate_file (sys/kern/link_elf.c:719). The module legitimately supplies DT_REL/DT_RELSZ but omits DT_SYMTAB/DT_STRTAB/DT_HASH; parse_dynamic (sys/kern/link_elf.c:238-299) never cross-validates tag presence, and every consumer (symbol_name :697-698, elf_lookup :1016, link_elf_lookup_symbol :818/:824) dereferences the NULL bases with attacker-supplied indices.

Impact

Root-gated (kldload) crafted-module NULL-offset wild read β†’ kernel panic (DoS). Read offset is attacker-influenced (st_name up to 2^32-1 once a strtab-less DT_SYMTAB variant is used, or symidx via r_info) so it is the "missing required-tag validation" sibling of DF-0060/DF-0062, which fixed the bounds but not the presence dimension. Low per the root-supplied rubric.

Fix validation

fix.diff adds a post-loop presence check in parse_dynamic: any of rel/rela/pltrel/pltrela/buckets present without both strtab and symtab β†’ ENOEXEC at load. Patched kernel: kldload: Missing DT_STRTAB/DT_SYMTAB for relocations, RC=1, guest stays up. See run_fix.log.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Patched kernel: 'kldload: Missing DT_STRTAB/DT_SYMTAB for relocations', RC=1, guest up, no fault at 0x18 (run_fix.log).

['run_fix.log', 'fix_build.log']
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Tue Sep 1 15:20:35 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

crafted .ko -> kldload(root) -> parse_dynamic accepts REL without SYMTAB/STRTAB -> relocate_file -> elf_reloc fails lookup -> symbol_name() at :696-698 derefs NULL symtab -> wild read at attacker-influenced offset (symidx via r_info) -> panic.

Evidence (decisive lines)

["run.log: 'fault virtual address = 0x18 ... Stopped at relocate_file+0x7b: movl (%rax),%esi'", 'panic.txt', "run_fix.log: 'kldload: Missing DT_STRTAB/DT_SYMTAB for relocations', RC=1, guest up"]

PoC changes

authored fresh this pass (gen_exec_ko.py nulldyn-rel mode)

Verified recommended fix

post-loop presence check in parse_dynamic: any of rel/rela/pltrel/pltrela/buckets without both strtab and symtab -> ENOEXEC (fix.diff)

Verdict

parse_dynamic (link_elf.c:232-314) stores each DT_ tag independently and never requires DT_SYMTAB/DT_STRTAB to accompany relocation/hash tags; every consumer then dereferences the NULL bases with attacker indices. Reproduced: module with DT_REL/DT_RELSZ but no SYMTAB/STRTAB/HASH -> elf_lookup fails (symidx >= nchains==0) -> relocate_file's error path calls symbol_name() -> ref = ef->symtab(NULL)+1 -> read at VA 0x18 -> Fatal trap 12, 'Stopped at relocate_file+0x7b: movl (%rax),%esi'. VA 0x18 == sizeof(Elf64_Sym)*1 + offsetof(st_name) - exact match. Root-gated DoS; Low (presence dimension left unchecked by DF-0060/0062 which fixed bounds).