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

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.

-   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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2812 Β· 9 files
FileTypeDescriptionSize
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)

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 undefined" and the module cannot load (this is what the PoC demonstrates); - on the 1-in-2^32 chance the low 32 bits of (ef->address + st_value) are zero, lookup "succeeds" without setting *result, and 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.

VERDICT.md
↓ download raw

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

fixed
baseline reproduced→ patch + rebuild →patched clean

Patched kernel: localsym loads (LOCAL_RC=0) and unloads; globalsym unchanged (no regression) - 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 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 undefined' -> load aborted (observed). If the truncated return were 0: uninitialized stack 'addr' written into the relocated module word.

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.