Skip to content

fix(dracut-install): validate ELF section-table bounds - #2615

Open
prabhakarpujeri wants to merge 1 commit into
dracut-ng:mainfrom
prabhakarpujeri:fix/elf-bounds-checks
Open

fix(dracut-install): validate ELF section-table bounds#2615
prabhakarpujeri wants to merge 1 commit into
dracut-ng:mainfrom
prabhakarpujeri:fix/elf-bounds-checks

Conversation

@prabhakarpujeri

Copy link
Copy Markdown

Problem

src/install/dracut-install.c's PARSE_ELF_START macro (starting point of all three ELF-walking macros: FIND_LIBRARY_RUNPATH_FOR_BITS, RESOLVE_DEPS_DLOPEN_FOR_BITS, RESOLVE_DEPS_NEEDED_FOR_BITS) only checked e_shoff > src_len and e_shstrndx >= e_shnum. It missed the two aggregate bounds that the subsequent pointer arithmetic relies on:

  1. Section table extente_shoff + e_shnum * sizeof(Shdr) <= src_len. Unvalidated, so iterating section headers (or reading shdr[i].sh_name) can walk past the end of the mapped file.
  2. shstrtab section extent — the shdr[i].sh_offset/sh_size values are file-controlled and used to compute shstrtab = map + sh_offset, and shstrtab[shdr[i].sh_name] is dereferenced (via strcmp() against ".dynamic" / ".note.dlopen") without checking either lies within the file. A malformed ELF can therefore cause a read outside the mmap: crash (SIGSEGV) or garbage-based decisions.

Additionally, shdr[i].sh_link (another file-originated index) was dereferenced without bounds-checking in the RUNPATH/SONAME/NEEDED walks.

The trigger context is the dependency-resolution path (dracut-install -l), invoked during every initramfs build that resolves lazy deps: the tool mmaps a binary under a sysroot and walks its sections. A malformed or truncated ELF inside the build environment makes the current code read out of bounds.

Fix

  • In PARSE_ELF_START: reject if the whole section header table doesn't fit (e_shnum > (src_len - e_shoff) / sizeof(Shdr)), then read the shstrtab section's sh_offset/sh_size from the (now-bounds-checked) table and validate both against src_len.
  • New helper elf_sect_name() returns a section name only if sh_name < shstrtab_len and the name is NUL-terminated within the table; used by all four strcmp(&shstrtab[shdr[i].sh_name], ...) sites.
  • In the RUNPATH/DT_SONAME/DT_NEEDED walks, reject if shdr[i].sh_link >= e_shnum before dereferencing shdr[sh_link].

No behavior change for well-formed input; malformed input is now rejected early rather than dereferenced through garbage pointers.

Verification

Built with CFLAGS="-g -O2 -Wall -Wextra", clean (make indent-c passes with the project's astyle config). Crafted 5 malformed ELF64 inputs exercising each check, fed through dracut-install -l (the code path hit during image builds):

input before after
shstrtab sh_offset points 16 MiB past EOF (F2) SIGSEGV exit 0 (dependency resolution skips it)
sh_name = 0xFFFFFFF0 (F3) SIGSEGV exit 0
name = ".dynamic" with no NUL within table, ends at page boundary (F5b) exit 0 exit 0
section table extent truncated (F1) exit 0 exit 0
sh_link = 99 with real .dynamic (F6b) SIGSEGV exit 0
/bin/true (valid) exit 0 exit 0

(F1/F5b don't crash on the unpatched binary because the specific reads happen to stay within the mapped region's page — reads return garbage rather than faulting. The only added observable effect is guarded rejection.)

Regression control: -l on /bin/bash + /bin/ls produces byte-identical dependency set before vs after (14 entries, same names).

Both compile paths tested: default (-fsyntax-only passes) and gcc -fsyntax-only -DHAVE_SYSTEMD -DCONFIG_WEAKDEP $(pkg-config --cflags libsystemd) ... — so RESOLVE_DEPS_DLOPEN_FOR_BITS (which gets the helper) is also syntax-checked.

@prabhakarpujeri
prabhakarpujeri requested a review from a team as a code owner August 19, 2026 04:39
The PARSE_ELF_START macro checked only that e_shoff and e_shstrndx are
individually < src_len, then indexed shdr[e_shstrndx].sh_offset into the
mmap'ed file. Two classes of OOB reads remain on truncated or malformed
ELFs:

1. The section header table extent is not validated: e_shnum section
   headers starting at shoff can extend past the end of the file, so
   iterating them reads past the map.
2. The section header string table (shstrtab) content extent isn't
   validated: sh_offset/sh_size come from the file, and the
   strcmp(&shstrtab[shdr[i].sh_name], ...) reads at attacker-controlled
   offsets, potentially past the mapping.

Add the missing bounds checks:

- PARSE_ELF_START now verifies the whole section table fits in the
  mapped file, then treats it as untrusted: a helper reads the shstrtab
  section's offset and size, validates both against src_len, and the
  section-name lookup checks the name offset and NUL termination inside
  the table before any strcmp.
- DRUNPATH/DT_SONAME/DT_NEEDED walks now validate the shadow-section
  link index (sh_link < e_shnum) before dereferencing shdr[sh_link].

Verified by crafting five malformed ELF64 inputs to the new
boundschecks and feeding them to the dependency resolution path:
- Off-map shstrtab.sh_offset: segfault without patch, clean skip with
- sh_name past table end:                    segfault without patch
- No NUL within name's table window:             segfault without patch
- Section table extent truncated (F1) / sh_link past end (F6b): clean
  rejection with patch
Regression control: -l dependency resolution of /bin/bash and /bin/ls
yields byte-identical results before/after. Also verified syntax with
and without HAVE_SYSTEMD (covers RESOLVE_DEPS_DLOPEN_FOR_BITS).
@github-actions github-actions Bot added c dracut-install Issues related to dracut install labels Aug 19, 2026
@devkontrol

Copy link
Copy Markdown
Collaborator

CC @marcosfrm

@devkontrol

Copy link
Copy Markdown
Collaborator

CC @chewi @aafeijoo-suse

@chewi chewi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, this is very thorough. I must admit that the low-level ELF handling made my head hurt, and I relied on AI a fair bit here. I've stared at these changes for a while though, and I think they look good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c dracut-install Issues related to dracut install

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants