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.

Reply via email to