https://sourceware.org/bugzilla/show_bug.cgi?id=34658
Bug ID: 34658
Summary: Heap-buffer-overflow write in S-record section loading
(`bfd/srec.c`)
Product: binutils
Version: 2.47
Status: UNCONFIRMED
Severity: normal
Priority: P2
Component: binutils
Assignee: unassigned at sourceware dot org
Reporter: hdzhao214 at gmail dot com
Target Milestone: ---
Created attachment 17017
--> https://sourceware.org/bugzilla/attachment.cgi?id=17017&action=edit
The `artifacts.zip` package includes the PoC generation script, the sanitizer
report, the bug report, and the candidate patch
## Vulnerability description
S-record input is scanned to calculate section sizes and then parsed again by
`srec_read_section`. The second pass trusts the earlier result and does not
bound a data record against the destination section. A concurrent replacement
of a scanned record's byte count lets the second pass write more decoded bytes
than the allocation holds.
```c
while (bytes-- != 0)
{
contents[sofar] = HEX (data);
sofar++;
data += 2;
}
```
The supplied modifier changes an S1 record after the scan has allocated a
4,096-byte section. The read pass accepts the altered 255-byte count and
overflows that allocation.
## Version and commit
GNU Binutils 2.47.50, commit `d715260f420066befb2d30ec8f5befcdf7ecfd84`
(2026-09-08).
## Environment
Ubuntu 24.04.4 LTS, x86_64, Linux 6.8.0-136-generic; GCC 13.3.0 and Python
3.12.3. The target was built with AddressSanitizer. The race input uses an
approximately 300 MB tail.
## Steps to reproduce
1. Install build prerequisites (for example, on Ubuntu):
```sh
sudo apt-get update
sudo apt-get install -y build-essential bison flex texinfo python3 \\
libgmp-dev libmpfr-dev libmpc-dev zlib1g-dev
```
2. Obtain the affected revision and make an AddressSanitizer build:
```sh
export SRC="$PWD/binutils-gdb"
git clone https://sourceware.org/git/binutils-gdb.git "$SRC"
git -C "$SRC" checkout d715260f420066befb2d30ec8f5befcdf7ecfd84
mkdir "$SRC/build-asan" && cd "$SRC/build-asan"
CC=gcc CFLAGS='-O0 -g3 -fsanitize=address -fno-omit-frame-pointer' \\
LDFLAGS='-fsanitize=address' \\
"$SRC/configure" --disable-gdb --disable-gdbserver --disable-sim \\
--disable-gprofng --disable-gold --disable-werror --disable-nls
make -j"$(nproc)" all-binutils
export BUILD="$SRC/build-asan"
```
3. Place `gen_srec.py` and `race_modifier_srec.py` in a writable directory and
create the initial input:
```sh
export WORK="$PWD/poc-work"
mkdir -p "$WORK"
python3 gen_srec.py "$WORK/sample.srec" 300000000
```
4. Start the reader, replace the already-scanned record, and wait for it:
```sh
ASAN_OPTIONS=detect_leaks=0:abort_on_error=1 \\
"$BUILD/binutils/objdump" -s -I srec "$WORK/sample.srec" \\
>"$WORK/asan.txt" 2>&1 &
pid=$!
python3 race_modifier_srec.py "$pid" "$WORK/sample.srec"
wait "$pid"
```
## Sanitizer report
The following is the complete, unmodified contents of `sanitizer_report.txt`.
```text
=================================================================
==250902==ERROR: AddressSanitizer: heap-buffer-overflow on address
0x52100000b110 at pc 0x5602f713c5dd bp 0x7ffea396f760 sp 0x7ffea396f750
WRITE of size 1 at 0x52100000b110 thread T0
#0 0x5602f713c5dc in srec_read_section ../../bfd/srec.c:797
#1 0x5602f713c92f in srec_get_section_contents ../../bfd/srec.c:845
#2 0x5602f712a174 in bfd_get_section_contents ../../bfd/section.c:1615
#3 0x5602f71170f0 in bfd_is_section_compressed_info
../../bfd/compress.c:901
#4 0x5602f711752f in bfd_is_section_compressed ../../bfd/compress.c:959
#5 0x5602f6fc471e in dump_section ../../binutils/objdump.c:5193
#6 0x5602f7129924 in bfd_map_over_sections ../../bfd/section.c:1369
#7 0x5602f6fc4f70 in dump_data ../../binutils/objdump.c:5276
#8 0x5602f6fc809c in dump_bfd ../../binutils/objdump.c:5908
#9 0x5602f6fc8374 in display_object_bfd ../../binutils/objdump.c:5971
#10 0x5602f6fc8696 in display_any_bfd ../../binutils/objdump.c:6050
#11 0x5602f6fc8706 in display_file ../../binutils/objdump.c:6071
#12 0x5602f6fca222 in main ../../binutils/objdump.c:6494
#13 0x7cca89c2a1c9 in __libc_start_call_main
../sysdeps/nptl/libc_start_call_main.h:58
#14 0x7cca89c2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#15 0x5602f6fad374 in _start
(/home/shootduck/hotracer-experiments/0-day/hotracer/pilot/objdump/2026-09-08-2.47.50-d715260/binutils-gdb/build-asan/binutils/objdump+0x143374)
(BuildId: 7b3b22cfe8266150e71d8e259cd00c3996daee41)
0x52100000b110 is located 0 bytes after 4112-byte region
[0x52100000a100,0x52100000b110)
allocated by thread T0 here:
#0 0x7cca8a0fd9c7 in malloc
../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x5602f73d2858 in _objalloc_alloc ../../libiberty/objalloc.c:143
#2 0x5602f711f501 in bfd_alloc ../../bfd/libbfd.c:453
#3 0x5602f713c89d in srec_get_section_contents ../../bfd/srec.c:841
#4 0x5602f712a174 in bfd_get_section_contents ../../bfd/section.c:1615
#5 0x5602f71170f0 in bfd_is_section_compressed_info
../../bfd/compress.c:901
#6 0x5602f711752f in bfd_is_section_compressed ../../bfd/compress.c:959
#7 0x5602f6fc471e in dump_section ../../binutils/objdump.c:5193
#8 0x5602f7129924 in bfd_map_over_sections ../../bfd/section.c:1369
#9 0x5602f6fc4f70 in dump_data ../../binutils/objdump.c:5276
#10 0x5602f6fc809c in dump_bfd ../../binutils/objdump.c:5908
#11 0x5602f6fc8374 in display_object_bfd ../../binutils/objdump.c:5971
#12 0x5602f6fc8696 in display_any_bfd ../../binutils/objdump.c:6050
#13 0x5602f6fc8706 in display_file ../../binutils/objdump.c:6071
#14 0x5602f6fca222 in main ../../binutils/objdump.c:6494
#15 0x7cca89c2a1c9 in __libc_start_call_main
../sysdeps/nptl/libc_start_call_main.h:58
#16 0x7cca89c2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#17 0x5602f6fad374 in _start
(/home/shootduck/hotracer-experiments/0-day/hotracer/pilot/objdump/2026-09-08-2.47.50-d715260/binutils-gdb/build-asan/binutils/objdump+0x143374)
(BuildId: 7b3b22cfe8266150e71d8e259cd00c3996daee41)
SUMMARY: AddressSanitizer: heap-buffer-overflow ../../bfd/srec.c:797 in
srec_read_section
Shadow bytes around the buggy address:
0x52100000ae80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x52100000af00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x52100000af80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x52100000b000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x52100000b080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x52100000b100: 00 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa
0x52100000b180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x52100000b200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x52100000b280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x52100000b300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x52100000b380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==250902==ABORTING
```
## Potential fix
Validate the current record's hexadecimal count and minimum address length,
then compare the decoded payload with the remaining section capacity before any
write. This also closes malformed-count underflows in the same parser.
```diff
diff --git a/bfd/srec.c b/bfd/srec.c
@@
- BFD_ASSERT (ISHEX (hdr[1]) && ISHEX (hdr[2]));
+ if (! ISHEX (hdr[1]) || ! ISHEX (hdr[2]))
+ goto error_return;
@@
+ if (bytes < min_bytes)
+ { bfd_set_error (bfd_error_bad_value); goto error_return; }
@@
+ if (sofar > section->size || bytes > section->size - sofar)
+ { bfd_set_error (bfd_error_bad_value); goto error_return; }
while (bytes-- != 0)
```
The complete, apply-ready patch is included as `proposed-fix.patch`. It was
applied to a disposable checkout of the stated commit and the supplied proof of
concept was rerun without an AddressSanitizer finding.
--
You are receiving this mail because:
You are on the CC list for the bug.