https://sourceware.org/bugzilla/show_bug.cgi?id=34273
Bug ID: 34273
Summary: Out-of-bounds reads in libsframe
Product: binutils
Version: unspecified
Status: UNCONFIRMED
Severity: minor
Priority: P2
Component: binutils
Assignee: unassigned at sourceware dot org
Reporter: luigino.camastra at aisle dot com
Target Milestone: ---
Dear Binutils/GDB maintainers,
I am writing to report a security issue in **libsframe**
(`libsframe/sframe.c`), the library used to decode SFrame unwind metadata. The
bug was found by fuzzing the public API `sframe_decode()`.
## Summary
`sframe_decode()` uses header offset and length fields to index the
caller-supplied buffer without validating them against the buffer size
`sf_size`. A crafted `.sframe` section can trigger **out-of-bounds reads**
(crash / denial of service). In assertion-enabled builds, a crafted `fre_type`
can also reach `sframe_assert(0)` and **abort** the process.
We did **not** observe memory corruption or out-of-bounds writes; impact
appears limited to DoS.
- **Component:** `libsframe/sframe.c` (binutils-gdb)
- **Observed in:** `gdb-17_1-5_1` source tree (libsframe as shipped with that
release)
- **Type:** Out-of-bounds reads (CWE-125); optional abort via failed assertion
(CWE-617)
- **Severity (our assessment):** Medium — remote/network DoS is unlikely; local
DoS when linking untrusted objects is plausible
## Root cause
The only pre-read validation is `sframe_header_sanity_check_p()` (around line
206), which checks magic, version, flags, and `sfh_fdeoff <= sfh_freoff`. It
does **not** bound `sfh_fdeoff`, `sfh_freoff`, `sfh_fre_len`, or `sfh_num_fdes`
against `sf_size`.
Unchecked uses follow immediately, for example:
```c
fidx_size = sfheaderp->sfh_num_fdes * sizeof (sframe_func_desc_entry);
memcpy (dctx->sfd_funcdesc, frame_buf + sfheaderp->sfh_fdeoff, fidx_size);
memcpy (dctx->sfd_fres,
frame_buf + sfheaderp->sfh_freoff,
sfheaderp->sfh_fre_len);
```
Additionally, `fidx_size` is computed as a signed `int` and can overflow (e.g.
`num_fdes = 0x10000001`), producing a bogus allocation size before the OOB
`memcpy`.
## Reachability
In the tree we analyzed, the only caller of `sframe_decode()` is **bfd**, on
the **linker** path:
- `bfd/elf-sframe.c` — `_bfd_elf_parse_sframe()` passes the raw `.sframe`
section to `sframe_decode()`
- `bfd/elflink.c` — `bfd_elf_discard_info()` (linker `.sframe` merge/discard
pass)
**Practical attack surface:** `ld` and other tools that perform a bfd ELF link
merging `.sframe` input sections from attacker-controlled object files or
archives.
**gdb 17.1 executable:** not affected in our analysis — nothing under `gdb/`
calls `sframe_decode`, and gdb's bfd usage does not reach
`bfd_elf_discard_info`. Other binutils consumers (`readelf --sframe`,
`objdump`, etc.) may also call libsframe depending on build configuration.
## Crash sites (single underlying issue)
1. `sframe.c:985` — FRE `memcpy` with unbounded `sfh_freoff` / `sfh_fre_len`
2. `sframe.c:973` — FDE `memcpy` with unbounded `sfh_fdeoff`; integer overflow
on `fidx_size`
3. `sframe.c:443` — foreign-endian `flip_fre`: pointer advanced without bounds
check
4. `sframe.c:809` — OOB read via `sframe_decoder_get_fre()`
5. `sframe.c:278` — invalid `fre_type` → `sframe_assert(0)` → `abort()`
(assertion-enabled builds only)
## Proof-of-concept
Minimal reproducer calling the public API:
```c
#include <stdio.h>
#include <stdlib.h>
#include "sframe-api.h"
int main(int argc, char **argv) {
FILE *f = fopen(argv[1], "rb");
fseek(f, 0, SEEK_END); long n = ftell(f); fseek(f, 0, SEEK_SET);
char *buf = malloc(n);
fread(buf, 1, n, f); fclose(f);
int err = 0;
sframe_decoder_ctx *dctx = sframe_decode(buf, n, &err);
if (dctx) sframe_decoder_free(&dctx);
free(buf);
return 0;
}
```
Build with AddressSanitizer against libsframe, then run a crafted input.
Example (46 bytes, FRE OOB — `freoff=55552`, `fre_len=13`):
```
e2 de 02 01 03 00 f8 00 01 00 00 00 04 00 00 00 0d 00 00 00 00 00 00 00 00 d9
00 00
```
```sh
printf
'\xe2\xde\x02\x01\x03\x00\xf8\x00\x01\x00\x00\x00\x04\x00\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00\xd9\x00\x00'
> crash_fre
./repro crash_fre
```
Confirmed locally (gcc 12, `-fsanitize=address`):
```
ERROR: AddressSanitizer: heap-buffer-overflow ... READ of size 20
#1 sframe_decode libsframe/sframe.c:973
located 0 bytes to the right of 28-byte region
```
Additional crash inputs (FDE OOB, foreign-endian flip path) are available on
request.
## Suggested fix
Before any read from `frame_buf`, validate all header extents against `sf_size`
using non-overflowing arithmetic, e.g.:
- `hdrsz + sfh_fdeoff + sfh_num_fdes * sizeof(sframe_func_desc_entry) <=
sf_size`
- `hdrsz + sfh_freoff + sfh_fre_len <= sf_size`
Also:
- Bound-check `fp` in `flip_sframe` / `flip_fre` before dereferencing
- Replace the `sframe_assert(0)` on parsed `fre_type` with a graceful
`SFRAME_ERR_*` return so untrusted input cannot abort regardless of `NDEBUG`
We have not verified whether upstream binutils already contains bounds-check
hardening for this code path; we would appreciate confirmation.
CREDIT:
Best regards,
Luigino Camastra Aisle Research
--
You are receiving this mail because:
You are on the CC list for the bug.