ELF ABI-note descriptor read out of bounds (note_overflow ignores n_descsz)
| Field | Value |
|---|---|
| ID | DF-0020 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L |
| CWE | CWE-125 Out-of-bounds Read |
| File | sys/kern/imgact_elf.c |
| Lines | 1700-1707, 1780, 1866-1874 |
| Area | kern |
| Confidence | likely |
| Discovered | 2026-06-29 |
| Reported | pending |
Summary
note_overflow() validates that a note's n_namesz fits in the remaining
PT_NOTE segment but never validates n_descsz. A crafted, truncated
.note.ABI-tag that matches the DragonFly brandnote (n_namesz=10,
n_descsz=4, n_type=1, vendor "DragonFly") โ but whose segment is too
short to actually hold the 4-byte descriptor โ passes note_overflow, matches,
and causes bsd_trans_osrel() to dereference the descriptor at
note + sizeof(Elf_Note) + roundup2(n_namesz, 4) = note + 24, 2โ6 bytes
past the end of the segment buffer. This is a kernel out-of-bounds read on a
file supplied by any local user via execve(2).
Root cause
sys/kern/imgact_elf.c:1700-1707 โ note_overflow:
static boolean_t
note_overflow(const Elf_Note *note, size_t maxsize)
{
if (sizeof(*note) > maxsize)
return TRUE;
if (note->n_namesz > maxsize - sizeof(*note)) /* checks namesz only */
return TRUE;
return FALSE; /* n_descsz never checked */
}
The note walk calls note_overflow(note, note_end - note) (:1780-1781). The
match (:1786-1790) requires n_descsz == checknote->hdr.n_descsz (4 for the
brandnote); on match, bsd_trans_osrel runs (:1791-1794):
static boolean_t
__elfN(bsd_trans_osrel)(const Elf_Note *note, int32_t *osrel)
{
uintptr_t p;
p = (uintptr_t)(note + 1); /* +12 (sizeof Elf_Note) */
p += roundup2(note->n_namesz, sizeof(Elf32_Addr)); /* +12 (roundup2(10,4)) */
*osrel = *(const int32_t *)(p); /* :1872 reads note+24 */
return (TRUE);
}
For a segment of p_filesz=22 (12-byte Elf_Note + 10-byte name, no
descriptor): note_overflow(note, 22) returns FALSE (12<=22, 10<=10), the
brandnote matches, and the descriptor read at note+24 lands past the 22-byte
segment buffer.
- In the
limited_to_first_pagepath, the buffer isimage_header(PAGE_SIZE); placing the note near the page end makes the read escape the page. - In the cross-page path, the buffer is
kmalloc(notesz)(the segment size); the read escapes the allocation.
Threat model & preconditions
- Attacker position: any local unprivileged user. The image activator runs
for every local
execve(2), including of attacker-owned files; no privilege required and the binary need not be loadable (the header/note check runs before vmspace setup). - Privileges gained or impact: kernel OOB read of โค4 bytes adjacent to the
note buffer. The read value is stored in
p_osrel, which is not exposed to userspace โ so this is not a confirmed info leak. When the OOB read straddles an unmapped page boundary, the kernel faults โ panic (local DoS). - Required config or capabilities: none; default kernel.
- Reachability:
execve(2)of a crafted ELF with a truncated PT_NOTE.note.ABI-tag.
Proof of concept
PoC source: findings/poc/DF-0020/elf_note_oob.py
Emits a minimal ELF with one PT_NOTE whose brandnote header claims a 4-byte descriptor but whose segment truncates it, placed near the first-page boundary.
Build & run (unprivileged)
python3 findings/poc/DF-0020/elf_note_oob.py -o /tmp/oob_elf chmod +x /tmp/oob_elf /tmp/oob_elf
Expected output
Either nothing visible (OOB bytes land in mapped adjacent memory, value to
p_osrel, not exposed) or โ when the OOB read straddles an unmapped page โ a
kernel page-fault panic (local DoS).
Impact
Kernel OOB read on attacker-controlled input; the value is not exposed, so the realistic impact is a local DoS (panic on a faulting read) rather than an info leak. Rated Low. Defense-in-depth: an attacker controlling adjacent kernel memory (via a separate primitive) could combine, but standalone this is a robustness/correctness fix.
Recommended fix
Validate n_descsz in note_overflow:
--- a/sys/kern/imgact_elf.c
+++ b/sys/kern/imgact_elf.c
@@ -1699,11 +1699,16 @@ static boolean_t
note_overflow(const Elf_Note *note, size_t maxsize)
{
+ size_t avail;
+
if (sizeof(*note) > maxsize)
return TRUE;
- if (note->n_namesz > maxsize - sizeof(*note))
+ avail = maxsize - sizeof(*note);
+ if (note->n_namesz > avail)
+ return TRUE;
+ /* Ensure the claimed descriptor also fits within the segment. */
+ avail -= roundup2(note->n_namesz, sizeof(Elf32_Addr));
+ if (note->n_descsz > avail)
return TRUE;
return FALSE;
}
References
sys/kern/imgact_elf.c:1700-1707โnote_overflow(ignoresn_descsz).sys/kern/imgact_elf.c:1780โ caller withnote_end - note.sys/kern/imgact_elf.c:1866-1874โbsd_trans_osreldescriptor deref.- CWE-125 Out-of-bounds Read.
Timeline
- 2026-06-29 Discovered during automated file-by-file audit of
sys/kern/imgact_elf.c. - pending Reported to DragonFlyBSD security contact.