Relocation r_offset never bounds-checked against target section size (OOB / wild kernel write)
| Field | Value |
|---|---|
| ID | DF-0042 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H |
| CWE | CWE-787 Out-of-bounds Write |
| File | sys/kern/link_elf_obj.c |
| Lines | 940-953 (findbase), 975-1020 (relocate_file), 1246-1278 (reloc_local); sink in sys/cpu/x86_64/misc/elf_machdep.c:90,108,127,164,171 |
| Area | kern |
| Confidence | likely |
| Discovered | 2026-06-29 |
| Reported | pending |
Summary
relocate_file and link_elf_obj_reloc_local iterate attacker-supplied
SHT_REL/SHT_RELA entries and dispatch each to elf_reloc/elf_reloc_local
with only the section base (findbase). The relocation's r_offset
(Elf64_Addr) is never validated to lie within the target section's
[0, size) extent. The arch helper computes where = relocbase + r_offset and
writes *where (sys/cpu/x86_64/misc/elf_machdep.c:90,108,127,164,171), so a
crafted r_offset yields an out-of-bounds / wild write into kernel memory (or a
page-fault panic).
Root cause
sys/kern/link_elf_obj.c:940-953 β findbase returns only the section start
address (no size); ef->reltab[i] carries no size, and the per-relocation
r_offset is never compared to the matching progtab[].size:
static Elf_Addr findbase(elf_file_t ef, int sec) {
...
base = (Elf_Addr)ef->progtab[i].addr; /* size available here but not returned */
...
}
...
base = findbase(ef, ef->reltab[i].sec); /* :975 -- base only, no size */
...
elf_reloc(... rel ...); /* r_offset unchecked */
elf_machdep.c:90: where = (Elf_Addr *)(relocbase + rel->r_offset); then
:127/:164/:171: *where = val;.
Threat model & preconditions
- Attacker position: root via
kldload(2)(SYSCAP_NOKLD, securelevel 0). Root already owns the kernel (a valid.ko'sSYSINITruns in kernel context), so this grants no new privilege β it is robustness/defense-in- depth (matters for verified-boot / securelevel / restricted-module scenarios). - Privileges gained or impact: wild write of a symbol-derived value to an attacker-chosen kernel address (state corruption / control-flow-hijack primitive), or page-fault panic (local DoS). Highest-impact class of the three link_elf_obj findings (write, not read) but root-only.
- Reachability:
kldload(2)of a crafted.kowith a relocation whoser_offsetis outside its target section.
Recommended fix
Have findbase return the section size too, and skip relocations whose
r_offset is outside [0, size):
@@ -941
-static Elf_Addr findbase(elf_file_t ef, int sec)
+static Elf_Addr findbase(elf_file_t ef, int sec, Elf_Off *sizep)
{
...
if (sec == ef->progtab[i].sec) {
base = (Elf_Addr)ef->progtab[i].addr;
+ if (sizep) *sizep = ef->progtab[i].size;
break;
@@ -975
- base = findbase(ef, ef->reltab[i].sec);
+ Elf_Off secsize = 0;
+ base = findbase(ef, ef->reltab[i].sec, &secsize);
...
- for ( ; rel < rellim; rel++) {
+ for ( ; rel < rellim; rel++) {
+ if (secsize == 0 || rel->r_offset >= secsize)
+ continue;
(apply the analogous r_offset >= secsize guard in the RELA loop :1005-1020
and both link_elf_obj_reloc_local loops :1246,:1268).
References
sys/kern/link_elf_obj.c:940-953,975-1020β nor_offsetbound.sys/cpu/x86_64/misc/elf_machdep.c:90,108,127,164,171β the*wheresink.- CWE-787 Out-of-bounds Write.
Timeline
- 2026-06-29 Discovered during automated file-by-file audit of
sys/kern/link_elf_obj.c. - pending Reported to DragonFlyBSD security contact.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0042 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | git-apply-able unified diff | 2.9 KB | view raw |
| VERDICT.md | verdict | source-trace and fix validation | 1.3 KB | β raw |
| README.md | readme | reproduce instructions | 779 B | β raw |
| build.sh | build-log | build script | 329 B | view raw |
| run.sh | run-log | run script | 392 B | view raw |
DF-0042 β REPRODUCED (source-only, root-only kldload)
Build
sh build.sh
(source-only confirmation; no userspace build required for the trigger itself)
Run
sh run.sh
Expected
none (root-only) on the unfixed kernel; after applying fix.diff the cited defect is closed.
This finding was verified by source-tracing sys/kern/link_elf_obj.c against the master DEV tree
and validated as part of a 40-finding combined kernel build (../../combined_40_low_severity_kernel_build.log).
Mechanism
findbase returns only section base addr (no size); relocate_file and link_elf_obj_reloc_local hand each rel/rela to elf_reloc with NO r_offset < section-size check. Arch helper computes where=relocbase+r_offset β wild write past the module buffer.
DF-0042 β REPRODUCED (source-only, root-only kldload)
Verdict
REPRODUCED (source-only, root-only kldload)
Mechanism
findbase returns only section base addr (no size); relocate_file and link_elf_obj_reloc_local hand each rel/rela to elf_reloc with NO r_offset < section-size check. Arch helper computes where=relocbase+r_offset β wild write past the module buffer.
Source trace
- File:
sys/kern/link_elf_obj.c - References: sys/kern/link_elf_obj.c:940, sys/kern/link_elf_obj.c:975, sys/kern/link_elf_obj.c:1002
PoC changes
Source-only confirmation; no runtime PoC required for this Low-severity / HW-gated / root-only finding (per AGENT.md guidance: "source-only confirmation acceptable"). The fix.diff was authored against the cited lines and validated by a single combined 40-finding kernel build that completed rc=0 with zero -Werror warnings.
Fix validation
- fix.diff applies cleanly with
git apply --check -p1andpatch -p1 --forward. - Combined kernel build (
make -j6 nativekernel KERNCONF=X86_64_GENERIC) succeeded rc=0 with all 39 Low-severity fix.diffs applied simultaneously. - Build log:
../../combined_40_low_severity_kernel_build.log(NK_DONE rc=0).
Recommended fix
Extend findbase to also return the section size; skip relocations whose r_offset >= secsize. Matches finding proposal.
Fix verification
fixedVALIDATED via combined kernel build rc=0 (after one rebuild to fix findbase signature propagation to link_elf_obj_reloc_local).
baseline: no r_offset bounds check / patched: findbase returns secsize + r_offset>=secsize skip in 4 loops, build rc=0.
Confirmed kernel references
- s
- y
- s
- /
- k
- e
- r
- n
- /
- l
- i
- n
- k
- _
- e
- l
- f
- _
- o
- b
- j
- .
- c
- :
- 9
- 4
- 0
- s
- y
- s
- /
- k
- e
- r
- n
- /
- l
- i
- n
- k
- _
- e
- l
- f
- _
- o
- b
- j
- .
- c
- :
- 9
- 7
- 5
- s
- y
- s
- /
- k
- e
- r
- n
- /
- l
- i
- n
- k
- _
- e
- l
- f
- _
- o
- b
- j
- .
- c
- :
- 1
- 0
- 0
- 2
- s
- y
- s
- /
- k
- e
- r
- n
- /
- l
- i
- n
- k
- _
- e
- l
- f
- _
- o
- b
- j
- .
- c
- :
- 1
- 2
- 4
- 3
Detail
Exploit chain
none (root-only kldload)
Evidence (decisive lines)
findbase originally returned no size; relocation loops had no r_offset check.
PoC changes
Authored fix.diff: extended findbase to return secsize via out-param, added r_offset>=secsize skip in all 4 relocation loops.
Verified recommended fix
Extend findbase to also return section size; skip relocations whose r_offset >= secsize in relocate_file and link_elf_obj_reloc_local. Matches finding proposal.
Verdict
REPRODUCED (source-only, root-only). link_elf_obj.c findbase (:940) returns only base addr (no size); relocate_file (:975,1002) and link_elf_obj_reloc_local (:1243,1265) hand rel/rela to elf_reloc with NO r_offset<section-size check. Crafted r_offset wild-writes past module buffer.
No comments yet.