file/libmagic — OOB Read in ELF Core Note Parser

A missing bounds check in file's ELF core note parser. When processing a FreeBSD-style NT_PRPSINFO note, the code reads the process name field without first verifying that the field's offset falls within the note's data buffer. A crafted ELF core file with a small NT_PRPSINFO descriptor triggers an out-of-bounds read.

1. Context

file(1) and libmagic parse ELF core files to extract identifying information — the process name, PID, and signal. The relevant code is in src/readelf.c, in the do_core_note() function. I audited this file while looking at how file handles the note section of ELF files, specifically the paths that deal with different OS-specific note formats.

2. NT_PRPSINFO and the FreeBSD layout

NT_PRPSINFO is an ELF note type that stores process information in a core dump. The in-memory layout differs between OS families. In the FreeBSD variant, the process name (a null-terminated string, up to 80 characters) sits at a fixed offset from the start of the note descriptor, with the offset depending on whether the core is 32-bit or 64-bit:

if (clazz == ELFCLASS32)
    argoff = 4 + 4 + 17;       /* = 25 */
else
    argoff = 4 + 4 + 8 + 17;   /* = 33 */

The code then reads the process name directly:

if (elf_printf(ms, ", from '%.80s'", nbuf + doff + argoff) == -1)
    return -1;

3. The missing check

The read at nbuf + doff + argoff happens before any check that doff + argoff + 81 (the maximum number of bytes the %.80s format could read, plus the null terminator scan) is within the bounds of the note's data buffer. The note descriptor is attacker-controlled: a crafted ELF core file can set descsz to a small value while still providing a valid-looking NT_PRPSINFO note type, making doff + argoff point past the end of the allocated buffer.

There is a size check one line later for the PID field:

pidoff = argoff + 81 + 2;
if (doff + pidoff + 4 <= size) {
    /* read PID */
}

So the PID access is guarded, but the process name access immediately before it is not.

4. Triggering it

A minimal ELF core file with a PT_NOTE segment containing an NT_PRPSINFO note and a descsz smaller than argoff + 81 is enough. When file crafted.core runs, do_core_note() enters the OS_STYLE_FREEBSD branch, computes argoff, and calls elf_printf with a pointer past the note's data. Depending on what follows in memory, this reads whatever happens to be there — adjacent heap data or mapped pages — until a null byte is found.

5. Fix — commit 6bb1b445

Christos Zoulas committed the fix in commit 6bb1b445 with the message "Add missing bounds check (Alexandru Hossu)". The change adds the missing guard before the process name read:

+if (doff + argoff + 81 <= size) {
     if (elf_printf(ms, ", from '%.80s'", nbuf + doff + argoff) == -1)
         return -1;
+}
The fix mirrors the pattern already used for the PID field a few lines below — the same kind of bounds check was already present for the adjacent access, just not for this one.