elf_lookup() STB_LOCAL branch returns the resolved address as the int error code and never writes *result β relocations against defined local symbols always fail (or write an uninitialized stack value)
| Field | Value |
|---|---|
| ID | DF-2812 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:N |
| CWE | CWE-628 (latent CWE-908) |
| File | sys/kern/link_elf.c |
| Lines | 1003-1007 (consumer elf_machdep.c:123-125) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-08-31 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | base:kern |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
The elf_lookup_fn contract is int-return = error, address via *result.
The global branch was converted correctly but the STB_LOCAL branch
still returns the address itself β a half-converted fossil of the old
Elf_Addr elf_lookup(...) API. Every relocation against a defined
STB_LOCAL symbol therefore fails with the truncated heap pointer taken
as an errno β ET_EXEC KLD modules with local relocs cannot load at all;
if the truncated value were 0 the consumer would write an uninitialized
stack value into the relocated module word. VERIFIED with an A/B pair
(byte-identical modules except one st_info bind): globalsym loads
RC=0 on stock; localsym fails RC=1 with "symbol s undefined" despite
the symbol being defined in-module; patched kernel loads both.
Recommended fix
- return ((Elf_Addr) ef->address + sym->st_value);
+ *result = (Elf_Addr) ef->address + sym->st_value;
+ return (0);
Timeline
- 2026-08-31 Discovered during pass-2 audit of link_elf.c (GLM 5.3); A/B reproduction + fix validated same run.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2812 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.8 KB | β raw | |
| VERDICT.md | β | 2.7 KB | β raw | |
| gen_exec_ko.py | β | 6.3 KB | view raw | |
| build.sh | β | 101 B | view raw | |
| run.sh | β | 365 B | view raw | |
| run.log | β | 551 B | view raw | |
| env.txt | β | 512 B | view raw | |
| fix.diff | β | 4.1 KB | view raw | |
| run_fix.log | β | 1.4 KB | view raw |
DF-2812 β elf_lookup() STB_LOCAL branch returns the resolved address as the int error code; *result never written β local-symbol relocations always fail (or write an uninitialized stack value)
Build
python3 gen_exec_ko.py globalsym.ko globalsym # control: STB_GLOBAL symbol python3 gen_exec_ko.py localsym.ko localsym # test: STB_LOCAL symbol
The two modules are byte-identical except for one field: the st_info bind of the single in-module symbol "s" (defined: st_shndx=1, st_value=0x80) referenced by one R_X86_64_64 relocation.
Run
scp -F dfbsd-qemu/config globalsym.ko localsym.ko dfbsd:/tmp/ dfbsd-qemu/vm.sh run_root 'kldload /tmp/globalsym.ko; echo GLOBAL_RC=$?; kldunload -n /tmp/globalsym.ko' dfbsd-qemu/vm.sh run_root 'kldload /tmp/localsym.ko; echo LOCAL_RC=$?' dfbsd-qemu/vm.sh log 10 # console shows the failure message
Expected / observed (stock kernel, verified 2026-09-01)
globalsym: GLOBAL_RC=0 (loads; kldstat shows mapsize 0x1000; unloads fine)
localsym : link_elf: symbol s undefined <- console
kldload: an error occurred while loading module ... (RC=1)
Expected (patched kernel)
localsym : LOCAL_RC=0 (the defined local symbol now resolves)
Why (root cause, sys/kern/link_elf.c)
elf_lookup_fn (sys/sys/linker.h:327) is
typedef int elf_lookup_fn(linker_file_t, Elf_Size, int, Elf_Addr *) β
int return = error code, address delivered via *result. The machine
relocation engine (sys/cpu/x86_64/misc/elf_machdep.c:123) does:
if (lookup(lf, symidx, 1, &addr))
return -1;
val = addr + addend; /* addr only set on the 0-return path */
link_elf.c's elf_lookup honors that contract on the global path (:1022) but the STB_LOCAL branch is a fossil of the pre-elf_machdep API:
1003: if (ELF_ST_BIND(sym->st_info) == STB_LOCAL) {
1005: if (sym->st_shndx == SHN_UNDEF || sym->st_value == 0)
1006: return (ENOENT);
1007: return ((Elf_Addr) ef->address + sym->st_value); /* address as errno */
1008: }
Consequences:
- kernel-heap pointer truncated to int != 0 β elf_reloc_internal treats it
as an error β every relocation against a defined STB_LOCAL symbol fails
β "link_elf: symbol val = addr +
addend in elf_machdep.c:125 writes an UNINITIALIZED STACK VALUE into
the relocated word of the module image.
The sibling loader is correct: link_elf_obj.c's elf_obj_lookup
(:1134-1166) writes *result = ...; return (0); and its relocate_file
skips locals entirely because link_elf_obj_reloc_local handles them via
the same correctly-converted helper.
DF-2812 VERDICT β reproduced (defined local symbol fails to resolve: "link_elf: symbol s undefined")
Status: reproduced. Stock guest kernel
DragonFly 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026 X86_64_GENERIC.
Evidence
Control module (STB_GLOBAL "s", one R_X86_64_64 reloc against it):
$ kldload /tmp/globalsym.ko; echo GLOBALSYM_RC=$? GLOBALSYM_RC=0 $ kldstat | tail -1 4 1 0xfffff80116944000 1000 globalsym.ko $ kldunload /tmp/globalsym.ko; echo UNLOAD_RC=0
(run.log) β the generator produces fully valid ET_EXEC KLD modules for link_elf_load_file, so the failure below is attributable solely to the bind byte.
Test module (byte-identical except st_info: STB_LOCAL "s"):
$ kldload /tmp/localsym.ko; echo LOCALSYM_RC=$? kldload: an error occurred while loading module /tmp/localsym.ko. ... LOCALSYM_RC=1 console: link_elf: symbol s undefined
(run.log) β the symbol is defined in the module itself (st_shndx=1, st_value=0x80) yet resolution fails.
Root cause
sys/kern/link_elf.c:1003-1008 β the STB_LOCAL branch of elf_lookup() returns the resolved kernel address as the function's int return value (the errno channel) and never writes the out-parameter:
return ((Elf_Addr) ef->address + sym->st_value); /* :1007 */
The contract (sys/sys/linker.h:327 typedef int elf_lookup_fn(...,
Elf_Addr *), consumer sys/cpu/x86_64/misc/elf_machdep.c:123-125) is
"nonzero return = failure; address via *result". The truncated heap
pointer is nonzero, so elf_reloc_internal reports failure β relocate_file
(sys/kern/link_elf.c:718-722) prints "symbol s undefined" and the load
aborts. If the truncated value happened to be 0, elf_machdep.c:125 would
instead compute val = addr + addend from an uninitialized stack addr
and store it into the module image (uninitialized-stack-value write into
kernel memory that the module then executes).
This is a half-converted fossil of the old Elf_Addr elf_lookup(...)
API; the global branch at :1022 was converted correctly, and
link_elf_obj.c's elf_obj_lookup (:1149-1150) shows the correct form
(*result = ...; return (0);).
Impact
Root-gated (kldload). Primary observable: ET_EXEC KLD modules with STB_LOCAL relocations cannot load at all (functional bug present since the elf_machdep conversion), with a latent uninitialized-stack-value write on the measure-zero path. Low severity; security interest is the uninit write and the fact that the failure message itself routes through symbol_name() (the DF-0062 unbounded-st_name surface).
Fix validation
fix.diff converts :1007 to *result = (Elf_Addr) ef->address +
sym->st_value; return (0);. Patched kernel: kldload /tmp/localsym.ko
returns RC=0 (loads, relocates the local symbol correctly, unloads) β
run_fix.log.
Fix verification
fixedPatched kernel: localsym loads (LOCAL_RC=0) and unloads; globalsym unchanged (no regression) - run_fix.log.
['run_fix.log', 'fix_build.log']
Confirmed kernel references
Detail
Exploit chain
crafted .ko with an R_X86_64_64 reloc referencing an STB_LOCAL symbol -> elf_lookup returns (int)(ef->address + st_value) != 0 -> elf_reloc_internal reports failure -> relocate_file prints 'symbol
Evidence (decisive lines)
["run.log: GLOBALSYM_RC=0 + kldstat line for globalsym.ko vs LOCALSYM_RC=1 + console 'link_elf: symbol s undefined' for localsym.ko", 'run_fix.log: LOCAL_RC=0 on the patched kernel (local symbol resolves; unloads cleanly)']
PoC changes
authored fresh this pass (gen_exec_ko.py globalsym/localsym A-B pair)
Verified recommended fix
convert :1007 to '*result = (Elf_Addr) ef->address + sym->st_value; return (0);' (fix.diff last hunk)
Verdict
elf_lookup's STB_LOCAL branch (link_elf.c:1003-1008) is a fossil of the pre-elf_machdep API: it returns the resolved address as the function's int error channel and never writes result, violating the elf_lookup_fn contract (sys/sys/linker.h:327; consumer sys/cpu/x86_64/misc/elf_machdep.c:123-125 'if (lookup(...)) return -1; val = addr + addend;'). Reproduced with a controlled A/B pair: byte-identical ET_EXEC modules differing only in st_info bind of the single defined, relocated symbol: globalsym loads (RC=0, kldstat shows it, unloads), localsym fails with console 'link_elf: symbol s undefined' and RC=1 despite the symbol being defined in-module (st_shndx=1, st_value=0x80). Latent hazard on the measure-zero low-32-bits-zero path: where receives an uninitialized stack value. Root-gated correctness bug; Low.
No comments yet.