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

            Bug ID: 34418
           Summary: Off-by-one section-index check in bfd/vms-alpha.c;
                    alpha_vms_slurp_relocs(): an ETIR relocation record
                    with section index == section_count passes a `>'
                    (should be `>=') bounds check and reads one element
                    past the PRIV(sections) array;
           Product: binutils
           Version: 2.48 (HEAD)
            Status: UNCONFIRMED
          Severity: normal
          Priority: P2
         Component: binutils
          Assignee: unassigned at sourceware dot org
          Reporter: achintya at umail dot ucsb.edu
  Target Milestone: ---

Created attachment 16855
  --> https://sourceware.org/bugzilla/attachment.cgi?id=16855&action=edit
It contains a zip with a poc and a crash report

================================================================================
VERSION
================================================================================
`objdump --version` prints:
    "GNU objdump (GNU Binutils) 2.47.50.20260720"
This is git master, commit cef1fbf3b02ff6e5bc62e1b8a7d30c0a32b8b9f7
(dated 2026-07-20).  Per binutils' versioning convention, bfd/version.m4 =
BFD_VERSION 2.47.50 on master denotes the DEVELOPMENT version heading to the
2.48 release (the 2.47 release branch, binutils-2_47-branch, carries 2.46.90).

Affected versions: the defect is present in BOTH:
  * git master (2.47.50, -> 2.48 development), commit above; and
  * the binutils-2_47-branch (2.46.90, -> the upcoming 2.47 release): the same
    line bfd/vms-alpha.c:5528 uses `>' there too (verified).
Not fixed as of either branch.

================================================================================
BUILD CONFIGURATION  (from config.status)
================================================================================
Host/target configuration name:  x86_64-pc-linux-gnu
configure options:
    --disable-gdb --disable-gdbserver --disable-sim --disable-gprofng
    --disable-werror --disable-nls --enable-targets=alpha-dec-vms
Compiler: clang 14.0.0 (Ubuntu 14.0.0-1ubuntu1.1), -O2.

NOTE: the alpha-vms BFD backend must be built in (it is included by
--enable-targets=all as well as by an explicit alpha*-*-*vms* target).  The
defect is in BFD source and is compiler/optimization independent.

================================================================================
WHAT WENT WRONG  /  WHAT SHOULD HAVE HAPPENED
================================================================================
What went wrong:
  While slurping relocations from an Alpha-VMS object, alpha_vms_slurp_relocs()
  validates the attacker-supplied section index `cur_psect` with

      if (cur_psect < 0 || cur_psect > (int) PRIV (section_count))   /* :5528
*/

  Because the upper bound uses `>' instead of `>=', the value
  cur_psect == section_count is accepted.  Only indices 0 .. section_count-1
are
  valid section pointers, so

      sec = PRIV (sections)[cur_psect];                             /* :5536 */

  with cur_psect == section_count reads an invalid entry.  PRIV(sections) is
  allocated in powers-of-two chunks (section_max entries, starting at 16 and
  doubling); when the number of sections is exactly a chunk boundary e.g. 16
  sections, so section_count == section_max == 16 and the allocation is 128
  bytes; index section_count is one asection* PAST THE END of the allocation
  (the heap-buffer-overflow read demonstrated below).  (At a non-boundary count
  it instead reads an uninitialised in-allocation slot; either way `sec' is a
  bogus pointer.)  The crafted object uses exactly 16 sections to make it a
  clean past-the-end read.  The
  resulting out-of-bounds pointer `sec' is then dereferenced vms_section_data
  (sec) == sec->used_by_bfd (:5543), sec->reloc_count (:5546) and written
  through vms_sec->reloc_max = 64, sec->relocation = bfd_zmalloc(...) (:5550-
  5551), sec->reloc_count++ (:5564).

What should have happened:
  The bounds check should reject cur_psect == section_count, i.e. use `>='
  (matching the five sibling checks in the same file at lines 1351, 1369, 1403,
  1500 and 5601, all of which correctly write
  `>= (int) PRIV (section_count)').  A crafted index should produce
  "invalid section index in ETIR" and a clean failure, not an out-of-bounds
  access.

================================================================================
ROOT CAUSE  (bfd/vms-alpha.c, alpha_vms_slurp_relocs)
================================================================================
    5528  if (cur_psect < 0 || cur_psect > (int) PRIV (section_count))  <-- '>'
    5529    { _bfd_error_handler (_("invalid section index in ETIR")); goto
fail; }
    5534  if (PRIV (sections) == NULL) goto fail;
    5536  sec = PRIV (sections)[cur_psect];        <-- OOB read when cur_psect
                                                       == section_count
    5543  vms_sec = vms_section_data (sec);        <-- sec->used_by_bfd 
(deref)
    5546  if (sec->reloc_count >= vms_sec->reloc_max) {
    5550    vms_sec->reloc_max = 64;               <-- write through wild ptr
    5551    sec->relocation = bfd_zmalloc (...);   <-- write through wild ptr
    ...
    5564  sec->reloc_count++;                      <-- write through wild ptr

  cur_psect is set from an attacker-controlled ETIR record: ETIR__C_STA_PQ
takes
  a 32-bit section index (bfd_getl32(ptr+4)) into cur_psidx, and
ETIR__C_CTL_SETRB
  copies it into cur_psect.  With exactly 16 program sections defined in the
  EGSD, PRIV(sections) is a 16-element (128-byte) allocation and section_count
  == 16, so cur_psect == 16 passes `16 > 16' == false and indexes sections[16].

  The sibling checks that correctly use `>=':
    vms-alpha.c:1351, 1369, 1403, 1500, 5601 -> ">= (int) PRIV (section_count)"
  Only line 5528 uses ">".

================================================================================
REPRODUCTION
================================================================================
Tool options at run time:
    objdump -r poc_vms.obj

(The same slurp path is reached by any BFD client that canonicalizes the VMS
object's relocations, e.g. `objdump -r/-R/-d' and `ld'.)

Input file poc_vms.obj  (228 bytes; sha256
8eec54268a810ba8ec437e9cefaf37876378091a9fcb91a0a7e698a923c316ac):
A minimal FF_NATIVE Alpha-VMS object EMH (module header) + EGSD defining 16
program sections (so section_count == 16) + one ETIR record whose command
stream
is STA_PQ(index=16) -> CTL_SETRB (cur_psect = 16) -> STO_OFF (reaches the
relocation-apply block) + EEOM.  One section is a real, relocatable ".text" so
that objdump -r queries its relocations and triggers alpha_vms_slurp_relocs.

poc_vms.obj (base64):
CAAsAAAAAAAAAAAAAAAAAAAAAAAEVEVTVAFWICAgICAgICAgICAgICAgICAKAJIAAAAAAAAAEgAA
AIgABAAAAAUudGV4dAAACAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAACAAAAAAAAAAIAAAAAAAAAAgA
AAAAAAAACAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAACAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAACAAA
AAAAAAAIAAAAAAAAAAgAAAAAAAsAHAADABAAEAAAAAAAAAAAAAAAlgAEADsABAAJAAoAAAAAAAAA

================================================================================
OBSERVED BEHAVIOUR
================================================================================
Under AddressSanitizer (clang -fsanitize=address):

    objdump -r poc_vms.obj:
    ERROR: AddressSanitizer: heap-buffer-overflow ...
    READ of size 8 at 0x... thread T0
      #0 alpha_vms_slurp_relocs          bfd/vms-alpha.c:5536
      #1 alpha_vms_get_reloc_upper_bound bfd/vms-alpha.c:5639
      #2 bfd_get_reloc_upper_bound       bfd/bfd.c:2198
      #3 dump_relocs_in_section          binutils/objdump.c:5558
      #4 bfd_map_over_sections           bfd/section.c:1369
      #5 dump_relocs / dump_bfd          binutils/objdump.c  ... objdump -r
    "0x... is located 0 bytes to the right of 128-byte region [...,...)"
    allocated by bfd_realloc_or_free at _bfd_vms_slurp_egsd (vms-alpha.c:1298)
    i.e. one asection* past the 16-entry PRIV(sections) array.

In an ordinary (non-ASan) build, clang -O2, with or without -DNDEBUG:
    objdump -r poc_vms.obj  ->  Segmentation fault (SIGSEGV).
The out-of-bounds slot holds unrelated heap data; the wild `sec' pointer is
dereferenced at vms-alpha.c:5543 and faults.  (No assertion or FORTIFY/canary
is
involved on this path it is a wild-pointer dereference.)

================================================================================
IMPACT
================================================================================
Reachable from an untrusted input file with no special privileges (running
objdump/ld etc. on an attacker-supplied Alpha-VMS object).

  - Out-of-bounds read of an asection* one element past a heap allocation
    [CWE-125], whose value then flows into a pointer dereference and several
    writes-through (sec->relocation = ..., vms_sec->reloc_max = 64,
    sec->reloc_count++) [CWE-787 potential].

Realistic impact: DENIAL OF SERVICE a reproducible crash (SIGSEGV) while
processing a crafted file, confirmed on ordinary (non-sanitizer) builds with
and
without -DNDEBUG.  The out-of-bounds slot holds unrelated heap data; the wild
`sec' is dereferenced at vms-alpha.c:5543 and faults.  No stack-protector /
FORTIFY/assertion is involved it is a wild-pointer dereference.

================================================================================
SUGGESTED FIX
================================================================================
Use `>=' to match the sibling checks, rejecting cur_psect == section_count:

    -  if (cur_psect < 0 || cur_psect > (int) PRIV (section_count))
    +  if (cur_psect < 0 || cur_psect >= (int) PRIV (section_count))
         {
           _bfd_error_handler (_("invalid section index in ETIR"));
           goto fail;
         }

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

Reply via email to