โฌข DragonFlyBSD Kernel Audit
โ† dashboard
DF-0020

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_page path, the buffer is image_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.

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

Timeline

  • 2026-06-29 Discovered during automated file-by-file audit of sys/kern/imgact_elf.c.
  • pending Reported to DragonFlyBSD security contact.