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

            Bug ID: 34353
           Summary: objcopy: null-pointer dereference in
                    bfd_set_section_size (bfd/section.c:1433)
           Product: binutils
           Version: 2.47 (HEAD)
            Status: UNCONFIRMED
          Severity: normal
          Priority: P2
         Component: binutils
          Assignee: unassigned at sourceware dot org
          Reporter: lswang1112 at gmail dot com
  Target Milestone: ---

Created attachment 16819
  --> https://sourceware.org/bugzilla/attachment.cgi?id=16819&action=edit
Minimal 488-byte ELF triggering the NULL deref

Affected version: GNU Binutils 2.46.50.20260703
(upstream HEAD, git commit d5a65d4c5acf91b6fa4223e930b3efd0fa8f4753)
Confirmed reproduced on latest upstream HEAD as of 2026-07-05.

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

copy_object() in binutils/objcopy.c handles the --update-section option
by iterating over the update_sections list. For each entry it retrieves
the corresponding section from the input BFD and then reads its
output_section pointer:

    /* binutils/objcopy.c:3076-3087 */
    for (pupdate = update_sections;
         pupdate != NULL;
         pupdate = pupdate->next)
      {
        pupdate->section = bfd_get_section_by_name (ibfd, pupdate->name);
        if (pupdate->section == NULL)
          {
            non_fatal (_("error: %s not found, can't be updated"),
pupdate->name);
            return false;
          }

        osec = pupdate->section->output_section;     /* line 3081 -- may be
NULL */
        if (!bfd_set_section_size (osec, pupdate->size))  /* line 3082 --
crashes */
          {
            bfd_nonfatal_message (NULL, obfd, osec, NULL);
            return false;
          }
      }

bfd_get_section_by_name() succeeds (section exists in the input), but
output_section is NULL when the named section is of type SHT_GROUP.
BFD does not assign an output section to SHT_GROUP sections during the
copy setup phase (setup_section()), so output_section remains NULL.
bfd_set_section_size() then unconditionally dereferences sec->owner at
line 1433, causing a NULL pointer dereference and SIGSEGV.

A crafted ELF file with a section whose name matches the --update-section
argument and whose sh_type is SHT_GROUP (17) is sufficient to trigger the
crash.

Call chain:

    objcopy --update-section .rodata=<file> poc.elf output.o
      copy_main           binutils/objcopy.c:6256
      copy_file           binutils/objcopy.c:4109
      copy_object         binutils/objcopy.c:3082  <-- osec = NULL
      bfd_set_section_size bfd/section.c:1433       <-- NULL deref, SIGSEGV

------------------------------------------------------------------------
CRASH OUTPUT
------------------------------------------------------------------------

Build from latest HEAD (see HOW TO REPRODUCE below) with ASan+UBSan:

    ../../bfd/section.c:1433:10: runtime error: member access within null
    pointer of type 'struct asection'
    AddressSanitizer:DEADLYSIGNAL
    =================================================================
    ==PID==ERROR: AddressSanitizer: SEGV on unknown address 0x0000000000d0
    ==PID==The signal is caused by a READ memory access.
    ==PID==Hint: address points to the zero page.
        #0 bfd_set_section_size ../../bfd/section.c:1433
        #1 copy_object          ../../binutils/objcopy.c:3082
        #2 copy_file            ../../binutils/objcopy.c:4109
        #3 copy_main            ../../binutils/objcopy.c:6256
        #4 main                 ../../binutils/objcopy.c:6360
    SUMMARY: AddressSanitizer: SEGV ../../bfd/section.c:1433
             in bfd_set_section_size

gdb backtrace at the fault, plain (non-instrumented) build:

    Program received signal SIGSEGV, Segmentation fault.
    0x... in bfd_set_section_size (sec=0x0, val=5) at ../../bfd/section.c:1433
    1433  if (sec->owner == NULL || sec->owner->output_has_begun)
    #0  bfd_set_section_size  ../../bfd/section.c:1433
    #1  copy_object           ../../binutils/objcopy.c:3082
    #2  copy_file             ../../binutils/objcopy.c:4109
    #3  copy_main             ../../binutils/objcopy.c:6256
    #4  main                  ../../binutils/objcopy.c:6360

    (gdb) print/x sec
    $1 = 0x0

------------------------------------------------------------------------
HOW TO REPRODUCE
------------------------------------------------------------------------

Build from latest HEAD:

    git clone --depth 1 https://sourceware.org/git/binutils-gdb.git
    cd binutils-gdb
    mkdir build && cd build
    ../configure --disable-gdb --disable-gdbserver --disable-gdbsupport \
      --disable-sim --disable-gprofng \
      CFLAGS="-g -O1 -fsanitize=address,undefined"
    make -j$(nproc) all-binutils

The attached poc.elf is a hand-crafted, 488-byte minimal ELF64. It
contains exactly five sections:

    [0] NULL         (required)
    [1] .rodata      SHT_GROUP, sh_link=2 (.symtab), sh_info=1,
                     sh_entsize=4, content=0x00000001 (GRP_COMDAT flag)
    [2] .symtab      two entries (NULL + one GLOBAL NOTYPE symbol)
    [3] .strtab      symbol name table
    [4] .shstrtab    section name string table

The critical property: section [1] is named ".rodata" but its sh_type is
SHT_GROUP (17). BFD accepts the file as a valid ELF but never assigns
output_section for GROUP-typed sections. When --update-section .rodata=<file>
is passed, copy_object() finds the section, reads output_section = NULL,
and passes NULL to bfd_set_section_size().

Create the update data file (any non-empty file works):

    echo "A" > data.bin

Run:

    ./binutils/objcopy --update-section .rodata=data.bin poc.elf output.o

Under gdb:

    gdb -q -batch -ex run -ex bt \
      --args ./binutils/objcopy --update-section .rodata=data.bin poc.elf
output.o

Plain (no-sanitizer) build crashes with SIGSEGV, as shown above.

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

Add a NULL check for osec before passing it to bfd_set_section_size().
When output_section is NULL the section will not appear in the output,
so updating its size is meaningless; emit a diagnostic and bail:

    --- a/binutils/objcopy.c
    +++ b/binutils/objcopy.c
    @@ -3079,6 +3079,13 @@ copy_object (bfd *ibfd, bfd *obfd, const
bfd_arch_info_type *input_arch)

           osec = pupdate->section->output_section;
    +      if (osec == NULL)
    +        {
    +          non_fatal (_("error: %s has no output section, can't be
updated"),
    +                     pupdate->name);
    +          return false;
    +        }
           if (!bfd_set_section_size (osec, pupdate->size))

Alternatively, the root issue can be fixed more broadly in setup_section()
(or filter_sections()) to ensure that --update-section targets whose
output_section is NULL are caught before copy_object() processes the
update list.

------------------------------------------------------------------------
Build & Platform
------------------------------------------------------------------------

binutils version: GNU Binutils 2.46.50.20260703
                  (git HEAD d5a65d4c5acf91b6fa4223e930b3efd0fa8f4753)
component: objcopy / libbfd
OS: Ubuntu 24.04.4 LTS
arch: x86_64

------------------------------------------------------------------------
IMPACT
------------------------------------------------------------------------

NULL pointer dereference (CWE-476) in libbfd, triggered when objcopy's
--update-section option names a section whose sh_type is SHT_GROUP in
the input ELF. Reproducible impact is denial of service (SIGSEGV).

objcopy is commonly run as part of build toolchains on third-party object
files (e.g. post-link binary patching, debug-section injection). A
malicious or malformed object file placed in a build pipeline can crash
the build host's objcopy unconditionally. Because objcopy is also invoked
by ld and other build tools on untrusted input in some workflows, the
crash surface extends beyond interactive use.

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

Reply via email to