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.
Recommended fix
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)
PoC verification
Evidence pack
findings/poc/DF-2810 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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
Why (root cause, sys/kern/link_elf.c)
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).
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
fixedPatched 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']
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).
No comments yet.