Public bug reported:

## Summary

GNU ld 2.47 crashes with SIGSEGV when an object with many global symbols is
placed between `--start-lib` and `--end-lib`. This is a use-after-free in
libbfd. It is fixed on upstream master but the fix is neither on
`binutils-2_47-branch` nor in the Ubuntu package.

Build systems now pass `--start-lib`/`--end-lib` to GNU ld because ld 2.47
supports these options. For example, Bazel's auto-configured C++ toolchain
enables its `supports_start_end_lib` feature. Many C++ projects therefore
fail to link (in my case, building `gawk` through Bazel/rules_cc_autoconf).

## Reproducer

```sh
seq -f 'int sym_%05g_with_a_reasonably_long_name(void) { return 0; }' 0 1999 > 
lib.c
echo 'int lib_entry(void) { return 0; }' >> lib.c

echo 'int lib_entry(void); int main(void) { return lib_entry(); }' >
main.c

gcc -c lib.c main.c
gcc main.o -Wl,--start-lib lib.o -Wl,--end-lib -o t
```

Result:

```
collect2: fatal error: ld terminated with signal 11 [Segmentation fault]
```

Linking `main.o lib.o` without `--start-lib`/`--end-lib` works.

Under valgrind:

```
Invalid read of size 8
   at 0x...: ??? (libbfd-2.47-system.so)   <- elf_link_add_archive_symbols
 Address ... is 24 bytes inside a block of size 32,032 free'd
   at free
   by objalloc_free_block
   by ...                                   <- _bfd_compute_and_push_armap
```

## Analysis

For each `--start-lib` group, ld builds a fake archive and its symbol map via
`_bfd_make_armap()` -> `_bfd_compute_and_push_armap()` (bfd/archive.c):

1. `_bfd_compute_and_push_armap()` allocates a marker:
   `first_name = bfd_alloc (arch, 1)`.
2. It calls the `push_armap` callback, `_bfd_load_armap()`. This callback
   allocates `ardata->symdefs` and the symbol names on the same `arch`
   objalloc, after the marker.
3. On return it calls `bfd_release (arch, first_name)`, which frees
   everything allocated after the marker, including the symbol map it just
   built.
4. `elf_link_add_archive_symbols()` (bfd/elflink.c) then iterates over the
   freed `symdefs`. Once the memory is reused, `symdef->name` holds garbage
   (e.g. `0x922`) and ld crashes in
   `bfd_hash_lookup` <- `bfd_link_hash_lookup` <- 
`_bfd_elf_archive_symbol_lookup`.

The crash only shows up when the symbol map is large enough to sit in its
own objalloc chunk, which is actually returned to malloc and reused. Small
test cases usually link fine (but still fail under valgrind).

## Why 2000 symbols

Valgrind reports the use-after-free with as few as ~40 symbols; an actual
segfault needs the freed chunk to be reused, hence 2000 symbols in the
reproducer.

This follows from how objalloc (libiberty/objalloc.c) works. Requests below
`BIG_REQUEST` (512 bytes) are carved out of a shared ~4 KiB chunk, and
releasing them only rewinds a pointer, so the data stays readable. Larger
requests get a dedicated malloc'd chunk, which `bfd_release` actually frees.
`symdefs` takes 16 bytes per symbol:

| Global symbols | `symdefs` size | Link     | Valgrind       |
|----------------|----------------|----------|----------------|
| 31             | 496 B          | OK       | clean          |
| 41             | 656 B          | OK       | use-after-free |
| 101 - 1001     | 1.6 - 16 KiB   | OK       | use-after-free |
| 2001           | 32 KiB         | SIGSEGV  | use-after-free |

Between ~32 and ~1000 symbols the link succeeds by luck, because the freed
chunk still holds its old contents. Only when malloc hands it back for the
object's symbol/relocation buffers does the symbol map get overwritten.
Real-world C++ objects easily reach this size.

## Upstream fix

Commit d9ab6e42e739fe826b02a5f868586132ea2d893a (Alan Modra, 2026-08-15):
"PR ld/24600: BFD: Fix use-after-free from `_bfd_load_armap'"
https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=d9ab6e42e739fe826b02a5f868586132ea2d893a

It fixes commit e34fd4bfa6d7 ("PR ld/24600: BFD: Add general linker support
for mapless archives"), which is part of 2.47. The commit applies cleanly on
top of `binutils-2_47-branch` (checked with `git merge-tree`).

The commit message says it was found in the context of CVE-2026-19548.
However, that CVE describes a different use-after-free (in
`plugin_maybe_claim`).

## Workaround

Avoid `--start-lib`/`--end-lib`, e.g. with Bazel:

```
--features=-supports_start_end_lib --host_features=-supports_start_end_lib
```

## Environment

- Ubuntu 26.10 (stonking), amd64
- binutils 2.47-6ubuntu1
- gcc 15.3.0-4ubuntu1

** Affects: binutils (Ubuntu)
     Importance: Undecided
         Status: New

-- 
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2172236

Title:
  ld segfaults with --start-lib/--end-lib (use-after-free in
  _bfd_load_armap), please backport upstream fix d9ab6e42

To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/binutils/+bug/2172236/+subscriptions


-- 
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs

Reply via email to