https://sourceware.org/bugzilla/show_bug.cgi?id=34420
Bug ID: 34420
Summary: libelf: output-layout arithmetic wrap can cause
out-of-mapping writes in elf_update
Product: elfutils
Version: unspecified
Status: UNCONFIRMED
Severity: normal
Priority: P2
Component: libelf
Assignee: unassigned at sourceware dot org
Reporter: karankurani3k at gmail dot com
CC: elfutils-devel at sourceware dot org
Target Milestone: ---
Created attachment 16856
--> https://sourceware.org/bugzilla/attachment.cgi?id=16856&action=edit
Public-API reproducers, ASan traces, SHT_NOBITS proof, validation results, and
proposed two-patch fix.
Tested revision:
01a933566e2606e17893a72121e70c81ba7558dc
Summary
=======
elf_update() computes the complete output layout in the shared
libelf/elf32_updatenull.c template before passing the resulting size and
offsets to either the mmap or pwrite serializer.
Several section, program-header, section-header, data-descriptor, and
alignment calculations are performed without first proving that the complete
extent is representable.
With caller-controlled ELF_F_LAYOUT offsets, unsigned arithmetic can wrap and
produce an apparently small output size. The mmap serializer then operates on
offsets outside the allocated mapping.
Confirmed failures
==================
I reproduced three independent failures through public libelf APIs on the
unmodified revision:
1. Section extent
sh_offset + sh_size wraps, followed by an out-of-mapping section-data write
in __elf64_updatemmap.
ASan result:
invalid memmove / SEGV caused by a WRITE.
2. Program-header extent
e_phoff + program-header size wraps, followed by a negative-size memset in
__elf64_updatemmap.
ASan result:
negative-size-param in memset.
3. Section-header extent
e_shoff + section-header table size wraps, followed by an out-of-mapping
section-header write.
ASan result:
invalid memcpy / SEGV caused by a WRITE.
Each failure reproduced 3/3 on the unmodified revision.
Signed Elf_Data.d_off conversion
================================
A separate supported SHT_NOBITS sequence demonstrates an unsafe signed
conversion without requiring a physically large buffer.
Upstream tests establish that SHT_NOBITS Elf_Data may use:
d_buf == NULL
d_size != 0
Using two descriptors and ELF_C_NULL, the first descriptor advances the
automatic running offset above INT64_MAX. The next descriptor stores the
uint64_t offset into the public int64_t Elf_Data.d_off field.
Before the proposed guard:
elf_update returned success
second descriptor d_off became INT64_MIN
After the proposed guard:
elf_update returns -1
elf_errno returns ELF_E_RANGE
the second descriptor retains its previous nonnegative d_off
Threat model
============
The three mmap failures require a caller to opt into ELF_F_LAYOUT and supply
extreme values through libelf's public mutation APIs.
This is not a malformed-input parser trigger and I am not claiming remote code
execution. The expected behavior is for libelf to reject an unrepresentable
layout with ELF_E_RANGE rather than derive an undersized mapping and write
outside it.
Root cause
==========
The final common ELF32/ELF64 layout builder does not consistently enforce that:
- additions and alignment operations do not wrap;
- output extents fit the destination ELF field;
- the final result fits elf_update()'s int64_t return type;
- an automatic uint64_t descriptor offset fits Elf_Data.d_off before it is
stored.
Proposed fix
============
The proposed patch series:
- computes layout state in uint64_t;
- adds small file-local checked addition, alignment, and extent helpers;
- validates ELF32 destination-field narrowing;
- rejects final results above INT64_MAX;
- checks an automatic offset before storing it in signed Elf_Data.d_off;
- preserves the existing d_align == 0 and d_align == 1 placement behavior;
- returns ELF_E_RANGE before serialization.
Validation
==========
- Native AArch64 GCC ASan/UBSan full suite:
255 PASS, 10 SKIP, 0 FAIL.
- Native AArch64 optimized full suite:
256 PASS, 9 SKIP, 0 FAIL.
- x86_64 Linux VM under CPU emulation:
targeted GCC and Clang ASan/UBSan tests pass.
- Three original ASan failures:
baseline reproduces 3/3;
patched calls return -1 with ELF_E_RANGE 3/3.
- Generated controls reopen through public libelf APIs and pass eu-elflint.
- Public ABI unchanged:
libelf.so.1 remains unchanged;
all 120 exported symbols are identical;
no public headers changed.
A two-patch RFC series with regression coverage is prepared for
elfutils-devel and will reference this Bugzilla report.
Credit requested:
Reported-by: Karan Kurani <[email protected]>
--
You are receiving this mail because:
You are on the CC list for the bug.