https://sourceware.org/bugzilla/show_bug.cgi?id=34324

Paul Wang <lswang1112 at gmail dot com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |lswang1112 at gmail dot com

--- Comment #2 from Paul Wang <lswang1112 at gmail dot com> ---
Created attachment 16811
  --> https://sourceware.org/bugzilla/attachment.cgi?id=16811&action=edit
poc.elf - alternate PoC for the same abort(), via corrupted (not missing)
DT_PLTREL entry

I independently found this exact crash through fuzzing before seeing this
report. My PoC (attached: poc.elf) reaches the same abort() at the same
line via a different malformed input than attachment 16798, which let me
pin down the precise root cause. Sharing that, plus a suggested fix.

------------------------------------------------------------------------
ROOT CAUSE
------------------------------------------------------------------------

process_relocs() resolves the "PLT" relocation kind from DT_PLTREL at
runtime:

    /* binutils/readelf.c:10064-10077 */
    if (rel_type == reltype_unknown)
      {
        if (dynamic_relocations [i].reloc != DT_JMPREL)
          abort ();
        switch (filedata->dynamic_info[DT_PLTREL])
          {
          case DT_REL:  rel_type = reltype_rel;  break;
          case DT_RELA: rel_type = reltype_rela; break;
          }
        /* no default: DT_PLTREL absent/unrecognised -> silently stays
           reltype_unknown */
      }

    switch (rel_type)
      {
      default:
        abort ();                              /* <-- readelf.c:10084 */
      case reltype_rel:
      ...

filedata->dynamic_info[DT_PLTREL] is only populated when a .dynamic
entry's d_tag is *exactly* DT_PLTREL (20):

    /* binutils/readelf.c:13456-13460 */
    case DT_PLTREL:
      filedata->dynamic_info[entry->d_tag] = entry->d_un.d_val;
      break;

If DT_PLTRELSZ is non-zero (so this code path runs) but no entry's d_tag
equals exactly 20, dynamic_info[DT_PLTREL] keeps its zero-initialised
value, matching neither DT_REL(17) nor DT_RELA(7). rel_type stays
reltype_unknown and hits the unconditional abort() at readelf.c:10084.

Both PoCs reach this same state via different inputs:
  - attachment 16798: no DT_PLTREL entry at all.
  - my poc.elf: a PLTREL-shaped entry IS present, but its d_tag is
    0x0800000000000014 -- the low 32 bits equal DT_PLTREL (0x14), but the
    polluted high bits make d_tag != 20, so the `case DT_PLTREL:` switch
    arm at readelf.c:13456 never matches and the entry is silently
    dropped, same as if it were absent.

So the real bug isn't "DT_PLTREL missing", it's that PLT-relocation-type
resolution has no failure path at all -- any input where DT_PLTRELSZ is
set but DT_PLTREL doesn't resolve to exactly DT_REL/DT_RELA (by omission,
corruption, or an out-of-spec tag) reaches the same abort().

`readelf -d` on my poc.elf confirms: PLTRELSZ present, no entry decodes
as DT_PLTREL. My gdb backtrace (build with debug info) confirms the exact
line: process_relocs @ readelf.c:10084 -> process_object @ 24955 ->
process_file @ 25404 -> main @ 25470 -- same frames as the original
report, one abort() site.

------------------------------------------------------------------------
SUGGESTED FIX
------------------------------------------------------------------------

Give the DT_PLTREL resolution an explicit failure path instead of letting
rel_type silently fall through to the unconditional abort():

    if (rel_type == reltype_unknown)
      {
        if (dynamic_relocations [i].reloc != DT_JMPREL)
          abort ();
        switch (filedata->dynamic_info[DT_PLTREL])
          {
          case DT_REL:
            rel_type = reltype_rel;
            break;
          case DT_RELA:
            rel_type = reltype_rela;
            break;
    +     default:
    +       error (_("DT_PLTRELSZ is set but DT_PLTREL is missing or"
    +                 " invalid -- skipping PLT relocations\n"));
    +       continue;
          }
      }

This keeps the existing `default: abort();` in the second switch as a
true "can't happen" assertion, and turns the attacker-controlled failure
mode into a normal parse error, consistent with how readelf reports every
other malformed .dynamic field.

Reproduced on git HEAD 18ca0215 (2026-07-02), Ubuntu 24.04.4 LTS -- same
crash site as the original report on a different HEAD/OS, so this isn't
platform-specific.

-- 
You are receiving this mail because:
You are on the CC list for the bug.

Reply via email to