* [PATCH BlueZ] unit: test-bap: disable optimization to speed up compilation
From: Pauli Virtanen @ 2026-06-21 12:54 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Compilation of this file with optimization takes 12 min with ASAN, 1 min
without. This is too long, and the -O2 doesn't serve purpose for unit
tests.
Disable optimization to reduce to 30 sec with ASAN (5 sec without).
autoconf puts global -O2 in CFLAGS that cannot be overridden eg. with
unit_test_bap_CFLAGS. Use pragma instead.
---
unit/test-bap.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/unit/test-bap.c b/unit/test-bap.c
index 318b405a7..4e89ba6af 100644
--- a/unit/test-bap.c
+++ b/unit/test-bap.c
@@ -13,6 +13,12 @@
#include <config.h>
#endif
+#if defined(__GNUC__)
+/* Speed up compilation */
+#pragma GCC optimize ("O0")
+#undef _FORTIFY_SOURCE
+#endif
+
#define _GNU_SOURCE
#include <unistd.h>
#include <string.h>
--
2.54.0
^ permalink raw reply related
* RTL8852BE (0bda:b853) Bluetooth initialization fails with -16 on ThinkBook 14+ 2026
From: Misha @ 2026-06-21 6:23 UTC (permalink / raw)
To: linux-bluetooth
To: linux-bluetooth@vger.kernel.org
Subject: RTL8852BE (0bda:b853) Bluetooth initialization fails with -16
on ThinkBook 14+ 2026
I'm experiencing Bluetooth initialization failure on a Lenovo ThinkBook
14+ 2026 (Lenovo 14 G8+ AHP - version 2026, released for the Chinese
market) with Realtek RTL8852BE module (USB ID 0bda:b853). The WiFi part
works correctly via rtw89_8852be driver, but Bluetooth fails to
initialize.
Hardware:
- Laptop: Lenovo ThinkBook 14+ 2026 21VR (Chinese market version)
- CPU: AMD Ryzen 7 H 255
- Bluetooth: Realtek RTL8852BE (USB ID 0bda:b853)
- Kernel: 7.1.0-352.vanilla.fc44.x86_64
- Distribution: Fedora 44 Workstation
- linux-firmware: latest from Fedora 44 repos
Problem description:
The Bluetooth device is detected on USB bus (1-5), firmware (see logs
below) are loaded successfully, but initialization fails with
"Opcode 0xfcf0 failed: -16".
Key observations:
1. The USB device ID is 0bda:b853, which appears to be a newer revision of
RTL8852BE not listed in linux-firmware/rtl_bt/ directory
2. Available firmware files: rtl8852au_fw.bin, rtl8852btu_fw.bin,
rtl8852bu_fw.bin,
rtl8852cu_fw.bin - but NO firmware for revision "b853"
3. Driver attempts to load rtl8852bu_fw.bin as fallback, but chip rejects
the vendor command 0xfcf0
Commands output:
➤ lsusb
Bus 001 Device 002: ID 0bda:5411 Realtek Semiconductor Corp. RTS5411 Hub
Bus 001 Device 003: ID 0bda:b853 Realtek Semiconductor Corp.
Bluetooth Radio
➤ lsusb -t
/: Bus 001.Port 001: Dev 001, Class=root_hub, Driver=xhci_hcd/5p, 480M
|__ Port 002: Dev 002, If 0, Class=Hub, Driver=hub/2p, 480M
|__ Port 005: Dev 003, If 0, Class=Wireless, Driver=btusb, 12M
|__ Port 005: Dev 003, If 1, Class=Wireless, Driver=btusb, 12M
➤ sudo dmesg | grep -i rtw
[ 6.876631] rtw89_8852be 0000:05:00.0: loaded firmware
rtw89/rtw8852b_fw-2.bin
[ 6.876987] rtw89_8852be 0000:05:00.0: enabling device (0000 -> 0003)
[ 6.886529] rtw89_8852be 0000:05:00.0: Firmware version
0.29.29.15 (6fb3ec41), cmd version 0, type 5
[ 6.886547] rtw89_8852be 0000:05:00.0: Firmware version
0.29.29.15 (6fb3ec41), cmd version 0, type 3
[ 7.197141] rtw89_8852be 0000:05:00.0: Firmware element BB
version: 00 28 00 00
[ 7.197185] rtw89_8852be 0000:05:00.0: Firmware element radio A
version: 00 32 00 00
[ 7.197215] rtw89_8852be 0000:05:00.0: Firmware element NCTL
version: 00 0a 00 00
[ 7.197312] rtw89_8852be 0000:05:00.0: Firmware element TXPWR
version: 00 43 00 00
[ 7.197314] rtw89_8852be 0000:05:00.0: Firmware element TXPWR
version: 00 43 00 00
[ 7.197315] rtw89_8852be 0000:05:00.0: Firmware element TXPWR
version: 00 43 00 00
[ 7.197322] rtw89_8852be 0000:05:00.0: Firmware element PWR_TRK
version: 00 32 00 00
[ 7.197330] rtw89_8852be 0000:05:00.0: Firmware element REGD
version: 00 49 00 33
[ 7.197414] rtw89_8852be 0000:05:00.0: chip info CID: 0, CV: 1,
AID: 0, ACV: 1, RFE: 1
[ 7.200038] rtw89_8852be 0000:05:00.0: rfkill hardware state
changed to enable
[ 7.212982] rtw89_8852be 0000:05:00.0 wlp5s0: renamed from wlan0
➤ sudo dmesg | grep -iE 'bluetooth|firmware|mtk|btusb'
[ 0.272488] ACPI: [Firmware Bug]: BIOS _OSI(Linux) query ignored
[ 1.267765] usb 1-5: Product: Bluetooth Radio
[ 4.109424] amdgpu 0000:c6:00.0: [drm] Loading DMUB firmware via
PSP: version=0x08005B00
[ 4.109850] amdgpu 0000:c6:00.0: [VCN instance 0] Found VCN
firmware Version ENC: 1.24 DEC: 9 VEP: 0 Revision: 34
[ 5.075990] SELinux: Permission firmware_load in class system
not defined in policy.
[ 6.030638] systemd[1]: systemd-boot-clear-sysfail.service -
Clear SysFail Entry If The Boot Is Successful skipped, unmet condition
check
ConditionPathExists=/sys/firmware/efi/efivars/LoaderEntrySysFail-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f
[ 6.030683] systemd[1]: systemd-hibernate-clear.service - Clear
Stale Hibernate Storage Info skipped, unmet condition check
ConditionPathExists=/sys/firmware/efi/efivars/HibernateLocation-8cf2644b-4b0b-428f-9387-6d876050dc67
[ 6.751552] kvm_amd: [Firmware Bug]: Cannot enable x2AVIC, AVIC
is unsupported
[ 6.876631] rtw89_8852be 0000:05:00.0: loaded firmware
rtw89/rtw8852b_fw-2.bin
[ 6.886529] rtw89_8852be 0000:05:00.0: Firmware version
0.29.29.15 (6fb3ec41), cmd version 0, type 5
[ 6.886547] rtw89_8852be 0000:05:00.0: Firmware version
0.29.29.15 (6fb3ec41), cmd version 0, type 3
[ 7.197141] rtw89_8852be 0000:05:00.0: Firmware element BB
version: 00 28 00 00
[ 7.197185] rtw89_8852be 0000:05:00.0: Firmware element radio A
version: 00 32 00 00
[ 7.197215] rtw89_8852be 0000:05:00.0: Firmware element NCTL
version: 00 0a 00 00
[ 7.197312] rtw89_8852be 0000:05:00.0: Firmware element TXPWR
version: 00 43 00 00
[ 7.197314] rtw89_8852be 0000:05:00.0: Firmware element TXPWR
version: 00 43 00 00
[ 7.197315] rtw89_8852be 0000:05:00.0: Firmware element TXPWR
version: 00 43 00 00
[ 7.197322] rtw89_8852be 0000:05:00.0: Firmware element PWR_TRK
version: 00 32 00 00
[ 7.197330] rtw89_8852be 0000:05:00.0: Firmware element REGD
version: 00 49 00 33
[ 9114.511326] Bluetooth: Core ver 2.22
[ 9114.511401] NET: Registered PF_BLUETOOTH protocol family
[ 9114.511405] Bluetooth: HCI device and connection manager initialized
[ 9114.511415] Bluetooth: HCI socket layer initialized
[ 9114.511420] Bluetooth: L2CAP socket layer initialized
[ 9114.511430] Bluetooth: SCO socket layer initialized
[ 9649.478314] Bluetooth: BNEP (Ethernet Emulation) ver 1.3
[ 9649.478328] Bluetooth: BNEP filters: protocol multicast
[ 9649.478340] Bluetooth: BNEP socket layer initialized
[10359.251101] usbcore: registered new interface driver btusb
[10359.255740] Bluetooth: hci0: RTL: examining hci_ver=0b
hci_rev=000b lmp_ver=0b lmp_subver=8852
[10359.258669] Bluetooth: hci0: RTL: rom_version status=0 version=3
[10359.261690] Bluetooth: hci0: RTL: btrtl_initialize: key id 0
[10359.261700] Bluetooth: hci0: RTL: loading rtl_bt/rtl8852bu_fw.bin
[10359.273061] Bluetooth: hci0: RTL: loading rtl_bt/rtl8852bu_config.bin
[10359.279751] Bluetooth: hci0: Opcode 0xfcf0 failed: -16
[10359.282699] Bluetooth: hci0: AOSP extensions version v0.96
[10359.282710] Bluetooth: hci0: AOSP quality report is not supported
➤ rfkill list
0: ideapad_wlan: Wireless LAN
Soft blocked: no
Hard blocked: no
1: ideapad_bluetooth: Bluetooth
Soft blocked: no
Hard blocked: no
2: phy0: Wireless LAN
Soft blocked: no
Hard blocked: no
3: hci0: Bluetooth
Soft blocked: no
Hard blocked: no
➤ bluetoothctl show
No default controller available
➤ systemctl status bluetooth
...
Available
...
➤ inxi -Fxz
System:
Kernel: 7.1.0-352.vanilla.fc44.x86_64 arch: x86_64 bits: 64
compiler: gcc
Machine:
Type: Laptop System: LENOVO product: 21VR v: ThinkBook 14 G8+ AHP
Mobo: LENOVO model: LNVNB161216
v: TDCN24WW date: 04/14/2026
CPU:
Info: 8-core model: AMD Ryzen 7 H 255 w/ Radeon 780M Graphics bits: 64
Network:
Device-1: Realtek RTL8111/8168/8211/8411 PCI Express Gigabit Ethernet
vendor: Lenovo driver: r8169 v: kernel port: b000 bus-ID: 03:00.0
IF: enp3s0 state: down mac: <filter>
Device-2: Realtek RTL8852BE PCIe 802.11ax Wireless Network vendor:
Lenovo
driver: rtw89_8852be v: kernel port: a000 bus-ID: 05:00.0
IF: wlp5s0 state: up mac: <filter>
IF-ID-1: docker0 state: down mac: <filter>
Bluetooth:
Device-1: Realtek Bluetooth Radio driver: btusb v: 0.8 type: USB
bus-ID: 1-5:3
Report: hciconfig ID: hci0 rfk-id: 3 state: down
bt-service: enabled,running rfk-block: hardware: no software: no
address: <filter>
Troubleshooting attempts:
1. Performed full power drain - no effect
2. Updated linux-firmware from git.kernel.org - no rtl8852b853 firmware
found
3. Tested on kernels 7.0.x and 7.1.0 - same error
Additional context: Windows 11 is pre-installed on this laptop, WiFi and
Bluetooth works correctly in Windows. Fast Startup is disabled, Secure
Boot is disabled.
Questions:
1. Is USB ID 0bda:b853 a known revision of RTL8852BE that requires specific
firmware mapping in btrtl driver?
2. The driver loads the firmware, but the chip rejects command 0xfcf0.
Does this revision require a different firmware file?
3. Is there a way to force the driver to use a different firmware file for
this USB ID?
4. Should a new entry be added to the btrtl_device_id table in btrtl.c for
USB_DEVICE(0x0bda, 0xb853)?
I'm willing to provide additional logs, test patches, or send the device
for
debugging if needed.
Thank you for your work on the Bluetooth subsystem.
Best regards,
michail383krasnov@mail.ru
^ permalink raw reply
* RE: Bluetooth: L2CAP: validate option length before reading conf opt value
From: bluez.test.bot @ 2026-06-20 21:28 UTC (permalink / raw)
To: linux-bluetooth, meatuni001
In-Reply-To: <20260620195635.41765-1-meatuni001@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 1235 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1114248
---Test result---
Test Summary:
CheckPatch PASS 0.84 seconds
VerifyFixes PASS 0.16 seconds
VerifySignedoff PASS 0.16 seconds
GitLint PASS 0.37 seconds
SubjectPrefix PASS 0.16 seconds
BuildKernel PASS 26.86 seconds
CheckAllWarning PASS 29.46 seconds
CheckSparse PASS 28.43 seconds
BuildKernel32 PASS 26.20 seconds
CheckKernelLLVM SKIP 0.00 seconds
TestRunnerSetup PASS 576.66 seconds
TestRunner_l2cap-tester PASS 58.98 seconds
IncrementalBuild PASS 25.33 seconds
Details
##############################
Test: CheckKernelLLVM - SKIP
Desc: Build kernel with LLVM + context analysis
Output:
Clang not found
https://github.com/bluez/bluetooth-next/pull/335
---
Regards,
Linux Bluetooth
^ permalink raw reply
* RE: [BlueZ,1/2] shared: harden btsnoop trace parsing
From: bluez.test.bot @ 2026-06-20 21:09 UTC (permalink / raw)
To: linux-bluetooth, geraldonetto
In-Reply-To: <20260620192228.2692610-1-geraldonetto@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 990 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1114243
---Test result---
Test Summary:
CheckPatch PASS 1.68 seconds
GitLint PASS 0.51 seconds
BuildEll PASS 20.34 seconds
BluezMake PASS 667.04 seconds
MakeCheck PASS 18.36 seconds
MakeDistcheck PASS 251.81 seconds
CheckValgrind PASS 303.17 seconds
CheckSmatch PASS 354.29 seconds
bluezmakeextell PASS 184.13 seconds
IncrementalBuild PASS 699.32 seconds
ScanBuild PASS 1041.22 seconds
https://github.com/bluez/bluez/pull/2245
---
Regards,
Linux Bluetooth
^ permalink raw reply
* RE: audio: harden A2DP parser handling
From: bluez.test.bot @ 2026-06-20 21:07 UTC (permalink / raw)
To: linux-bluetooth, geraldonetto
In-Reply-To: <20260620191735.2675946-2-geraldonetto@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 990 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1114241
---Test result---
Test Summary:
CheckPatch PASS 1.10 seconds
GitLint PASS 0.66 seconds
BuildEll PASS 20.28 seconds
BluezMake PASS 655.58 seconds
MakeCheck PASS 18.46 seconds
MakeDistcheck PASS 246.91 seconds
CheckValgrind PASS 297.01 seconds
CheckSmatch PASS 350.72 seconds
bluezmakeextell PASS 181.74 seconds
IncrementalBuild PASS 662.39 seconds
ScanBuild PASS 1035.90 seconds
https://github.com/bluez/bluez/pull/2244
---
Regards,
Linux Bluetooth
^ permalink raw reply
* [bluez/bluez] cfc7fc: shared: harden btsnoop trace parsing
From: Geraldo Netto @ 2026-06-20 20:09 UTC (permalink / raw)
To: linux-bluetooth
Branch: refs/heads/1114243
Home: https://github.com/bluez/bluez
Commit: cfc7fc7c0b3c95c9445764d5a2d21323e803c32d
https://github.com/bluez/bluez/commit/cfc7fc7c0b3c95c9445764d5a2d21323e803c32d
Author: Geraldo Netto <geraldonetto@gmail.com>
Date: 2026-06-20 (Sat, 20 Jun 2026)
Changed paths:
M Makefile.am
M monitor/analyze.c
M monitor/control.c
M src/shared/btsnoop.c
M src/shared/btsnoop.h
A unit/test-btsnoop.c
A unit/test-btsnoop.h
Log Message:
-----------
shared: harden btsnoop trace parsing
Commit: ffadedbfd700d2cc08c39a6a0ffddba0184a72b0
https://github.com/bluez/bluez/commit/ffadedbfd700d2cc08c39a6a0ffddba0184a72b0
Author: Geraldo Netto <geraldonetto@gmail.com>
Date: 2026-06-20 (Sat, 20 Jun 2026)
Changed paths:
M Makefile.am
A unit/test-btsnoop-pklg.c
M unit/test-btsnoop.c
Log Message:
-----------
unit: split btsnoop pklg tests
Compare: https://github.com/bluez/bluez/compare/cfc7fc7c0b3c%5E...ffadedbfd700
To unsubscribe from these emails, change your notification settings at https://github.com/bluez/bluez/settings/notifications
^ permalink raw reply
* [bluez/bluez] 92081f: audio: harden a2dp parsers
From: Geraldo Netto @ 2026-06-20 20:09 UTC (permalink / raw)
To: linux-bluetooth
Branch: refs/heads/1114241
Home: https://github.com/bluez/bluez
Commit: 92081f8ff1f50f6acd338bea9222e9bebe6e319c
https://github.com/bluez/bluez/commit/92081f8ff1f50f6acd338bea9222e9bebe6e319c
Author: Geraldo Netto <geraldonetto@gmail.com>
Date: 2026-06-20 (Sat, 20 Jun 2026)
Changed paths:
M Makefile.am
M Makefile.plugins
A profiles/audio/a2dp-helpers.c
A profiles/audio/a2dp-helpers.h
M profiles/audio/a2dp.c
A unit/test-a2dp.c
Log Message:
-----------
audio: harden a2dp parsers
Commit: dc20a9c9a09e4dd566a0bfa5edde8be179545880
https://github.com/bluez/bluez/commit/dc20a9c9a09e4dd566a0bfa5edde8be179545880
Author: Geraldo Netto <geraldonetto@gmail.com>
Date: 2026-06-20 (Sat, 20 Jun 2026)
Changed paths:
M profiles/audio/a2dp-helpers.c
Log Message:
-----------
audio: reduce a2dp parser complexity
Compare: https://github.com/bluez/bluez/compare/92081f8ff1f5%5E...dc20a9c9a09e
To unsubscribe from these emails, change your notification settings at https://github.com/bluez/bluez/settings/notifications
^ permalink raw reply
* [PATCH] Bluetooth: L2CAP: validate option length before reading conf opt value
From: Muhammad Bilal @ 2026-06-20 19:56 UTC (permalink / raw)
To: linux-bluetooth
Cc: linux-kernel, Marcel Holtmann, Luiz Augusto von Dentz,
Muhammad Bilal, stable
l2cap_get_conf_opt() derives the option length from the
attacker-controlled opt->len field and immediately dereferences
opt->val (as u8, get_unaligned_le16() or get_unaligned_le32(), or a
raw pointer for the default case) before any caller has confirmed
that opt->len bytes are present in the buffer. The callers
(l2cap_parse_conf_req(), l2cap_parse_conf_rsp() and
l2cap_conf_rfc_get()) only detect a malformed option afterwards, once
the running length has gone negative, by which point the
out-of-bounds read has already executed.
An existing post-hoc length check keeps the garbage value from being
consumed, so this is not a data leak in the current control flow. It
is still a validate-after-use ordering bug: up to 4 bytes are read
past the end of the buffer before it is known to contain them, and it
is fragile to future changes in the callers.
Fix it at the source. Pass the end of the buffer into
l2cap_get_conf_opt() and refuse to touch opt->val unless the full
option (header + value) fits. Each caller computes an end pointer
once before the loop and checks the return value directly instead of
inferring the error from a negative length.
Fixes: 7c9cbd0b5e38 ("Bluetooth: Verify that l2cap_get_conf_opt provides large enough buffer")
Cc: stable@vger.kernel.org
Signed-off-by: Muhammad Bilal <meatuni001@gmail.com>
---
net/bluetooth/l2cap_core.c | 36 ++++++++++++++++++++++++++++--------
1 file changed, 28 insertions(+), 8 deletions(-)
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index c4ccfbda9d789..ebe44990a22e2 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -3052,13 +3052,24 @@ static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn, u8 code,
return NULL;
}
-static inline int l2cap_get_conf_opt(void **ptr, int *type, int *olen,
- unsigned long *val)
+static inline int l2cap_get_conf_opt(void **ptr, void *end, int *type,
+ int *olen, unsigned long *val)
{
struct l2cap_conf_opt *opt = *ptr;
int len;
+ /* opt->len is attacker-controlled. Validate that the full option
+ * (header + value) actually fits in the buffer before touching
+ * opt->val, otherwise the switch below reads past the end of the
+ * caller's buffer.
+ */
+ if (end - *ptr < L2CAP_CONF_OPT_SIZE)
+ return -EINVAL;
+
len = L2CAP_CONF_OPT_SIZE + opt->len;
+ if (end - *ptr < len)
+ return -EINVAL;
+
*ptr += len;
*type = opt->type;
@@ -3426,6 +3437,7 @@ static int l2cap_parse_conf_req(struct l2cap_chan *chan, void *data, size_t data
void *ptr = rsp->data;
void *endptr = data + data_size;
void *req = chan->conf_req;
+ void *req_end = req + chan->conf_len;
int len = chan->conf_len;
int type, hint, olen;
unsigned long val;
@@ -3439,9 +3451,11 @@ static int l2cap_parse_conf_req(struct l2cap_chan *chan, void *data, size_t data
BT_DBG("chan %p", chan);
while (len >= L2CAP_CONF_OPT_SIZE) {
- len -= l2cap_get_conf_opt(&req, &type, &olen, &val);
- if (len < 0)
+ int ret = l2cap_get_conf_opt(&req, req_end, &type, &olen, &val);
+
+ if (ret < 0)
break;
+ len -= ret;
hint = type & L2CAP_CONF_HINT;
type &= L2CAP_CONF_MASK;
@@ -3669,6 +3683,7 @@ static int l2cap_parse_conf_rsp(struct l2cap_chan *chan, void *rsp, int len,
struct l2cap_conf_req *req = data;
void *ptr = req->data;
void *endptr = data + size;
+ void *rsp_end = rsp + len;
int type, olen;
unsigned long val;
struct l2cap_conf_rfc rfc = { .mode = L2CAP_MODE_BASIC };
@@ -3677,9 +3692,11 @@ static int l2cap_parse_conf_rsp(struct l2cap_chan *chan, void *rsp, int len,
BT_DBG("chan %p, rsp %p, len %d, req %p", chan, rsp, len, data);
while (len >= L2CAP_CONF_OPT_SIZE) {
- len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val);
- if (len < 0)
+ int ret = l2cap_get_conf_opt(&rsp, rsp_end, &type, &olen, &val);
+
+ if (ret < 0)
break;
+ len -= ret;
switch (type) {
case L2CAP_CONF_MTU:
@@ -3930,6 +3947,7 @@ static void l2cap_conf_rfc_get(struct l2cap_chan *chan, void *rsp, int len)
{
int type, olen;
unsigned long val;
+ void *rsp_end = rsp + len;
/* Use sane default values in case a misbehaving remote device
* did not send an RFC or extended window size option.
*/
@@ -3948,9 +3966,11 @@ static void l2cap_conf_rfc_get(struct l2cap_chan *chan, void *rsp, int len)
return;
while (len >= L2CAP_CONF_OPT_SIZE) {
- len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val);
- if (len < 0)
+ int ret = l2cap_get_conf_opt(&rsp, rsp_end, &type, &olen, &val);
+
+ if (ret < 0)
break;
+ len -= ret;
switch (type) {
case L2CAP_CONF_RFC:
--
2.54.0
^ permalink raw reply related
* [PATCH BlueZ 2/2] unit: split btsnoop pklg tests
From: Geraldo Netto @ 2026-06-20 19:22 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Geraldo Netto
In-Reply-To: <20260620192228.2692610-1-geraldonetto@gmail.com>
---
Makefile.am | 3 +-
unit/test-btsnoop-pklg.c | 266 ++++++++++++++++++++++
unit/test-btsnoop.c | 462 +++++++++------------------------------
3 files changed, 374 insertions(+), 357 deletions(-)
create mode 100644 unit/test-btsnoop-pklg.c
diff --git a/Makefile.am b/Makefile.am
index 4887934a9..db63f3b07 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -589,7 +589,8 @@ unit_test_textfile_LDADD = src/libshared-glib.la $(GLIB_LIBS)
unit_tests += unit/test-btsnoop
-unit_test_btsnoop_SOURCES = unit/test-btsnoop.c \
+unit_test_btsnoop_SOURCES = unit/test-btsnoop.c unit/test-btsnoop.h \
+ unit/test-btsnoop-pklg.c \
src/shared/btsnoop.h src/shared/btsnoop.c
unit_test_btsnoop_LDADD = src/libshared-glib.la $(GLIB_LIBS)
diff --git a/unit/test-btsnoop-pklg.c b/unit/test-btsnoop-pklg.c
new file mode 100644
index 000000000..ff3c24354
--- /dev/null
+++ b/unit/test-btsnoop-pklg.c
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <endian.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+
+#include "src/shared/att-types.h"
+#include "src/shared/btsnoop.h"
+#include "unit/test-btsnoop.h"
+
+#define PKLG_PAYLOAD_OFFSET 9
+
+struct test_pklg_pkt {
+ uint32_t len;
+ uint64_t ts;
+ uint8_t type;
+} __packed;
+
+struct read_result {
+ uint8_t data[BTSNOOP_MAX_PACKET_SIZE];
+ uint16_t size;
+ uint16_t index;
+ uint16_t opcode;
+ struct timeval tv;
+};
+
+static void read_result_init(struct read_result *result)
+{
+ memset(result->data, 0xa5, sizeof(result->data));
+ result->size = 0;
+ result->index = 0xffff;
+ result->opcode = 0xffff;
+ memset(&result->tv, 0, sizeof(result->tv));
+}
+
+static void append_bytes(GByteArray *array, const void *data, size_t size)
+{
+ if (size)
+ g_byte_array_append(array, data, size);
+}
+
+static void append_pklg_packet(GByteArray *array, bool little_endian,
+ uint32_t payload_len, uint64_t ts,
+ uint8_t type, const void *data, size_t data_len)
+{
+ struct test_pklg_pkt pkt;
+ uint32_t len = PKLG_PAYLOAD_OFFSET + payload_len;
+
+ pkt.len = little_endian ? htole32(len) : htobe32(len);
+ pkt.ts = little_endian ? htole64(ts) : htobe64(ts);
+ pkt.type = type;
+
+ append_bytes(array, &pkt, sizeof(pkt));
+ append_bytes(array, data, data_len);
+}
+
+static char *write_tmp_trace(const void *data, size_t size)
+{
+ char *path = NULL;
+ ssize_t written;
+ int fd;
+
+ fd = g_file_open_tmp("bluez-btsnoop-XXXXXX", &path, NULL);
+ g_assert(fd >= 0);
+ written = write(fd, data, size);
+ g_assert_cmpint(written, ==, (ssize_t) size);
+ g_assert_cmpint(close(fd), ==, 0);
+
+ return path;
+}
+
+static bool read_tmp_trace(const void *trace, size_t trace_len,
+ uint16_t data_size, struct read_result *result)
+{
+ struct btsnoop *btsnoop;
+ char *path;
+ bool ok;
+
+ read_result_init(result);
+ path = write_tmp_trace(trace, trace_len);
+ btsnoop = btsnoop_open(path, BTSNOOP_FLAG_PKLG_SUPPORT);
+ unlink(path);
+ g_free(path);
+
+ if (!btsnoop)
+ return false;
+
+ ok = btsnoop_read_hci(btsnoop, &result->tv, &result->index,
+ &result->opcode, result->data, data_size,
+ &result->size);
+ btsnoop_unref(btsnoop);
+
+ return ok;
+}
+
+static void test_pklg_big_endian_valid(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { 0x0e, 0x01, 0x00 };
+ struct read_result result;
+
+ append_pklg_packet(trace, false, sizeof(payload),
+ ((uint64_t) 123 << 32) | 456, 0x01, payload,
+ sizeof(payload));
+
+ g_assert_true(read_tmp_trace(trace->data, trace->len, sizeof(payload),
+ &result));
+ g_assert_cmpint(result.index, ==, 0);
+ g_assert_cmpint(result.opcode, ==, BTSNOOP_OPCODE_EVENT_PKT);
+ g_assert_cmpint(result.size, ==, sizeof(payload));
+ g_assert_cmpint(memcmp(result.data, payload, sizeof(payload)), ==, 0);
+ g_assert_cmpint(result.tv.tv_sec, ==, 123);
+ g_assert_cmpint(result.tv.tv_usec, ==, 456);
+
+ g_byte_array_unref(trace);
+}
+
+static void test_pklg_little_endian_valid(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+ struct read_result result;
+
+ append_pklg_packet(trace, true, sizeof(payload),
+ ((uint64_t) 456 << 32) | 123, 0x00, payload,
+ sizeof(payload));
+
+ g_assert_true(read_tmp_trace(trace->data, trace->len, sizeof(payload),
+ &result));
+ g_assert_cmpint(result.index, ==, 0);
+ g_assert_cmpint(result.opcode, ==, BTSNOOP_OPCODE_COMMAND_PKT);
+ g_assert_cmpint(result.size, ==, sizeof(payload));
+ g_assert_cmpint(result.data[0], ==, payload[0]);
+ g_assert_cmpint(result.tv.tv_sec, ==, 123);
+ g_assert_cmpint(result.tv.tv_usec, ==, 456);
+
+ g_byte_array_unref(trace);
+}
+
+static void test_pklg_rejects_short_length(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ struct test_pklg_pkt pkt;
+ const uint8_t padding[] = { 0x00, 0x00, 0x00 };
+ struct read_result result;
+
+ pkt.len = htobe32(PKLG_PAYLOAD_OFFSET - 1);
+ pkt.ts = 0;
+ pkt.type = 0x01;
+
+ append_bytes(trace, &pkt, sizeof(pkt));
+ append_bytes(trace, padding, sizeof(padding));
+
+ g_assert_false(read_tmp_trace(trace->data, trace->len, 0, &result));
+
+ g_byte_array_unref(trace);
+}
+
+static void test_pklg_rejects_small_capacity(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+ struct read_result result;
+
+ append_pklg_packet(trace, false, sizeof(payload), 0, 0x01, payload,
+ sizeof(payload));
+
+ g_assert_false(read_tmp_trace(trace->data, trace->len, 2, &result));
+ g_assert_cmpint(result.data[0], ==, 0xa5);
+ g_assert_cmpint(result.data[1], ==, 0xa5);
+ g_assert_cmpint(result.data[2], ==, 0xa5);
+ g_assert_cmpint(result.data[3], ==, 0xa5);
+
+ g_byte_array_unref(trace);
+}
+
+static void test_pklg_rejects_short_payload(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+ struct read_result result;
+
+ append_pklg_packet(trace, false, 4, 0, 0x01, payload,
+ sizeof(payload));
+
+ g_assert_false(read_tmp_trace(trace->data, trace->len, 4, &result));
+
+ g_byte_array_unref(trace);
+}
+
+static void test_pklg_type_map(void)
+{
+ static const struct {
+ uint8_t type;
+ uint16_t index;
+ uint16_t opcode;
+ } cases[] = {
+ { 0x02, 0x0000, BTSNOOP_OPCODE_ACL_TX_PKT },
+ { 0x03, 0x0000, BTSNOOP_OPCODE_ACL_RX_PKT },
+ { 0x08, 0x0000, BTSNOOP_OPCODE_SCO_TX_PKT },
+ { 0x09, 0x0000, BTSNOOP_OPCODE_SCO_RX_PKT },
+ { 0x12, 0x0000, BTSNOOP_OPCODE_ISO_TX_PKT },
+ { 0x13, 0x0000, BTSNOOP_OPCODE_ISO_RX_PKT },
+ { 0x0b, 0x0000, BTSNOOP_OPCODE_VENDOR_DIAG },
+ { 0xfc, 0xffff, BTSNOOP_OPCODE_SYSTEM_NOTE },
+ { 0xaa, 0xffff, 0xffff },
+ };
+ const uint8_t payload[] = { 0x00, 0x01, 0x02 };
+ unsigned int i;
+
+ for (i = 0; i < G_N_ELEMENTS(cases); i++) {
+ GByteArray *trace = g_byte_array_new();
+ struct read_result result;
+
+ append_pklg_packet(trace, false, sizeof(payload), 0,
+ cases[i].type, payload,
+ sizeof(payload));
+
+ g_assert_true(read_tmp_trace(trace->data, trace->len,
+ sizeof(payload), &result));
+ g_assert_cmpint(result.index, ==, cases[i].index);
+ g_assert_cmpint(result.opcode, ==, cases[i].opcode);
+ g_byte_array_unref(trace);
+ }
+}
+
+static void test_pklg_truncation_fuzz(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+ size_t len;
+
+ append_pklg_packet(trace, false, sizeof(payload), 0, 0x01, payload,
+ sizeof(payload));
+
+ for (len = 0; len < trace->len; len++) {
+ struct read_result result;
+
+ g_assert_false(read_tmp_trace(trace->data, len, sizeof(payload),
+ &result));
+ }
+
+ g_byte_array_unref(trace);
+}
+
+void add_pklg_tests(void)
+{
+ g_test_add_func("/pklg/big-endian/valid", test_pklg_big_endian_valid);
+ g_test_add_func("/pklg/little-endian/valid",
+ test_pklg_little_endian_valid);
+ g_test_add_func("/pklg/length/short", test_pklg_rejects_short_length);
+ g_test_add_func("/pklg/capacity/reject",
+ test_pklg_rejects_small_capacity);
+ g_test_add_func("/pklg/payload/short", test_pklg_rejects_short_payload);
+ g_test_add_func("/pklg/type-map", test_pklg_type_map);
+ g_test_add_func("/pklg/fuzz/truncation", test_pklg_truncation_fuzz);
+}
diff --git a/unit/test-btsnoop.c b/unit/test-btsnoop.c
index 710209097..fa7587d1a 100644
--- a/unit/test-btsnoop.c
+++ b/unit/test-btsnoop.c
@@ -18,7 +18,6 @@
#include "unit/test-btsnoop.h"
#define BTSNOOP_EPOCH_OFFSET 0x00E03AB44A676000ull
-#define PKLG_PAYLOAD_OFFSET 9
struct test_btsnoop_hdr {
uint8_t id[8];
@@ -34,22 +33,31 @@ struct test_btsnoop_pkt {
uint64_t ts;
} __packed;
-struct test_pklg_pkt {
- uint32_t len;
- uint64_t ts;
- uint8_t type;
-} __packed;
+struct read_result {
+ uint8_t data[BTSNOOP_MAX_PACKET_SIZE];
+ uint16_t size;
+ uint16_t index;
+ uint16_t opcode;
+ struct timeval tv;
+};
static const uint8_t btsnoop_id[] = {
0x62, 0x74, 0x73, 0x6e, 0x6f, 0x6f, 0x70, 0x00
};
-static void append_bytes(GByteArray *array, const void *data, size_t size)
+static void read_result_init(struct read_result *result)
{
- if (!size)
- return;
+ memset(result->data, 0xa5, sizeof(result->data));
+ result->size = 0;
+ result->index = 0xffff;
+ result->opcode = 0xffff;
+ memset(&result->tv, 0, sizeof(result->tv));
+}
- g_byte_array_append(array, data, size);
+static void append_bytes(GByteArray *array, const void *data, size_t size)
+{
+ if (size)
+ g_byte_array_append(array, data, size);
}
static void append_btsnoop_header(GByteArray *array, uint32_t format)
@@ -79,21 +87,6 @@ static void append_btsnoop_packet(GByteArray *array, uint32_t len,
append_bytes(array, data, data_len);
}
-static void append_pklg_packet(GByteArray *array, bool little_endian,
- uint32_t payload_len, uint64_t ts,
- uint8_t type, const void *data, size_t data_len)
-{
- struct test_pklg_pkt pkt;
- uint32_t len = PKLG_PAYLOAD_OFFSET + payload_len;
-
- pkt.len = little_endian ? htole32(len) : htobe32(len);
- pkt.ts = little_endian ? htole64(ts) : htobe64(ts);
- pkt.type = type;
-
- append_bytes(array, &pkt, sizeof(pkt));
- append_bytes(array, data, data_len);
-}
-
static char *write_tmp_trace(const void *data, size_t size)
{
char *path = NULL;
@@ -127,9 +120,8 @@ static void unlink_rotated(const char *path, unsigned int count)
unsigned int i;
for (i = 0; i <= count; i++) {
- char *name;
+ char *name = g_strdup_printf("%s.%u", path, i);
- name = g_strdup_printf("%s.%u", path, i);
unlink(name);
g_free(name);
}
@@ -137,14 +129,13 @@ static void unlink_rotated(const char *path, unsigned int count)
static bool read_tmp_trace(const void *trace, size_t trace_len,
unsigned long flags, uint16_t data_size,
- uint8_t *data, uint16_t *size,
- uint16_t *index, uint16_t *opcode,
- struct timeval *tv)
+ struct read_result *result)
{
struct btsnoop *btsnoop;
char *path;
- bool result;
+ bool ok;
+ read_result_init(result);
path = write_tmp_trace(trace, trace_len);
btsnoop = btsnoop_open(path, flags);
unlink(path);
@@ -153,54 +144,51 @@ static bool read_tmp_trace(const void *trace, size_t trace_len,
if (!btsnoop)
return false;
- result = btsnoop_read_hci(btsnoop, tv, index, opcode, data,
- data_size, size);
+ ok = btsnoop_read_hci(btsnoop, &result->tv, &result->index,
+ &result->opcode, result->data, data_size,
+ &result->size);
btsnoop_unref(btsnoop);
- return result;
+ return ok;
}
static bool read_trace_file(const char *path, unsigned long flags,
- uint8_t *data, uint16_t data_size,
- uint16_t *size, uint16_t *index,
- uint16_t *opcode, struct timeval *tv)
+ uint16_t data_size, struct read_result *result)
{
struct btsnoop *btsnoop;
- bool result;
+ bool ok;
+ read_result_init(result);
btsnoop = btsnoop_open(path, flags);
g_assert_nonnull(btsnoop);
- result = btsnoop_read_hci(btsnoop, tv, index, opcode, data,
- data_size, size);
+ ok = btsnoop_read_hci(btsnoop, &result->tv, &result->index,
+ &result->opcode, result->data, data_size,
+ &result->size);
btsnoop_unref(btsnoop);
- return result;
+ return ok;
}
static void test_btsnoop_hci_valid(void)
{
GByteArray *trace = g_byte_array_new();
const uint8_t payload[] = { 0x01, 0x02, 0x03 };
- uint8_t data[sizeof(payload)];
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0xffff;
- uint16_t opcode = 0xffff;
+ struct read_result result;
append_btsnoop_header(trace, BTSNOOP_FORMAT_HCI);
append_btsnoop_packet(trace, sizeof(payload), 0x02,
BTSNOOP_EPOCH_OFFSET + 1234567, payload,
sizeof(payload));
- g_assert_true(read_tmp_trace(trace->data, trace->len, 0, sizeof(data),
- data, &size, &index, &opcode, &tv));
- g_assert_cmpint(index, ==, 0);
- g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_COMMAND_PKT);
- g_assert_cmpint(size, ==, sizeof(payload));
- g_assert_cmpint(memcmp(data, payload, sizeof(payload)), ==, 0);
- g_assert_cmpint(tv.tv_sec, ==, 946684801);
- g_assert_cmpint(tv.tv_usec, ==, 234567);
+ g_assert_true(read_tmp_trace(trace->data, trace->len, 0,
+ sizeof(payload), &result));
+ g_assert_cmpint(result.index, ==, 0);
+ g_assert_cmpint(result.opcode, ==, BTSNOOP_OPCODE_COMMAND_PKT);
+ g_assert_cmpint(result.size, ==, sizeof(payload));
+ g_assert_cmpint(memcmp(result.data, payload, sizeof(payload)), ==, 0);
+ g_assert_cmpint(result.tv.tv_sec, ==, 946684801);
+ g_assert_cmpint(result.tv.tv_usec, ==, 234567);
g_byte_array_unref(trace);
}
@@ -247,12 +235,9 @@ static void test_btsnoop_write_hci_roundtrip(void)
{
const uint8_t command[] = { 0x01, 0x02, 0x03 };
const uint8_t event[] = { 0x04, 0x05 };
- uint8_t data[sizeof(command)];
struct btsnoop *btsnoop;
+ struct read_result result;
struct timeval tv = { .tv_sec = 946684802, .tv_usec = 345678 };
- uint16_t size = 0;
- uint16_t index = 0;
- uint16_t opcode = 0;
char *path = new_tmp_path();
btsnoop = btsnoop_create(path, 0, 0, BTSNOOP_FORMAT_HCI);
@@ -272,24 +257,30 @@ static void test_btsnoop_write_hci_roundtrip(void)
command, sizeof(command)));
btsnoop_unref(btsnoop);
- g_assert_true(read_trace_file(path, 0, data, sizeof(data), &size,
- &index, &opcode, &tv));
- g_assert_cmpint(index, ==, 0);
- g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_COMMAND_PKT);
- g_assert_cmpint(size, ==, sizeof(command));
- g_assert_cmpint(memcmp(data, command, sizeof(command)), ==, 0);
+ g_assert_true(read_trace_file(path, 0, sizeof(command), &result));
+ g_assert_cmpint(result.index, ==, 0);
+ g_assert_cmpint(result.opcode, ==, BTSNOOP_OPCODE_COMMAND_PKT);
+ g_assert_cmpint(result.size, ==, sizeof(command));
+ g_assert_cmpint(memcmp(result.data, command, sizeof(command)), ==, 0);
btsnoop = btsnoop_open(path, 0);
g_assert_nonnull(btsnoop);
- g_assert_true(btsnoop_read_hci(btsnoop, &tv, &index, &opcode, data,
- sizeof(data), &size));
- g_assert_true(btsnoop_read_hci(btsnoop, &tv, &index, &opcode, data,
- sizeof(data), &size));
- g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_EVENT_PKT);
- g_assert_cmpint(size, ==, sizeof(event));
- g_assert_cmpint(memcmp(data, event, sizeof(event)), ==, 0);
- g_assert_false(btsnoop_read_hci(btsnoop, &tv, &index, &opcode, data,
- sizeof(data), &size));
+ read_result_init(&result);
+ g_assert_true(btsnoop_read_hci(btsnoop, &result.tv,
+ &result.index, &result.opcode,
+ result.data, sizeof(result.data),
+ &result.size));
+ g_assert_true(btsnoop_read_hci(btsnoop, &result.tv,
+ &result.index, &result.opcode,
+ result.data, sizeof(result.data),
+ &result.size));
+ g_assert_cmpint(result.opcode, ==, BTSNOOP_OPCODE_EVENT_PKT);
+ g_assert_cmpint(result.size, ==, sizeof(event));
+ g_assert_cmpint(memcmp(result.data, event, sizeof(event)), ==, 0);
+ g_assert_false(btsnoop_read_hci(btsnoop, &result.tv,
+ &result.index, &result.opcode,
+ result.data, sizeof(result.data),
+ &result.size));
btsnoop_unref(btsnoop);
unlink(path);
@@ -299,12 +290,9 @@ static void test_btsnoop_write_hci_roundtrip(void)
static void test_btsnoop_write_monitor_roundtrip(void)
{
const uint8_t payload[] = { 0xaa, 0xbb };
- uint8_t data[sizeof(payload)];
struct btsnoop *btsnoop;
+ struct read_result result;
struct timeval tv = { .tv_sec = 946684800, .tv_usec = 0 };
- uint16_t size = 0;
- uint16_t index = 0;
- uint16_t opcode = 0;
char *path = new_tmp_path();
btsnoop = btsnoop_create(path, 0, 0, BTSNOOP_FORMAT_MONITOR);
@@ -313,12 +301,11 @@ static void test_btsnoop_write_monitor_roundtrip(void)
payload, sizeof(payload)));
btsnoop_unref(btsnoop);
- g_assert_true(read_trace_file(path, 0, data, sizeof(data), &size,
- &index, &opcode, &tv));
- g_assert_cmpint(index, ==, 7);
- g_assert_cmpint(opcode, ==, 0x1234);
- g_assert_cmpint(size, ==, sizeof(payload));
- g_assert_cmpint(memcmp(data, payload, sizeof(payload)), ==, 0);
+ g_assert_true(read_trace_file(path, 0, sizeof(payload), &result));
+ g_assert_cmpint(result.index, ==, 7);
+ g_assert_cmpint(result.opcode, ==, 0x1234);
+ g_assert_cmpint(result.size, ==, sizeof(payload));
+ g_assert_cmpint(memcmp(result.data, payload, sizeof(payload)), ==, 0);
unlink(path);
g_free(path);
@@ -355,22 +342,18 @@ static void test_btsnoop_monitor_valid(void)
{
GByteArray *trace = g_byte_array_new();
const uint8_t payload[] = { 0xaa, 0xbb };
- uint8_t data[sizeof(payload)];
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0xffff;
- uint16_t opcode = 0xffff;
+ struct read_result result;
append_btsnoop_header(trace, BTSNOOP_FORMAT_MONITOR);
append_btsnoop_packet(trace, sizeof(payload), 0x00051234,
BTSNOOP_EPOCH_OFFSET, payload, sizeof(payload));
- g_assert_true(read_tmp_trace(trace->data, trace->len, 0, sizeof(data),
- data, &size, &index, &opcode, &tv));
- g_assert_cmpint(index, ==, 5);
- g_assert_cmpint(opcode, ==, 0x1234);
- g_assert_cmpint(size, ==, sizeof(payload));
- g_assert_cmpint(memcmp(data, payload, sizeof(payload)), ==, 0);
+ g_assert_true(read_tmp_trace(trace->data, trace->len, 0,
+ sizeof(payload), &result));
+ g_assert_cmpint(result.index, ==, 5);
+ g_assert_cmpint(result.opcode, ==, 0x1234);
+ g_assert_cmpint(result.size, ==, sizeof(payload));
+ g_assert_cmpint(memcmp(result.data, payload, sizeof(payload)), ==, 0);
g_byte_array_unref(trace);
}
@@ -379,22 +362,18 @@ static void test_btsnoop_uart_valid(void)
{
GByteArray *trace = g_byte_array_new();
const uint8_t payload[] = { 0x04, 0x0e, 0x01, 0x00 };
- uint8_t data[sizeof(payload) - 1];
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0xffff;
- uint16_t opcode = 0xffff;
+ struct read_result result;
append_btsnoop_header(trace, BTSNOOP_FORMAT_UART);
append_btsnoop_packet(trace, sizeof(payload), 0,
BTSNOOP_EPOCH_OFFSET, payload, sizeof(payload));
- g_assert_true(read_tmp_trace(trace->data, trace->len, 0, sizeof(data),
- data, &size, &index, &opcode, &tv));
- g_assert_cmpint(index, ==, 0);
- g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_EVENT_PKT);
- g_assert_cmpint(size, ==, sizeof(payload) - 1);
- g_assert_cmpint(memcmp(data, payload + 1, sizeof(data)), ==, 0);
+ g_assert_true(read_tmp_trace(trace->data, trace->len, 0,
+ sizeof(payload) - 1, &result));
+ g_assert_cmpint(result.index, ==, 0);
+ g_assert_cmpint(result.opcode, ==, BTSNOOP_OPCODE_EVENT_PKT);
+ g_assert_cmpint(result.size, ==, sizeof(payload) - 1);
+ g_assert_cmpint(memcmp(result.data, payload + 1, result.size), ==, 0);
g_byte_array_unref(trace);
}
@@ -420,20 +399,15 @@ static void test_btsnoop_uart_opcode_map(void)
for (i = 0; i < G_N_ELEMENTS(cases); i++) {
GByteArray *trace = g_byte_array_new();
const uint8_t payload[] = { cases[i].type, 0x00 };
- uint8_t data[1];
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0;
- uint16_t opcode = 0;
+ struct read_result result;
append_btsnoop_header(trace, BTSNOOP_FORMAT_UART);
append_btsnoop_packet(trace, sizeof(payload), cases[i].flags,
BTSNOOP_EPOCH_OFFSET, payload, sizeof(payload));
- g_assert_true(read_tmp_trace(trace->data, trace->len, 0,
- sizeof(data), data, &size,
- &index, &opcode, &tv));
- g_assert_cmpint(opcode, ==, cases[i].opcode);
+ g_assert_true(read_tmp_trace(trace->data, trace->len, 0, 1,
+ &result));
+ g_assert_cmpint(result.opcode, ==, cases[i].opcode);
g_byte_array_unref(trace);
}
}
@@ -442,22 +416,17 @@ static void test_btsnoop_rejects_small_capacity(void)
{
GByteArray *trace = g_byte_array_new();
const uint8_t payload[] = { 0x01, 0x02, 0x03 };
- uint8_t data[4] = { 0xa5, 0xa5, 0xa5, 0xa5 };
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0;
- uint16_t opcode = 0;
+ struct read_result result;
append_btsnoop_header(trace, BTSNOOP_FORMAT_HCI);
append_btsnoop_packet(trace, sizeof(payload), 0x02,
BTSNOOP_EPOCH_OFFSET, payload, sizeof(payload));
- g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 2, data,
- &size, &index, &opcode, &tv));
- g_assert_cmpint(data[0], ==, 0xa5);
- g_assert_cmpint(data[1], ==, 0xa5);
- g_assert_cmpint(data[2], ==, 0xa5);
- g_assert_cmpint(data[3], ==, 0xa5);
+ g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 2, &result));
+ g_assert_cmpint(result.data[0], ==, 0xa5);
+ g_assert_cmpint(result.data[1], ==, 0xa5);
+ g_assert_cmpint(result.data[2], ==, 0xa5);
+ g_assert_cmpint(result.data[3], ==, 0xa5);
g_byte_array_unref(trace);
}
@@ -465,17 +434,13 @@ static void test_btsnoop_rejects_small_capacity(void)
static void test_btsnoop_rejects_timestamp_underflow(void)
{
GByteArray *trace = g_byte_array_new();
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0;
- uint16_t opcode = 0;
+ struct read_result result;
append_btsnoop_header(trace, BTSNOOP_FORMAT_HCI);
append_btsnoop_packet(trace, 0, 0x02, BTSNOOP_EPOCH_OFFSET - 1,
NULL, 0);
- g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 0, NULL,
- &size, &index, &opcode, &tv));
+ g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 0, &result));
g_byte_array_unref(trace);
}
@@ -483,16 +448,12 @@ static void test_btsnoop_rejects_timestamp_underflow(void)
static void test_btsnoop_rejects_uart_zero_length(void)
{
GByteArray *trace = g_byte_array_new();
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0;
- uint16_t opcode = 0;
+ struct read_result result;
append_btsnoop_header(trace, BTSNOOP_FORMAT_UART);
append_btsnoop_packet(trace, 0, 0, BTSNOOP_EPOCH_OFFSET, NULL, 0);
- g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 0, NULL,
- &size, &index, &opcode, &tv));
+ g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 0, &result));
g_byte_array_unref(trace);
}
@@ -500,16 +461,12 @@ static void test_btsnoop_rejects_uart_zero_length(void)
static void test_btsnoop_rejects_uart_short_type(void)
{
GByteArray *trace = g_byte_array_new();
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0;
- uint16_t opcode = 0;
+ struct read_result result;
append_btsnoop_header(trace, BTSNOOP_FORMAT_UART);
append_btsnoop_packet(trace, 1, 0, BTSNOOP_EPOCH_OFFSET, NULL, 0);
- g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 0, NULL,
- &size, &index, &opcode, &tv));
+ g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 0, &result));
g_byte_array_unref(trace);
}
@@ -518,187 +475,17 @@ static void test_btsnoop_rejects_short_payload(void)
{
GByteArray *trace = g_byte_array_new();
const uint8_t payload[] = { 0x01, 0x02 };
- uint8_t data[3];
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0;
- uint16_t opcode = 0;
+ struct read_result result;
append_btsnoop_header(trace, BTSNOOP_FORMAT_HCI);
append_btsnoop_packet(trace, 3, 0x02, BTSNOOP_EPOCH_OFFSET,
payload, sizeof(payload));
- g_assert_false(read_tmp_trace(trace->data, trace->len, 0,
- sizeof(data), data, &size,
- &index, &opcode, &tv));
-
- g_byte_array_unref(trace);
-}
-
-static void test_pklg_big_endian_valid(void)
-{
- GByteArray *trace = g_byte_array_new();
- const uint8_t payload[] = { 0x0e, 0x01, 0x00 };
- uint8_t data[sizeof(payload)];
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0xffff;
- uint16_t opcode = 0xffff;
-
- append_pklg_packet(trace, false, sizeof(payload),
- ((uint64_t) 123 << 32) | 456, 0x01, payload,
- sizeof(payload));
-
- g_assert_true(read_tmp_trace(trace->data, trace->len,
- BTSNOOP_FLAG_PKLG_SUPPORT, sizeof(data),
- data, &size, &index, &opcode, &tv));
- g_assert_cmpint(index, ==, 0);
- g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_EVENT_PKT);
- g_assert_cmpint(size, ==, sizeof(payload));
- g_assert_cmpint(memcmp(data, payload, sizeof(payload)), ==, 0);
- g_assert_cmpint(tv.tv_sec, ==, 123);
- g_assert_cmpint(tv.tv_usec, ==, 456);
-
- g_byte_array_unref(trace);
-}
-
-static void test_pklg_little_endian_valid(void)
-{
- GByteArray *trace = g_byte_array_new();
- const uint8_t payload[] = { 0x01, 0x02, 0x03 };
- uint8_t data[sizeof(payload)];
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0xffff;
- uint16_t opcode = 0xffff;
-
- append_pklg_packet(trace, true, sizeof(payload),
- ((uint64_t) 456 << 32) | 123, 0x00, payload,
- sizeof(payload));
-
- g_assert_true(read_tmp_trace(trace->data, trace->len,
- BTSNOOP_FLAG_PKLG_SUPPORT, sizeof(data),
- data, &size, &index, &opcode, &tv));
- g_assert_cmpint(index, ==, 0);
- g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_COMMAND_PKT);
- g_assert_cmpint(size, ==, sizeof(payload));
- g_assert_cmpint(data[0], ==, payload[0]);
- g_assert_cmpint(tv.tv_sec, ==, 123);
- g_assert_cmpint(tv.tv_usec, ==, 456);
-
- g_byte_array_unref(trace);
-}
-
-static void test_pklg_rejects_short_length(void)
-{
- GByteArray *trace = g_byte_array_new();
- struct test_pklg_pkt pkt;
- const uint8_t padding[] = { 0x00, 0x00, 0x00 };
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0;
- uint16_t opcode = 0;
-
- pkt.len = htobe32(PKLG_PAYLOAD_OFFSET - 1);
- pkt.ts = 0;
- pkt.type = 0x01;
-
- append_bytes(trace, &pkt, sizeof(pkt));
- append_bytes(trace, padding, sizeof(padding));
-
- g_assert_false(read_tmp_trace(trace->data, trace->len,
- BTSNOOP_FLAG_PKLG_SUPPORT, 0, NULL,
- &size, &index, &opcode, &tv));
-
- g_byte_array_unref(trace);
-}
-
-static void test_pklg_rejects_small_capacity(void)
-{
- GByteArray *trace = g_byte_array_new();
- const uint8_t payload[] = { 0x01, 0x02, 0x03 };
- uint8_t data[4] = { 0xa5, 0xa5, 0xa5, 0xa5 };
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0;
- uint16_t opcode = 0;
-
- append_pklg_packet(trace, false, sizeof(payload), 0, 0x01, payload,
- sizeof(payload));
-
- g_assert_false(read_tmp_trace(trace->data, trace->len,
- BTSNOOP_FLAG_PKLG_SUPPORT, 2, data,
- &size, &index, &opcode, &tv));
- g_assert_cmpint(data[0], ==, 0xa5);
- g_assert_cmpint(data[1], ==, 0xa5);
- g_assert_cmpint(data[2], ==, 0xa5);
- g_assert_cmpint(data[3], ==, 0xa5);
+ g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 3, &result));
g_byte_array_unref(trace);
}
-static void test_pklg_rejects_short_payload(void)
-{
- GByteArray *trace = g_byte_array_new();
- const uint8_t payload[] = { 0x01, 0x02, 0x03 };
- uint8_t data[4];
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0;
- uint16_t opcode = 0;
-
- append_pklg_packet(trace, false, 4, 0, 0x01, payload,
- sizeof(payload));
-
- g_assert_false(read_tmp_trace(trace->data, trace->len,
- BTSNOOP_FLAG_PKLG_SUPPORT, sizeof(data),
- data, &size, &index, &opcode, &tv));
-
- g_byte_array_unref(trace);
-}
-
-static void test_pklg_type_map(void)
-{
- static const struct {
- uint8_t type;
- uint16_t index;
- uint16_t opcode;
- } cases[] = {
- { 0x02, 0x0000, BTSNOOP_OPCODE_ACL_TX_PKT },
- { 0x03, 0x0000, BTSNOOP_OPCODE_ACL_RX_PKT },
- { 0x08, 0x0000, BTSNOOP_OPCODE_SCO_TX_PKT },
- { 0x09, 0x0000, BTSNOOP_OPCODE_SCO_RX_PKT },
- { 0x12, 0x0000, BTSNOOP_OPCODE_ISO_TX_PKT },
- { 0x13, 0x0000, BTSNOOP_OPCODE_ISO_RX_PKT },
- { 0x0b, 0x0000, BTSNOOP_OPCODE_VENDOR_DIAG },
- { 0xfc, 0xffff, BTSNOOP_OPCODE_SYSTEM_NOTE },
- { 0xaa, 0xffff, 0xffff },
- };
- const uint8_t payload[] = { 0x00, 0x01, 0x02 };
- unsigned int i;
-
- for (i = 0; i < G_N_ELEMENTS(cases); i++) {
- GByteArray *trace = g_byte_array_new();
- uint8_t data[sizeof(payload)];
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0;
- uint16_t opcode = 0;
-
- append_pklg_packet(trace, false, sizeof(payload), 0,
- cases[i].type, payload,
- sizeof(payload));
-
- g_assert_true(read_tmp_trace(trace->data, trace->len,
- BTSNOOP_FLAG_PKLG_SUPPORT,
- sizeof(data), data, &size,
- &index, &opcode, &tv));
- g_assert_cmpint(index, ==, cases[i].index);
- g_assert_cmpint(opcode, ==, cases[i].opcode);
- g_byte_array_unref(trace);
- }
-}
-
static void test_btsnoop_truncation_fuzz(void)
{
GByteArray *trace = g_byte_array_new();
@@ -710,40 +497,10 @@ static void test_btsnoop_truncation_fuzz(void)
BTSNOOP_EPOCH_OFFSET, payload, sizeof(payload));
for (len = 0; len < trace->len; len++) {
- uint8_t data[sizeof(payload)];
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0;
- uint16_t opcode = 0;
+ struct read_result result;
g_assert_false(read_tmp_trace(trace->data, len, 0,
- sizeof(data), data, &size,
- &index, &opcode, &tv));
- }
-
- g_byte_array_unref(trace);
-}
-
-static void test_pklg_truncation_fuzz(void)
-{
- GByteArray *trace = g_byte_array_new();
- const uint8_t payload[] = { 0x01, 0x02, 0x03 };
- size_t len;
-
- append_pklg_packet(trace, false, sizeof(payload), 0, 0x01, payload,
- sizeof(payload));
-
- for (len = 0; len < trace->len; len++) {
- uint8_t data[sizeof(payload)];
- struct timeval tv;
- uint16_t size = 0;
- uint16_t index = 0;
- uint16_t opcode = 0;
-
- g_assert_false(read_tmp_trace(trace->data, len,
- BTSNOOP_FLAG_PKLG_SUPPORT,
- sizeof(data), data, &size,
- &index, &opcode, &tv));
+ sizeof(payload), &result));
}
g_byte_array_unref(trace);
@@ -778,17 +535,10 @@ int main(int argc, char *argv[])
test_btsnoop_rejects_uart_short_type);
g_test_add_func("/btsnoop/payload/short",
test_btsnoop_rejects_short_payload);
- g_test_add_func("/pklg/big-endian/valid", test_pklg_big_endian_valid);
- g_test_add_func("/pklg/little-endian/valid",
- test_pklg_little_endian_valid);
- g_test_add_func("/pklg/length/short", test_pklg_rejects_short_length);
- g_test_add_func("/pklg/capacity/reject",
- test_pklg_rejects_small_capacity);
- g_test_add_func("/pklg/payload/short", test_pklg_rejects_short_payload);
- g_test_add_func("/pklg/type-map", test_pklg_type_map);
g_test_add_func("/btsnoop/fuzz/truncation",
test_btsnoop_truncation_fuzz);
- g_test_add_func("/pklg/fuzz/truncation", test_pklg_truncation_fuzz);
+
+ add_pklg_tests();
return g_test_run();
}
--
2.43.0
^ permalink raw reply related
* [PATCH BlueZ 1/2] shared: harden btsnoop trace parsing
From: Geraldo Netto @ 2026-06-20 19:22 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Geraldo Netto
---
Makefile.am | 6 +
monitor/analyze.c | 4 +-
monitor/control.c | 2 +-
src/shared/btsnoop.c | 303 +++++++++++------
src/shared/btsnoop.h | 6 +-
unit/test-btsnoop.c | 794 +++++++++++++++++++++++++++++++++++++++++++
unit/test-btsnoop.h | 3 +
7 files changed, 1009 insertions(+), 109 deletions(-)
create mode 100644 unit/test-btsnoop.c
create mode 100644 unit/test-btsnoop.h
diff --git a/Makefile.am b/Makefile.am
index 76c4ab5d4..4887934a9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -587,6 +587,12 @@ unit_tests += unit/test-textfile
unit_test_textfile_SOURCES = unit/test-textfile.c src/textfile.h src/textfile.c
unit_test_textfile_LDADD = src/libshared-glib.la $(GLIB_LIBS)
+unit_tests += unit/test-btsnoop
+
+unit_test_btsnoop_SOURCES = unit/test-btsnoop.c \
+ src/shared/btsnoop.h src/shared/btsnoop.c
+unit_test_btsnoop_LDADD = src/libshared-glib.la $(GLIB_LIBS)
+
unit_tests += unit/test-crc
unit_test_crc_SOURCES = unit/test-crc.c monitor/crc.h monitor/crc.c
diff --git a/monitor/analyze.c b/monitor/analyze.c
index de9c23603..c9400ceb3 100644
--- a/monitor/analyze.c
+++ b/monitor/analyze.c
@@ -1404,8 +1404,8 @@ void analyze_trace(const char *path)
struct timeval tv;
uint16_t index, opcode, pktlen;
- if (!btsnoop_read_hci(btsnoop_file, &tv, &index, &opcode,
- buf, &pktlen))
+ if (!btsnoop_read_hci(btsnoop_file, &tv, &index,
+ &opcode, buf, sizeof(buf), &pktlen))
break;
switch (opcode) {
diff --git a/monitor/control.c b/monitor/control.c
index 83347d5db..975e1d117 100644
--- a/monitor/control.c
+++ b/monitor/control.c
@@ -1564,7 +1564,7 @@ void control_reader(const char *path, bool pager)
uint16_t index, opcode;
if (!btsnoop_read_hci(btsnoop_file, &tv, &index,
- &opcode, buf, &pktlen))
+ &opcode, buf, sizeof(buf), &pktlen))
break;
if (opcode == 0xffff)
diff --git a/src/shared/btsnoop.c b/src/shared/btsnoop.c
index 9ce2f2655..39960c4b8 100644
--- a/src/shared/btsnoop.c
+++ b/src/shared/btsnoop.c
@@ -13,6 +13,7 @@
#endif
#define _GNU_SOURCE
+#include <errno.h>
#include <endian.h>
#include <fcntl.h>
#include <unistd.h>
@@ -47,12 +48,16 @@ static const uint8_t btsnoop_id[] = { 0x62, 0x74, 0x73, 0x6e,
static const uint32_t btsnoop_version = 1;
+#define BTSNOOP_EPOCH_OFFSET 0x00E03AB44A676000ull
+#define BTSNOOP_UNIX_TIME_OFFSET 946684800ll
+
struct pklg_pkt {
uint32_t len;
uint64_t ts;
uint8_t type;
} __attribute__ ((packed));
#define PKLG_PKT_SIZE (sizeof(struct pklg_pkt))
+#define PKLG_PAYLOAD_OFFSET (PKLG_PKT_SIZE - sizeof(uint32_t))
struct btsnoop {
int ref_count;
@@ -271,13 +276,14 @@ bool btsnoop_write(struct btsnoop *btsnoop, struct timeval *tv,
if (!btsnoop_rotate(btsnoop))
return false;
- ts = (tv->tv_sec - 946684800ll) * 1000000ll + tv->tv_usec;
+ ts = (tv->tv_sec - BTSNOOP_UNIX_TIME_OFFSET) * 1000000ll +
+ tv->tv_usec;
pkt.size = htobe32(size);
pkt.len = htobe32(size);
pkt.flags = htobe32(flags);
pkt.drops = htobe32(drops);
- pkt.ts = htobe64(ts + 0x00E03AB44A676000ll);
+ pkt.ts = htobe64(ts + BTSNOOP_EPOCH_OFFSET);
written = write(btsnoop->fd, &pkt, BTSNOOP_PKT_SIZE);
if (written < 0)
@@ -324,6 +330,121 @@ static uint32_t get_flags_from_opcode(uint16_t opcode)
return 0xff;
}
+static ssize_t read_exact(int fd, void *data, size_t size)
+{
+ uint8_t *ptr = data;
+ size_t offset = 0;
+
+ while (offset < size) {
+ ssize_t len;
+
+ len = read(fd, ptr + offset, size - offset);
+ if (len < 0) {
+ if (errno == EINTR)
+ continue;
+ return -1;
+ }
+
+ if (len == 0)
+ break;
+
+ offset += len;
+ }
+
+ return offset;
+}
+
+static bool read_packet_data(struct btsnoop *btsnoop, void *data,
+ uint16_t data_size, uint32_t toread,
+ uint16_t *size)
+{
+ ssize_t len;
+
+ if (!size || (!data && toread)) {
+ btsnoop->aborted = true;
+ return false;
+ }
+
+ if (toread > data_size) {
+ btsnoop->aborted = true;
+ return false;
+ }
+
+ len = read_exact(btsnoop->fd, data, toread);
+ if (len != (ssize_t) toread) {
+ btsnoop->aborted = true;
+ return false;
+ }
+
+ *size = toread;
+
+ return true;
+}
+
+static bool decode_btsnoop_timestamp(uint64_t raw_ts, struct timeval *tv)
+{
+ uint64_t ts;
+
+ if (raw_ts < BTSNOOP_EPOCH_OFFSET)
+ return false;
+
+ ts = raw_ts - BTSNOOP_EPOCH_OFFSET;
+ tv->tv_sec = (ts / 1000000ll) + BTSNOOP_UNIX_TIME_OFFSET;
+ tv->tv_usec = ts % 1000000ll;
+
+ return true;
+}
+
+static void get_pklg_opcode(uint8_t type, uint16_t *index, uint16_t *opcode)
+{
+ switch (type) {
+ case 0x00:
+ *index = 0x0000;
+ *opcode = BTSNOOP_OPCODE_COMMAND_PKT;
+ break;
+ case 0x01:
+ *index = 0x0000;
+ *opcode = BTSNOOP_OPCODE_EVENT_PKT;
+ break;
+ case 0x02:
+ *index = 0x0000;
+ *opcode = BTSNOOP_OPCODE_ACL_TX_PKT;
+ break;
+ case 0x03:
+ *index = 0x0000;
+ *opcode = BTSNOOP_OPCODE_ACL_RX_PKT;
+ break;
+ case 0x08:
+ *index = 0x0000;
+ *opcode = BTSNOOP_OPCODE_SCO_TX_PKT;
+ break;
+ case 0x09:
+ *index = 0x0000;
+ *opcode = BTSNOOP_OPCODE_SCO_RX_PKT;
+ break;
+ case 0x12:
+ *index = 0x0000;
+ *opcode = BTSNOOP_OPCODE_ISO_TX_PKT;
+ break;
+ case 0x13:
+ *index = 0x0000;
+ *opcode = BTSNOOP_OPCODE_ISO_RX_PKT;
+ break;
+ case 0x0b:
+ *index = 0x0000;
+ *opcode = BTSNOOP_OPCODE_VENDOR_DIAG;
+ break;
+ case 0xfc:
+ *index = 0xffff;
+ *opcode = BTSNOOP_OPCODE_SYSTEM_NOTE;
+ break;
+ default:
+ *index = 0xffff;
+ *opcode = 0xffff;
+ break;
+ }
+}
+
bool btsnoop_write_hci(struct btsnoop *btsnoop, struct timeval *tv,
uint16_t index, uint16_t opcode, uint32_t drops,
const void *data, uint16_t size)
@@ -377,99 +498,53 @@ bool btsnoop_write_phy(struct btsnoop *btsnoop, struct timeval *tv,
return btsnoop_write(btsnoop, tv, flags, 0, data, size);
}
-static bool pklg_read_hci(struct btsnoop *btsnoop, struct timeval *tv,
- uint16_t *index, uint16_t *opcode,
- void *data, uint16_t *size)
+static bool pklg_read_hci(struct btsnoop *btsnoop,
+ struct timeval *tv, uint16_t *index, uint16_t *opcode,
+ void *data, uint16_t data_size, uint16_t *size)
{
struct pklg_pkt pkt;
+ uint32_t pkt_len;
uint32_t toread;
uint64_t ts;
ssize_t len;
- len = read(btsnoop->fd, &pkt, PKLG_PKT_SIZE);
+ len = read_exact(btsnoop->fd, &pkt, PKLG_PKT_SIZE);
if (len == 0)
return false;
- if (len < 0 || len != PKLG_PKT_SIZE) {
+ if (len != PKLG_PKT_SIZE) {
btsnoop->aborted = true;
return false;
}
if (btsnoop->pklg_v2) {
- toread = le32toh(pkt.len) - (PKLG_PKT_SIZE - 4);
+ pkt_len = le32toh(pkt.len);
ts = le64toh(pkt.ts);
tv->tv_sec = ts & 0xffffffff;
tv->tv_usec = ts >> 32;
} else {
- toread = be32toh(pkt.len) - (PKLG_PKT_SIZE - 4);
+ pkt_len = be32toh(pkt.len);
ts = be64toh(pkt.ts);
tv->tv_sec = ts >> 32;
tv->tv_usec = ts & 0xffffffff;
}
- if (toread > BTSNOOP_MAX_PACKET_SIZE) {
- btsnoop->aborted = true;
- return false;
- }
-
- switch (pkt.type) {
- case 0x00:
- *index = 0x0000;
- *opcode = BTSNOOP_OPCODE_COMMAND_PKT;
- break;
- case 0x01:
- *index = 0x0000;
- *opcode = BTSNOOP_OPCODE_EVENT_PKT;
- break;
- case 0x02:
- *index = 0x0000;
- *opcode = BTSNOOP_OPCODE_ACL_TX_PKT;
- break;
- case 0x03:
- *index = 0x0000;
- *opcode = BTSNOOP_OPCODE_ACL_RX_PKT;
- break;
- case 0x08:
- *index = 0x0000;
- *opcode = BTSNOOP_OPCODE_SCO_TX_PKT;
- break;
- case 0x09:
- *index = 0x0000;
- *opcode = BTSNOOP_OPCODE_SCO_RX_PKT;
- break;
- case 0x12:
- *index = 0x0000;
- *opcode = BTSNOOP_OPCODE_ISO_TX_PKT;
- break;
- case 0x13:
- *index = 0x0000;
- *opcode = BTSNOOP_OPCODE_ISO_RX_PKT;
- break;
- case 0x0b:
- *index = 0x0000;
- *opcode = BTSNOOP_OPCODE_VENDOR_DIAG;
- break;
- case 0xfc:
- *index = 0xffff;
- *opcode = BTSNOOP_OPCODE_SYSTEM_NOTE;
- break;
- default:
- *index = 0xffff;
- *opcode = 0xffff;
- break;
+ if (pkt_len < PKLG_PAYLOAD_OFFSET) {
+ btsnoop->aborted = true;
+ return false;
}
- len = read(btsnoop->fd, data, toread);
- if (len < 0) {
+ toread = pkt_len - PKLG_PAYLOAD_OFFSET;
+ if (toread > BTSNOOP_MAX_PACKET_SIZE) {
btsnoop->aborted = true;
return false;
}
- *size = toread;
+ get_pklg_opcode(pkt.type, index, opcode);
- return true;
+ return read_packet_data(btsnoop, data, data_size, toread, size);
}
static uint16_t get_opcode_from_flags(uint8_t type, uint32_t flags)
@@ -512,84 +587,106 @@ static uint16_t get_opcode_from_flags(uint8_t type, uint32_t flags)
return 0xffff;
}
-bool btsnoop_read_hci(struct btsnoop *btsnoop, struct timeval *tv,
- uint16_t *index, uint16_t *opcode,
- void *data, uint16_t *size)
+static bool read_uart_type(struct btsnoop *btsnoop, uint32_t *toread,
+ uint8_t *type)
{
- struct btsnoop_pkt pkt;
- uint32_t toread, flags;
- uint64_t ts;
- uint8_t pkt_type;
ssize_t len;
- if (!btsnoop || btsnoop->aborted)
- return false;
-
- if (btsnoop->pklg_format)
- return pklg_read_hci(btsnoop, tv, index, opcode, data, size);
-
- len = read(btsnoop->fd, &pkt, BTSNOOP_PKT_SIZE);
- if (len == 0)
- return false;
-
- if (len < 0 || len != BTSNOOP_PKT_SIZE) {
+ if (!*toread) {
btsnoop->aborted = true;
return false;
}
- toread = be32toh(pkt.len);
- if (toread > BTSNOOP_MAX_PACKET_SIZE) {
+ len = read_exact(btsnoop->fd, type, 1);
+ if (len != 1) {
btsnoop->aborted = true;
return false;
}
- flags = be32toh(pkt.flags);
+ (*toread)--;
- ts = be64toh(pkt.ts) - 0x00E03AB44A676000ll;
- tv->tv_sec = (ts / 1000000ll) + 946684800ll;
- tv->tv_usec = ts % 1000000ll;
+ return true;
+}
+
+static bool decode_btsnoop_record(struct btsnoop *btsnoop, uint32_t flags,
+ uint32_t *toread, uint16_t *index,
+ uint16_t *opcode)
+{
+ uint8_t pkt_type;
switch (btsnoop->format) {
case BTSNOOP_FORMAT_HCI:
*index = 0;
*opcode = get_opcode_from_flags(0xff, flags);
- break;
-
+ return true;
case BTSNOOP_FORMAT_UART:
- len = read(btsnoop->fd, &pkt_type, 1);
- if (len < 0) {
- btsnoop->aborted = true;
+ if (!read_uart_type(btsnoop, toread, &pkt_type))
return false;
- }
- toread--;
*index = 0;
*opcode = get_opcode_from_flags(pkt_type, flags);
- break;
-
+ return true;
case BTSNOOP_FORMAT_MONITOR:
*index = flags >> 16;
*opcode = flags & 0xffff;
- break;
-
+ return true;
default:
btsnoop->aborted = true;
return false;
}
+}
- len = read(btsnoop->fd, data, toread);
- if (len < 0) {
+bool btsnoop_read_hci(struct btsnoop *btsnoop,
+ struct timeval *tv, uint16_t *index, uint16_t *opcode,
+ void *data, uint16_t data_size, uint16_t *size)
+{
+ struct btsnoop_pkt pkt;
+ uint32_t toread, flags;
+ ssize_t len;
+
+ if (!btsnoop || !tv || !index || !opcode || !size || btsnoop->aborted)
+ return false;
+
+ if (btsnoop->pklg_format)
+ return pklg_read_hci(btsnoop, tv, index, opcode,
+ data, data_size, size);
+
+ len = read_exact(btsnoop->fd, &pkt, BTSNOOP_PKT_SIZE);
+ if (len == 0)
+ return false;
+
+ if (len != BTSNOOP_PKT_SIZE) {
btsnoop->aborted = true;
return false;
}
- *size = toread;
+ toread = be32toh(pkt.len);
+ if (toread > BTSNOOP_MAX_PACKET_SIZE) {
+ btsnoop->aborted = true;
+ return false;
+ }
- return true;
+ flags = be32toh(pkt.flags);
+
+ if (!decode_btsnoop_timestamp(be64toh(pkt.ts), tv)) {
+ btsnoop->aborted = true;
+ return false;
+ }
+
+ if (!decode_btsnoop_record(btsnoop, flags, &toread, index, opcode))
+ return false;
+
+ return read_packet_data(btsnoop, data, data_size, toread, size);
}
bool btsnoop_read_phy(struct btsnoop *btsnoop, struct timeval *tv,
- uint16_t *frequency, void *data, uint16_t *size)
+ uint16_t *frequency, void *data, uint16_t *size)
{
+ (void) btsnoop;
+ (void) tv;
+ (void) frequency;
+ (void) data;
+ (void) size;
+
return false;
}
diff --git a/src/shared/btsnoop.h b/src/shared/btsnoop.h
index c24755d56..796604c58 100644
--- a/src/shared/btsnoop.h
+++ b/src/shared/btsnoop.h
@@ -106,8 +106,8 @@ bool btsnoop_write_hci(struct btsnoop *btsnoop, struct timeval *tv,
bool btsnoop_write_phy(struct btsnoop *btsnoop, struct timeval *tv,
uint16_t frequency, const void *data, uint16_t size);
-bool btsnoop_read_hci(struct btsnoop *btsnoop, struct timeval *tv,
- uint16_t *index, uint16_t *opcode,
- void *data, uint16_t *size);
+bool btsnoop_read_hci(struct btsnoop *btsnoop,
+ struct timeval *tv, uint16_t *index, uint16_t *opcode,
+ void *data, uint16_t data_size, uint16_t *size);
bool btsnoop_read_phy(struct btsnoop *btsnoop, struct timeval *tv,
uint16_t *frequency, void *data, uint16_t *size);
diff --git a/unit/test-btsnoop.c b/unit/test-btsnoop.c
new file mode 100644
index 000000000..710209097
--- /dev/null
+++ b/unit/test-btsnoop.c
@@ -0,0 +1,794 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <endian.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+
+#include "src/shared/att-types.h"
+#include "src/shared/btsnoop.h"
+#include "unit/test-btsnoop.h"
+
+#define BTSNOOP_EPOCH_OFFSET 0x00E03AB44A676000ull
+#define PKLG_PAYLOAD_OFFSET 9
+
+struct test_btsnoop_hdr {
+ uint8_t id[8];
+ uint32_t version;
+ uint32_t type;
+} __packed;
+
+struct test_btsnoop_pkt {
+ uint32_t size;
+ uint32_t len;
+ uint32_t flags;
+ uint32_t drops;
+ uint64_t ts;
+} __packed;
+
+struct test_pklg_pkt {
+ uint32_t len;
+ uint64_t ts;
+ uint8_t type;
+} __packed;
+
+static const uint8_t btsnoop_id[] = {
+ 0x62, 0x74, 0x73, 0x6e, 0x6f, 0x6f, 0x70, 0x00
+};
+
+static void append_bytes(GByteArray *array, const void *data, size_t size)
+{
+ if (!size)
+ return;
+
+ g_byte_array_append(array, data, size);
+}
+
+static void append_btsnoop_header(GByteArray *array, uint32_t format)
+{
+ struct test_btsnoop_hdr hdr;
+
+ memcpy(hdr.id, btsnoop_id, sizeof(btsnoop_id));
+ hdr.version = htobe32(1);
+ hdr.type = htobe32(format);
+
+ append_bytes(array, &hdr, sizeof(hdr));
+}
+
+static void append_btsnoop_packet(GByteArray *array, uint32_t len,
+ uint32_t flags, uint64_t ts,
+ const void *data, size_t data_len)
+{
+ struct test_btsnoop_pkt pkt;
+
+ pkt.size = htobe32(len);
+ pkt.len = htobe32(len);
+ pkt.flags = htobe32(flags);
+ pkt.drops = 0;
+ pkt.ts = htobe64(ts);
+
+ append_bytes(array, &pkt, sizeof(pkt));
+ append_bytes(array, data, data_len);
+}
+
+static void append_pklg_packet(GByteArray *array, bool little_endian,
+ uint32_t payload_len, uint64_t ts,
+ uint8_t type, const void *data, size_t data_len)
+{
+ struct test_pklg_pkt pkt;
+ uint32_t len = PKLG_PAYLOAD_OFFSET + payload_len;
+
+ pkt.len = little_endian ? htole32(len) : htobe32(len);
+ pkt.ts = little_endian ? htole64(ts) : htobe64(ts);
+ pkt.type = type;
+
+ append_bytes(array, &pkt, sizeof(pkt));
+ append_bytes(array, data, data_len);
+}
+
+static char *write_tmp_trace(const void *data, size_t size)
+{
+ char *path = NULL;
+ ssize_t written;
+ int fd;
+
+ fd = g_file_open_tmp("bluez-btsnoop-XXXXXX", &path, NULL);
+ g_assert(fd >= 0);
+ written = write(fd, data, size);
+ g_assert_cmpint(written, ==, (ssize_t) size);
+ g_assert_cmpint(close(fd), ==, 0);
+
+ return path;
+}
+
+static char *new_tmp_path(void)
+{
+ char *path = NULL;
+ int fd;
+
+ fd = g_file_open_tmp("bluez-btsnoop-XXXXXX", &path, NULL);
+ g_assert(fd >= 0);
+ g_assert_cmpint(close(fd), ==, 0);
+ unlink(path);
+
+ return path;
+}
+
+static void unlink_rotated(const char *path, unsigned int count)
+{
+ unsigned int i;
+
+ for (i = 0; i <= count; i++) {
+ char *name;
+
+ name = g_strdup_printf("%s.%u", path, i);
+ unlink(name);
+ g_free(name);
+ }
+}
+
+static bool read_tmp_trace(const void *trace, size_t trace_len,
+ unsigned long flags, uint16_t data_size,
+ uint8_t *data, uint16_t *size,
+ uint16_t *index, uint16_t *opcode,
+ struct timeval *tv)
+{
+ struct btsnoop *btsnoop;
+ char *path;
+ bool result;
+
+ path = write_tmp_trace(trace, trace_len);
+ btsnoop = btsnoop_open(path, flags);
+ unlink(path);
+ g_free(path);
+
+ if (!btsnoop)
+ return false;
+
+ result = btsnoop_read_hci(btsnoop, tv, index, opcode, data,
+ data_size, size);
+ btsnoop_unref(btsnoop);
+
+ return result;
+}
+
+static bool read_trace_file(const char *path, unsigned long flags,
+ uint8_t *data, uint16_t data_size,
+ uint16_t *size, uint16_t *index,
+ uint16_t *opcode, struct timeval *tv)
+{
+ struct btsnoop *btsnoop;
+ bool result;
+
+ btsnoop = btsnoop_open(path, flags);
+ g_assert_nonnull(btsnoop);
+
+ result = btsnoop_read_hci(btsnoop, tv, index, opcode, data,
+ data_size, size);
+ btsnoop_unref(btsnoop);
+
+ return result;
+}
+
+static void test_btsnoop_hci_valid(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+ uint8_t data[sizeof(payload)];
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0xffff;
+ uint16_t opcode = 0xffff;
+
+ append_btsnoop_header(trace, BTSNOOP_FORMAT_HCI);
+ append_btsnoop_packet(trace, sizeof(payload), 0x02,
+ BTSNOOP_EPOCH_OFFSET + 1234567, payload,
+ sizeof(payload));
+
+ g_assert_true(read_tmp_trace(trace->data, trace->len, 0, sizeof(data),
+ data, &size, &index, &opcode, &tv));
+ g_assert_cmpint(index, ==, 0);
+ g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_COMMAND_PKT);
+ g_assert_cmpint(size, ==, sizeof(payload));
+ g_assert_cmpint(memcmp(data, payload, sizeof(payload)), ==, 0);
+ g_assert_cmpint(tv.tv_sec, ==, 946684801);
+ g_assert_cmpint(tv.tv_usec, ==, 234567);
+
+ g_byte_array_unref(trace);
+}
+
+static void test_btsnoop_create_invalid_args(void)
+{
+ char *path = new_tmp_path();
+
+ g_assert_null(btsnoop_create(path, 0, 1, BTSNOOP_FORMAT_HCI));
+ g_assert_null(btsnoop_create("/tmp/bluez/no/such/path", 0, 0,
+ BTSNOOP_FORMAT_HCI));
+ g_assert_null(btsnoop_ref(NULL));
+ btsnoop_unref(NULL);
+ g_assert_cmpint(btsnoop_get_format(NULL), ==, BTSNOOP_FORMAT_INVALID);
+
+ g_free(path);
+}
+
+static void test_btsnoop_open_invalid_headers(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ struct test_btsnoop_hdr hdr;
+ char *path;
+
+ append_btsnoop_header(trace, BTSNOOP_FORMAT_HCI);
+ ((struct test_btsnoop_hdr *) trace->data)->version = htobe32(2);
+ path = write_tmp_trace(trace->data, trace->len);
+ g_assert_null(btsnoop_open(path, 0));
+ unlink(path);
+ g_free(path);
+ g_byte_array_set_size(trace, 0);
+
+ memset(&hdr, 0x55, sizeof(hdr));
+ append_bytes(trace, &hdr, sizeof(hdr));
+ path = write_tmp_trace(trace->data, trace->len);
+ g_assert_null(btsnoop_open(path, 0));
+ g_assert_null(btsnoop_open(path, BTSNOOP_FLAG_PKLG_SUPPORT));
+ unlink(path);
+ g_free(path);
+ g_byte_array_unref(trace);
+}
+
+static void test_btsnoop_write_hci_roundtrip(void)
+{
+ const uint8_t command[] = { 0x01, 0x02, 0x03 };
+ const uint8_t event[] = { 0x04, 0x05 };
+ uint8_t data[sizeof(command)];
+ struct btsnoop *btsnoop;
+ struct timeval tv = { .tv_sec = 946684802, .tv_usec = 345678 };
+ uint16_t size = 0;
+ uint16_t index = 0;
+ uint16_t opcode = 0;
+ char *path = new_tmp_path();
+
+ btsnoop = btsnoop_create(path, 0, 0, BTSNOOP_FORMAT_HCI);
+ g_assert_nonnull(btsnoop);
+ g_assert_cmpint(btsnoop_get_format(btsnoop), ==, BTSNOOP_FORMAT_HCI);
+ g_assert_true(btsnoop_write_hci(btsnoop, &tv, 0,
+ BTSNOOP_OPCODE_COMMAND_PKT, 0,
+ command, sizeof(command)));
+ g_assert_true(btsnoop_write_hci(btsnoop, &tv, 0,
+ BTSNOOP_OPCODE_EVENT_PKT, 0,
+ event, sizeof(event)));
+ g_assert_false(btsnoop_write_hci(btsnoop, &tv, 1,
+ BTSNOOP_OPCODE_COMMAND_PKT, 0,
+ command, sizeof(command)));
+ g_assert_false(btsnoop_write_hci(btsnoop, &tv, 0,
+ BTSNOOP_OPCODE_NEW_INDEX, 0,
+ command, sizeof(command)));
+ btsnoop_unref(btsnoop);
+
+ g_assert_true(read_trace_file(path, 0, data, sizeof(data), &size,
+ &index, &opcode, &tv));
+ g_assert_cmpint(index, ==, 0);
+ g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_COMMAND_PKT);
+ g_assert_cmpint(size, ==, sizeof(command));
+ g_assert_cmpint(memcmp(data, command, sizeof(command)), ==, 0);
+
+ btsnoop = btsnoop_open(path, 0);
+ g_assert_nonnull(btsnoop);
+ g_assert_true(btsnoop_read_hci(btsnoop, &tv, &index, &opcode, data,
+ sizeof(data), &size));
+ g_assert_true(btsnoop_read_hci(btsnoop, &tv, &index, &opcode, data,
+ sizeof(data), &size));
+ g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_EVENT_PKT);
+ g_assert_cmpint(size, ==, sizeof(event));
+ g_assert_cmpint(memcmp(data, event, sizeof(event)), ==, 0);
+ g_assert_false(btsnoop_read_hci(btsnoop, &tv, &index, &opcode, data,
+ sizeof(data), &size));
+ btsnoop_unref(btsnoop);
+
+ unlink(path);
+ g_free(path);
+}
+
+static void test_btsnoop_write_monitor_roundtrip(void)
+{
+ const uint8_t payload[] = { 0xaa, 0xbb };
+ uint8_t data[sizeof(payload)];
+ struct btsnoop *btsnoop;
+ struct timeval tv = { .tv_sec = 946684800, .tv_usec = 0 };
+ uint16_t size = 0;
+ uint16_t index = 0;
+ uint16_t opcode = 0;
+ char *path = new_tmp_path();
+
+ btsnoop = btsnoop_create(path, 0, 0, BTSNOOP_FORMAT_MONITOR);
+ g_assert_nonnull(btsnoop);
+ g_assert_true(btsnoop_write_hci(btsnoop, &tv, 7, 0x1234, 0,
+ payload, sizeof(payload)));
+ btsnoop_unref(btsnoop);
+
+ g_assert_true(read_trace_file(path, 0, data, sizeof(data), &size,
+ &index, &opcode, &tv));
+ g_assert_cmpint(index, ==, 7);
+ g_assert_cmpint(opcode, ==, 0x1234);
+ g_assert_cmpint(size, ==, sizeof(payload));
+ g_assert_cmpint(memcmp(data, payload, sizeof(payload)), ==, 0);
+
+ unlink(path);
+ g_free(path);
+}
+
+static void test_btsnoop_write_phy_and_rotate(void)
+{
+ const uint8_t payload[] = { 0x01 };
+ struct btsnoop *btsnoop;
+ struct timeval tv = { .tv_sec = 946684800, .tv_usec = 0 };
+ char *path = new_tmp_path();
+
+ g_assert_false(btsnoop_write(NULL, &tv, 0, 0, payload,
+ sizeof(payload)));
+
+ btsnoop = btsnoop_create(path, 24, 1, BTSNOOP_FORMAT_SIMULATOR);
+ g_assert_nonnull(btsnoop);
+ g_assert_true(btsnoop_write_phy(btsnoop, &tv, 2402, payload,
+ sizeof(payload)));
+ btsnoop_unref(btsnoop);
+
+ btsnoop = btsnoop_create(path, 0, 0, BTSNOOP_FORMAT_HCI);
+ g_assert_nonnull(btsnoop);
+ g_assert_false(btsnoop_write_phy(btsnoop, &tv, 2402, payload,
+ sizeof(payload)));
+ btsnoop_unref(btsnoop);
+
+ unlink(path);
+ unlink_rotated(path, 1);
+ g_free(path);
+}
+
+static void test_btsnoop_monitor_valid(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { 0xaa, 0xbb };
+ uint8_t data[sizeof(payload)];
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0xffff;
+ uint16_t opcode = 0xffff;
+
+ append_btsnoop_header(trace, BTSNOOP_FORMAT_MONITOR);
+ append_btsnoop_packet(trace, sizeof(payload), 0x00051234,
+ BTSNOOP_EPOCH_OFFSET, payload, sizeof(payload));
+
+ g_assert_true(read_tmp_trace(trace->data, trace->len, 0, sizeof(data),
+ data, &size, &index, &opcode, &tv));
+ g_assert_cmpint(index, ==, 5);
+ g_assert_cmpint(opcode, ==, 0x1234);
+ g_assert_cmpint(size, ==, sizeof(payload));
+ g_assert_cmpint(memcmp(data, payload, sizeof(payload)), ==, 0);
+
+ g_byte_array_unref(trace);
+}
+
+static void test_btsnoop_uart_valid(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { 0x04, 0x0e, 0x01, 0x00 };
+ uint8_t data[sizeof(payload) - 1];
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0xffff;
+ uint16_t opcode = 0xffff;
+
+ append_btsnoop_header(trace, BTSNOOP_FORMAT_UART);
+ append_btsnoop_packet(trace, sizeof(payload), 0,
+ BTSNOOP_EPOCH_OFFSET, payload, sizeof(payload));
+
+ g_assert_true(read_tmp_trace(trace->data, trace->len, 0, sizeof(data),
+ data, &size, &index, &opcode, &tv));
+ g_assert_cmpint(index, ==, 0);
+ g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_EVENT_PKT);
+ g_assert_cmpint(size, ==, sizeof(payload) - 1);
+ g_assert_cmpint(memcmp(data, payload + 1, sizeof(data)), ==, 0);
+
+ g_byte_array_unref(trace);
+}
+
+static void test_btsnoop_uart_opcode_map(void)
+{
+ static const struct {
+ uint8_t type;
+ uint32_t flags;
+ uint16_t opcode;
+ } cases[] = {
+ { 0x01, 0x00, BTSNOOP_OPCODE_COMMAND_PKT },
+ { 0x02, 0x00, BTSNOOP_OPCODE_ACL_TX_PKT },
+ { 0x02, 0x01, BTSNOOP_OPCODE_ACL_RX_PKT },
+ { 0x03, 0x00, BTSNOOP_OPCODE_SCO_TX_PKT },
+ { 0x03, 0x01, BTSNOOP_OPCODE_SCO_RX_PKT },
+ { 0x05, 0x00, BTSNOOP_OPCODE_ISO_TX_PKT },
+ { 0x05, 0x01, BTSNOOP_OPCODE_ISO_RX_PKT },
+ { 0x99, 0x00, 0xffff },
+ };
+ unsigned int i;
+
+ for (i = 0; i < G_N_ELEMENTS(cases); i++) {
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { cases[i].type, 0x00 };
+ uint8_t data[1];
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0;
+ uint16_t opcode = 0;
+
+ append_btsnoop_header(trace, BTSNOOP_FORMAT_UART);
+ append_btsnoop_packet(trace, sizeof(payload), cases[i].flags,
+ BTSNOOP_EPOCH_OFFSET, payload, sizeof(payload));
+
+ g_assert_true(read_tmp_trace(trace->data, trace->len, 0,
+ sizeof(data), data, &size,
+ &index, &opcode, &tv));
+ g_assert_cmpint(opcode, ==, cases[i].opcode);
+ g_byte_array_unref(trace);
+ }
+}
+
+static void test_btsnoop_rejects_small_capacity(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+ uint8_t data[4] = { 0xa5, 0xa5, 0xa5, 0xa5 };
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0;
+ uint16_t opcode = 0;
+
+ append_btsnoop_header(trace, BTSNOOP_FORMAT_HCI);
+ append_btsnoop_packet(trace, sizeof(payload), 0x02,
+ BTSNOOP_EPOCH_OFFSET, payload, sizeof(payload));
+
+ g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 2, data,
+ &size, &index, &opcode, &tv));
+ g_assert_cmpint(data[0], ==, 0xa5);
+ g_assert_cmpint(data[1], ==, 0xa5);
+ g_assert_cmpint(data[2], ==, 0xa5);
+ g_assert_cmpint(data[3], ==, 0xa5);
+
+ g_byte_array_unref(trace);
+}
+
+static void test_btsnoop_rejects_timestamp_underflow(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0;
+ uint16_t opcode = 0;
+
+ append_btsnoop_header(trace, BTSNOOP_FORMAT_HCI);
+ append_btsnoop_packet(trace, 0, 0x02, BTSNOOP_EPOCH_OFFSET - 1,
+ NULL, 0);
+
+ g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 0, NULL,
+ &size, &index, &opcode, &tv));
+
+ g_byte_array_unref(trace);
+}
+
+static void test_btsnoop_rejects_uart_zero_length(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0;
+ uint16_t opcode = 0;
+
+ append_btsnoop_header(trace, BTSNOOP_FORMAT_UART);
+ append_btsnoop_packet(trace, 0, 0, BTSNOOP_EPOCH_OFFSET, NULL, 0);
+
+ g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 0, NULL,
+ &size, &index, &opcode, &tv));
+
+ g_byte_array_unref(trace);
+}
+
+static void test_btsnoop_rejects_uart_short_type(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0;
+ uint16_t opcode = 0;
+
+ append_btsnoop_header(trace, BTSNOOP_FORMAT_UART);
+ append_btsnoop_packet(trace, 1, 0, BTSNOOP_EPOCH_OFFSET, NULL, 0);
+
+ g_assert_false(read_tmp_trace(trace->data, trace->len, 0, 0, NULL,
+ &size, &index, &opcode, &tv));
+
+ g_byte_array_unref(trace);
+}
+
+static void test_btsnoop_rejects_short_payload(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { 0x01, 0x02 };
+ uint8_t data[3];
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0;
+ uint16_t opcode = 0;
+
+ append_btsnoop_header(trace, BTSNOOP_FORMAT_HCI);
+ append_btsnoop_packet(trace, 3, 0x02, BTSNOOP_EPOCH_OFFSET,
+ payload, sizeof(payload));
+
+ g_assert_false(read_tmp_trace(trace->data, trace->len, 0,
+ sizeof(data), data, &size,
+ &index, &opcode, &tv));
+
+ g_byte_array_unref(trace);
+}
+
+static void test_pklg_big_endian_valid(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { 0x0e, 0x01, 0x00 };
+ uint8_t data[sizeof(payload)];
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0xffff;
+ uint16_t opcode = 0xffff;
+
+ append_pklg_packet(trace, false, sizeof(payload),
+ ((uint64_t) 123 << 32) | 456, 0x01, payload,
+ sizeof(payload));
+
+ g_assert_true(read_tmp_trace(trace->data, trace->len,
+ BTSNOOP_FLAG_PKLG_SUPPORT, sizeof(data),
+ data, &size, &index, &opcode, &tv));
+ g_assert_cmpint(index, ==, 0);
+ g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_EVENT_PKT);
+ g_assert_cmpint(size, ==, sizeof(payload));
+ g_assert_cmpint(memcmp(data, payload, sizeof(payload)), ==, 0);
+ g_assert_cmpint(tv.tv_sec, ==, 123);
+ g_assert_cmpint(tv.tv_usec, ==, 456);
+
+ g_byte_array_unref(trace);
+}
+
+static void test_pklg_little_endian_valid(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+ uint8_t data[sizeof(payload)];
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0xffff;
+ uint16_t opcode = 0xffff;
+
+ append_pklg_packet(trace, true, sizeof(payload),
+ ((uint64_t) 456 << 32) | 123, 0x00, payload,
+ sizeof(payload));
+
+ g_assert_true(read_tmp_trace(trace->data, trace->len,
+ BTSNOOP_FLAG_PKLG_SUPPORT, sizeof(data),
+ data, &size, &index, &opcode, &tv));
+ g_assert_cmpint(index, ==, 0);
+ g_assert_cmpint(opcode, ==, BTSNOOP_OPCODE_COMMAND_PKT);
+ g_assert_cmpint(size, ==, sizeof(payload));
+ g_assert_cmpint(data[0], ==, payload[0]);
+ g_assert_cmpint(tv.tv_sec, ==, 123);
+ g_assert_cmpint(tv.tv_usec, ==, 456);
+
+ g_byte_array_unref(trace);
+}
+
+static void test_pklg_rejects_short_length(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ struct test_pklg_pkt pkt;
+ const uint8_t padding[] = { 0x00, 0x00, 0x00 };
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0;
+ uint16_t opcode = 0;
+
+ pkt.len = htobe32(PKLG_PAYLOAD_OFFSET - 1);
+ pkt.ts = 0;
+ pkt.type = 0x01;
+
+ append_bytes(trace, &pkt, sizeof(pkt));
+ append_bytes(trace, padding, sizeof(padding));
+
+ g_assert_false(read_tmp_trace(trace->data, trace->len,
+ BTSNOOP_FLAG_PKLG_SUPPORT, 0, NULL,
+ &size, &index, &opcode, &tv));
+
+ g_byte_array_unref(trace);
+}
+
+static void test_pklg_rejects_small_capacity(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+ uint8_t data[4] = { 0xa5, 0xa5, 0xa5, 0xa5 };
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0;
+ uint16_t opcode = 0;
+
+ append_pklg_packet(trace, false, sizeof(payload), 0, 0x01, payload,
+ sizeof(payload));
+
+ g_assert_false(read_tmp_trace(trace->data, trace->len,
+ BTSNOOP_FLAG_PKLG_SUPPORT, 2, data,
+ &size, &index, &opcode, &tv));
+ g_assert_cmpint(data[0], ==, 0xa5);
+ g_assert_cmpint(data[1], ==, 0xa5);
+ g_assert_cmpint(data[2], ==, 0xa5);
+ g_assert_cmpint(data[3], ==, 0xa5);
+
+ g_byte_array_unref(trace);
+}
+
+static void test_pklg_rejects_short_payload(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+ uint8_t data[4];
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0;
+ uint16_t opcode = 0;
+
+ append_pklg_packet(trace, false, 4, 0, 0x01, payload,
+ sizeof(payload));
+
+ g_assert_false(read_tmp_trace(trace->data, trace->len,
+ BTSNOOP_FLAG_PKLG_SUPPORT, sizeof(data),
+ data, &size, &index, &opcode, &tv));
+
+ g_byte_array_unref(trace);
+}
+
+static void test_pklg_type_map(void)
+{
+ static const struct {
+ uint8_t type;
+ uint16_t index;
+ uint16_t opcode;
+ } cases[] = {
+ { 0x02, 0x0000, BTSNOOP_OPCODE_ACL_TX_PKT },
+ { 0x03, 0x0000, BTSNOOP_OPCODE_ACL_RX_PKT },
+ { 0x08, 0x0000, BTSNOOP_OPCODE_SCO_TX_PKT },
+ { 0x09, 0x0000, BTSNOOP_OPCODE_SCO_RX_PKT },
+ { 0x12, 0x0000, BTSNOOP_OPCODE_ISO_TX_PKT },
+ { 0x13, 0x0000, BTSNOOP_OPCODE_ISO_RX_PKT },
+ { 0x0b, 0x0000, BTSNOOP_OPCODE_VENDOR_DIAG },
+ { 0xfc, 0xffff, BTSNOOP_OPCODE_SYSTEM_NOTE },
+ { 0xaa, 0xffff, 0xffff },
+ };
+ const uint8_t payload[] = { 0x00, 0x01, 0x02 };
+ unsigned int i;
+
+ for (i = 0; i < G_N_ELEMENTS(cases); i++) {
+ GByteArray *trace = g_byte_array_new();
+ uint8_t data[sizeof(payload)];
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0;
+ uint16_t opcode = 0;
+
+ append_pklg_packet(trace, false, sizeof(payload), 0,
+ cases[i].type, payload,
+ sizeof(payload));
+
+ g_assert_true(read_tmp_trace(trace->data, trace->len,
+ BTSNOOP_FLAG_PKLG_SUPPORT,
+ sizeof(data), data, &size,
+ &index, &opcode, &tv));
+ g_assert_cmpint(index, ==, cases[i].index);
+ g_assert_cmpint(opcode, ==, cases[i].opcode);
+ g_byte_array_unref(trace);
+ }
+}
+
+static void test_btsnoop_truncation_fuzz(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+ size_t len;
+
+ append_btsnoop_header(trace, BTSNOOP_FORMAT_HCI);
+ append_btsnoop_packet(trace, sizeof(payload), 0x02,
+ BTSNOOP_EPOCH_OFFSET, payload, sizeof(payload));
+
+ for (len = 0; len < trace->len; len++) {
+ uint8_t data[sizeof(payload)];
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0;
+ uint16_t opcode = 0;
+
+ g_assert_false(read_tmp_trace(trace->data, len, 0,
+ sizeof(data), data, &size,
+ &index, &opcode, &tv));
+ }
+
+ g_byte_array_unref(trace);
+}
+
+static void test_pklg_truncation_fuzz(void)
+{
+ GByteArray *trace = g_byte_array_new();
+ const uint8_t payload[] = { 0x01, 0x02, 0x03 };
+ size_t len;
+
+ append_pklg_packet(trace, false, sizeof(payload), 0, 0x01, payload,
+ sizeof(payload));
+
+ for (len = 0; len < trace->len; len++) {
+ uint8_t data[sizeof(payload)];
+ struct timeval tv;
+ uint16_t size = 0;
+ uint16_t index = 0;
+ uint16_t opcode = 0;
+
+ g_assert_false(read_tmp_trace(trace->data, len,
+ BTSNOOP_FLAG_PKLG_SUPPORT,
+ sizeof(data), data, &size,
+ &index, &opcode, &tv));
+ }
+
+ g_byte_array_unref(trace);
+}
+
+int main(int argc, char *argv[])
+{
+ g_test_init(&argc, &argv, NULL);
+
+ g_test_add_func("/btsnoop/hci/valid", test_btsnoop_hci_valid);
+ g_test_add_func("/btsnoop/create/invalid",
+ test_btsnoop_create_invalid_args);
+ g_test_add_func("/btsnoop/open/invalid",
+ test_btsnoop_open_invalid_headers);
+ g_test_add_func("/btsnoop/write/hci-roundtrip",
+ test_btsnoop_write_hci_roundtrip);
+ g_test_add_func("/btsnoop/write/monitor-roundtrip",
+ test_btsnoop_write_monitor_roundtrip);
+ g_test_add_func("/btsnoop/write/phy-and-rotate",
+ test_btsnoop_write_phy_and_rotate);
+ g_test_add_func("/btsnoop/monitor/valid", test_btsnoop_monitor_valid);
+ g_test_add_func("/btsnoop/uart/valid", test_btsnoop_uart_valid);
+ g_test_add_func("/btsnoop/uart/opcode-map",
+ test_btsnoop_uart_opcode_map);
+ g_test_add_func("/btsnoop/capacity/reject",
+ test_btsnoop_rejects_small_capacity);
+ g_test_add_func("/btsnoop/timestamp/underflow",
+ test_btsnoop_rejects_timestamp_underflow);
+ g_test_add_func("/btsnoop/uart/zero-length",
+ test_btsnoop_rejects_uart_zero_length);
+ g_test_add_func("/btsnoop/uart/short-type",
+ test_btsnoop_rejects_uart_short_type);
+ g_test_add_func("/btsnoop/payload/short",
+ test_btsnoop_rejects_short_payload);
+ g_test_add_func("/pklg/big-endian/valid", test_pklg_big_endian_valid);
+ g_test_add_func("/pklg/little-endian/valid",
+ test_pklg_little_endian_valid);
+ g_test_add_func("/pklg/length/short", test_pklg_rejects_short_length);
+ g_test_add_func("/pklg/capacity/reject",
+ test_pklg_rejects_small_capacity);
+ g_test_add_func("/pklg/payload/short", test_pklg_rejects_short_payload);
+ g_test_add_func("/pklg/type-map", test_pklg_type_map);
+ g_test_add_func("/btsnoop/fuzz/truncation",
+ test_btsnoop_truncation_fuzz);
+ g_test_add_func("/pklg/fuzz/truncation", test_pklg_truncation_fuzz);
+
+ return g_test_run();
+}
diff --git a/unit/test-btsnoop.h b/unit/test-btsnoop.h
new file mode 100644
index 000000000..9a12f3e71
--- /dev/null
+++ b/unit/test-btsnoop.h
@@ -0,0 +1,3 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+void add_pklg_tests(void);
--
2.43.0
^ permalink raw reply related
* [PATCH BlueZ 2/2] audio: reduce a2dp parser complexity
From: Geraldo Netto @ 2026-06-20 19:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Geraldo Netto
In-Reply-To: <20260620191735.2675946-1-geraldonetto@gmail.com>
---
profiles/audio/a2dp-helpers.c | 55 +++++++++++++++++++++++++++--------
1 file changed, 43 insertions(+), 12 deletions(-)
diff --git a/profiles/audio/a2dp-helpers.c b/profiles/audio/a2dp-helpers.c
index 035236df6..09dc6db91 100644
--- a/profiles/audio/a2dp-helpers.c
+++ b/profiles/audio/a2dp-helpers.c
@@ -74,6 +74,46 @@ static bool parse_caps(const char *value, uint8_t *caps, size_t caps_len,
return true;
}
+static bool has_delay_field(const char *value)
+{
+ return value[0] != '\0' && value[1] != '\0' &&
+ g_ascii_isxdigit(value[0]) &&
+ g_ascii_isxdigit(value[1]) &&
+ value[2] == ':';
+}
+
+static bool parse_endpoint_header(const char **value, uint8_t *type,
+ uint8_t *codec)
+{
+ if (!parse_hex_byte(value, type) || !parse_colon(value))
+ return false;
+
+ if (!parse_hex_byte(value, codec) || !parse_colon(value))
+ return false;
+
+ return true;
+}
+
+static bool parse_delay_field(const char **value, uint8_t *delay)
+{
+ *delay = 0;
+
+ if (!has_delay_field(*value))
+ return true;
+
+ parse_hex_byte(value, delay);
+ parse_colon(value);
+
+ return *delay <= 1;
+}
+
+static bool valid_endpoint_args(const char *value,
+ const uint8_t *type, const uint8_t *codec,
+ const bool *delay_reporting, const size_t *size)
+{
+ return value && type && codec && delay_reporting && size;
+}
+
bool a2dp_parse_capabilities_array(DBusMessageIter *value,
uint8_t **caps, int *size)
{
@@ -106,27 +146,18 @@ bool a2dp_parse_persisted_endpoint(const char *value, uint8_t *type,
const char *pos;
uint8_t delay = 0;
- if (!value || !type || !codec || !delay_reporting || !size)
+ if (!valid_endpoint_args(value, type, codec, delay_reporting, size))
return false;
*size = 0;
pos = value;
- if (!parse_hex_byte(&pos, type) || !parse_colon(&pos))
+ if (!parse_endpoint_header(&pos, type, codec))
return false;
- if (!parse_hex_byte(&pos, codec) || !parse_colon(&pos))
+ if (!parse_delay_field(&pos, &delay))
return false;
- if (pos[0] != '\0' && pos[1] != '\0' &&
- g_ascii_isxdigit(pos[0]) && g_ascii_isxdigit(pos[1]) &&
- pos[2] == ':') {
- parse_hex_byte(&pos, &delay);
- parse_colon(&pos);
- if (delay > 1)
- return false;
- }
-
if (!parse_caps(pos, caps, caps_len, size))
return false;
--
2.43.0
^ permalink raw reply related
* [PATCH BlueZ 1/2] audio: harden a2dp parsers
From: Geraldo Netto @ 2026-06-20 19:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Geraldo Netto
In-Reply-To: <20260620191735.2675946-1-geraldonetto@gmail.com>
---
Makefile.am | 7 +
Makefile.plugins | 1 +
profiles/audio/a2dp-helpers.c | 136 ++++++++++++++++
profiles/audio/a2dp-helpers.h | 20 +++
profiles/audio/a2dp.c | 86 +++++-----
unit/test-a2dp.c | 288 ++++++++++++++++++++++++++++++++++
6 files changed, 489 insertions(+), 49 deletions(-)
create mode 100644 profiles/audio/a2dp-helpers.c
create mode 100644 profiles/audio/a2dp-helpers.h
create mode 100644 unit/test-a2dp.c
diff --git a/Makefile.am b/Makefile.am
index 76c4ab5d4..35871cc57 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -642,6 +642,13 @@ unit_test_avdtp_SOURCES = unit/test-avdtp.c \
unit/avdtp.c unit/avdtp.h
unit_test_avdtp_LDADD = src/libshared-glib.la $(GLIB_LIBS)
+unit_tests += unit/test-a2dp
+
+unit_test_a2dp_SOURCES = unit/test-a2dp.c \
+ profiles/audio/a2dp-helpers.c \
+ profiles/audio/a2dp-helpers.h
+unit_test_a2dp_LDADD = src/libshared-glib.la $(GLIB_LIBS) $(DBUS_LIBS)
+
unit_tests += unit/test-avctp
unit_test_avctp_SOURCES = unit/test-avctp.c \
diff --git a/Makefile.plugins b/Makefile.plugins
index ac667beda..57400d877 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -27,6 +27,7 @@ builtin_modules += a2dp
builtin_sources += profiles/audio/source.h profiles/audio/source.c \
profiles/audio/sink.h profiles/audio/sink.c \
profiles/audio/a2dp.h profiles/audio/a2dp.c \
+ profiles/audio/a2dp-helpers.h profiles/audio/a2dp-helpers.c \
profiles/audio/avdtp.h profiles/audio/avdtp.c \
profiles/audio/a2dp-codecs.h
endif
diff --git a/profiles/audio/a2dp-helpers.c b/profiles/audio/a2dp-helpers.c
new file mode 100644
index 000000000..035236df6
--- /dev/null
+++ b/profiles/audio/a2dp-helpers.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <dbus/dbus.h>
+#include <glib.h>
+
+#include "a2dp-helpers.h"
+
+static bool parse_hex_byte(const char **value, uint8_t *byte)
+{
+ int high;
+ int low;
+
+ if ((*value)[0] == '\0' || (*value)[1] == '\0')
+ return false;
+
+ high = g_ascii_xdigit_value((*value)[0]);
+ low = g_ascii_xdigit_value((*value)[1]);
+ if (high < 0 || low < 0)
+ return false;
+
+ *byte = high << 4 | low;
+ *value += 2;
+
+ return true;
+}
+
+static bool parse_colon(const char **value)
+{
+ if (**value != ':')
+ return false;
+
+ (*value)++;
+
+ return true;
+}
+
+static bool parse_caps(const char *value, uint8_t *caps, size_t caps_len,
+ size_t *size)
+{
+ size_t len;
+ size_t i;
+
+ if (!value || !caps || !size)
+ return false;
+
+ *size = 0;
+
+ len = strlen(value);
+ if (!len || len % 2 || len / 2 > caps_len)
+ return false;
+
+ for (i = 0; i < len; i++) {
+ if (!g_ascii_isxdigit(value[i]))
+ return false;
+ }
+
+ for (i = 0; i < len; i += 2) {
+ const char *pos = value + i;
+
+ parse_hex_byte(&pos, &caps[i / 2]);
+ }
+
+ *size = len / 2;
+
+ return true;
+}
+
+bool a2dp_parse_capabilities_array(DBusMessageIter *value,
+ uint8_t **caps, int *size)
+{
+ DBusMessageIter array;
+
+ if (!value || !caps || !size)
+ return false;
+
+ *caps = NULL;
+ *size = 0;
+
+ if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_ARRAY)
+ return false;
+
+ if (dbus_message_iter_get_element_type(value) != DBUS_TYPE_BYTE)
+ return false;
+
+ dbus_message_iter_recurse(value, &array);
+ dbus_message_iter_get_fixed_array(&array, caps, size);
+
+ return *caps && *size > 0;
+}
+
+bool a2dp_parse_persisted_endpoint(const char *value, uint8_t *type,
+ uint8_t *codec,
+ bool *delay_reporting,
+ uint8_t *caps, size_t caps_len,
+ size_t *size)
+{
+ const char *pos;
+ uint8_t delay = 0;
+
+ if (!value || !type || !codec || !delay_reporting || !size)
+ return false;
+
+ *size = 0;
+
+ pos = value;
+ if (!parse_hex_byte(&pos, type) || !parse_colon(&pos))
+ return false;
+
+ if (!parse_hex_byte(&pos, codec) || !parse_colon(&pos))
+ return false;
+
+ if (pos[0] != '\0' && pos[1] != '\0' &&
+ g_ascii_isxdigit(pos[0]) && g_ascii_isxdigit(pos[1]) &&
+ pos[2] == ':') {
+ parse_hex_byte(&pos, &delay);
+ parse_colon(&pos);
+ if (delay > 1)
+ return false;
+ }
+
+ if (!parse_caps(pos, caps, caps_len, size))
+ return false;
+
+ *delay_reporting = delay;
+
+ return true;
+}
diff --git a/profiles/audio/a2dp-helpers.h b/profiles/audio/a2dp-helpers.h
new file mode 100644
index 000000000..a5c90c516
--- /dev/null
+++ b/profiles/audio/a2dp-helpers.h
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifndef BLUEZ_A2DP_HELPERS_H
+#define BLUEZ_A2DP_HELPERS_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <dbus/dbus.h>
+
+bool a2dp_parse_capabilities_array(DBusMessageIter *value,
+ uint8_t **caps, int *size);
+bool a2dp_parse_persisted_endpoint(const char *value, uint8_t *type,
+ uint8_t *codec,
+ bool *delay_reporting,
+ uint8_t *caps, size_t caps_len,
+ size_t *size);
+
+#endif /* BLUEZ_A2DP_HELPERS_H */
diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c
index a5e002784..0999436c5 100644
--- a/profiles/audio/a2dp.c
+++ b/profiles/audio/a2dp.c
@@ -52,6 +52,7 @@
#include "sink.h"
#include "source.h"
#include "a2dp.h"
+#include "a2dp-helpers.h"
#include "a2dp-codecs.h"
#include "media.h"
@@ -689,7 +690,7 @@ static void stream_state_changed(struct avdtp_stream *stream,
if (err < 0 && err != -EINPROGRESS) {
error("avdtp_start: %s (%d)", strerror(-err), -err);
finalize_setup_errno(setup, err, finalize_resume,
- NULL);
+ (GSourceFunc) NULL);
return;
}
@@ -942,7 +943,8 @@ static void endpoint_open_cb(struct a2dp_setup *setup, uint8_t error_code)
if (error_code != 0) {
setup->stream = NULL;
- finalize_setup_errno(setup, -EPERM, finalize_config, NULL);
+ finalize_setup_errno(setup, -EPERM, finalize_config,
+ (GSourceFunc) NULL);
goto done;
}
@@ -954,7 +956,7 @@ static void endpoint_open_cb(struct a2dp_setup *setup, uint8_t error_code)
error("avdtp_open %s (%d)", strerror(-err), -err);
setup->stream = NULL;
- finalize_setup_errno(setup, err, finalize_config, NULL);
+ finalize_setup_errno(setup, err, finalize_config, (GSourceFunc) NULL);
done:
setup_unref(setup);
}
@@ -974,6 +976,7 @@ static void store_remote_sep(void *data, void *user_data)
char seid[4], value[256];
struct avdtp_service_capability *service = avdtp_get_codec(sep->sep);
struct avdtp_media_codec_capability *codec;
+ uint8_t delay_reporting;
unsigned int i;
ssize_t offset;
@@ -981,12 +984,13 @@ static void store_remote_sep(void *data, void *user_data)
return;
codec = (void *) service->data;
+ delay_reporting = avdtp_get_delay_reporting(sep->sep);
sprintf(seid, "%02hhx", avdtp_get_seid(sep->sep));
offset = sprintf(value, "%02hhx:%02hhx:%02hhx:",
avdtp_get_type(sep->sep), codec->media_codec_type,
- avdtp_get_delay_reporting(sep->sep));
+ delay_reporting);
for (i = 0; i < service->length - sizeof(*codec); i++)
offset += sprintf(value + offset, "%02hhx", codec->data[i]);
@@ -1139,7 +1143,8 @@ static void setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
return;
setup->stream = NULL;
- finalize_setup_errno(setup, -EPERM, finalize_config, NULL);
+ finalize_setup_errno(setup, -EPERM, finalize_config,
+ (GSourceFunc) NULL);
setup_unref(setup);
return;
}
@@ -1148,7 +1153,8 @@ static void setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
if (ret < 0) {
error("avdtp_open %s (%d)", strerror(-ret), -ret);
setup->stream = NULL;
- finalize_setup_errno(setup, ret, finalize_config, NULL);
+ finalize_setup_errno(setup, ret, finalize_config,
+ (GSourceFunc) NULL);
}
}
@@ -1431,7 +1437,8 @@ static gboolean suspend_ind(struct avdtp *session, struct avdtp_local_sep *sep,
if (start_err < 0 && start_err != -EINPROGRESS) {
error("avdtp_start: %s (%d)", strerror(-start_err),
-start_err);
- finalize_setup_errno(setup, start_err, finalize_resume, NULL);
+ finalize_setup_errno(setup, start_err, finalize_resume,
+ (GSourceFunc) NULL);
}
return TRUE;
@@ -1483,7 +1490,8 @@ static void suspend_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
start_err = avdtp_start(session, a2dp_stream->stream);
if (start_err < 0 && start_err != -EINPROGRESS) {
error("avdtp_start: %s (%d)", strerror(-start_err), -start_err);
- finalize_setup_errno(setup, start_err, finalize_suspend, NULL);
+ finalize_setup_errno(setup, start_err, finalize_suspend,
+ (GSourceFunc) NULL);
}
}
@@ -1504,7 +1512,7 @@ static gboolean close_ind(struct avdtp *session, struct avdtp_local_sep *sep,
return TRUE;
finalize_setup_errno(setup, -ECONNRESET, finalize_suspend,
- finalize_resume, NULL);
+ finalize_resume, (GSourceFunc) NULL);
return TRUE;
}
@@ -1572,7 +1580,8 @@ static gboolean a2dp_reconfigure(gpointer data)
return FALSE;
failed:
- finalize_setup_errno(setup, posix_err, finalize_config, NULL);
+ finalize_setup_errno(setup, posix_err, finalize_config,
+ (GSourceFunc) NULL);
return FALSE;
}
@@ -1648,8 +1657,8 @@ static void abort_ind(struct avdtp *session, struct avdtp_local_sep *sep,
return;
finalize_setup_errno(setup, -ECONNRESET, finalize_suspend,
- finalize_resume,
- finalize_config, NULL);
+ finalize_resume, finalize_config,
+ (GSourceFunc) NULL);
return;
}
@@ -1901,7 +1910,8 @@ static void channel_free(void *data)
setup->chan = NULL;
setup_ref(setup);
/* Finalize pending commands before we NULL setup->session */
- finalize_setup_errno(setup, -ENOTCONN, finalize_all, NULL);
+ finalize_setup_errno(setup, -ENOTCONN, finalize_all,
+ (GSourceFunc) NULL);
avdtp_unref(setup->session);
setup->session = NULL;
setup_unref(setup);
@@ -1991,10 +2001,12 @@ static struct a2dp_sep *find_sep(struct a2dp_server *server, uint8_t type,
static int parse_properties(DBusMessageIter *props, uint8_t **caps, int *size)
{
+ *caps = NULL;
+ *size = 0;
+
while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) {
const char *key;
DBusMessageIter value, entry;
- int var;
dbus_message_iter_recurse(props, &entry);
dbus_message_iter_get_basic(&entry, &key);
@@ -2002,15 +2014,10 @@ static int parse_properties(DBusMessageIter *props, uint8_t **caps, int *size)
dbus_message_iter_next(&entry);
dbus_message_iter_recurse(&entry, &value);
- var = dbus_message_iter_get_arg_type(&value);
if (strcasecmp(key, "Capabilities") == 0) {
- DBusMessageIter array;
-
- if (var != DBUS_TYPE_ARRAY)
+ if (!a2dp_parse_capabilities_array(&value, caps, size))
return -EINVAL;
- dbus_message_iter_recurse(&value, &array);
- dbus_message_iter_get_fixed_array(&array, caps, size);
return 0;
}
@@ -2130,7 +2137,7 @@ static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg,
struct avdtp_media_codec_capability *codec;
DBusMessageIter args, props;
const char *sender, *path;
- uint8_t *caps;
+ uint8_t *caps = NULL;
int err, size = 0;
sender = dbus_message_get_sender(msg);
@@ -2371,11 +2378,10 @@ static void load_remote_sep(struct a2dp_channel *chan, GKeyFile *key_file,
for (; *seids; seids++) {
uint8_t type;
uint8_t codec;
- uint8_t delay_reporting;
+ bool delay_reporting;
GSList *l = NULL;
- char caps[256];
uint8_t data[128];
- int i, size;
+ size_t size;
if (sscanf(*seids, "%02hhx", &rseid) != 1)
continue;
@@ -2385,34 +2391,16 @@ static void load_remote_sep(struct a2dp_channel *chan, GKeyFile *key_file,
if (!value)
continue;
- /* Try loading with delay_reporting first */
- if (sscanf(value, "%02hhx:%02hhx:%02hhx:%s", &type, &codec,
- &delay_reporting, caps) != 4) {
- /* Try old format */
- if (sscanf(value, "%02hhx:%02hhx:%s", &type, &codec,
- caps) != 3) {
- warn("Unable to load Endpoint: seid %u", rseid);
- g_free(value);
- continue;
- }
- delay_reporting = false;
- }
-
- for (i = 0, size = strlen(caps); i < size; i += 2) {
- uint8_t *tmp = data + i / 2;
-
- if (sscanf(caps + i, "%02hhx", tmp) != 1) {
- warn("Unable to load Endpoint: seid %u", rseid);
- break;
- }
+ if (!a2dp_parse_persisted_endpoint(value, &type, &codec,
+ &delay_reporting, data,
+ sizeof(data), &size)) {
+ warn("Unable to load Endpoint: seid %u", rseid);
+ g_free(value);
+ continue;
}
-
g_free(value);
- if (i != size)
- continue;
-
- caps_add_codec(&l, codec, data, size / 2);
+ caps_add_codec(&l, codec, data, size);
rsep = avdtp_register_remote_sep(chan->session, rseid, type, l,
delay_reporting);
diff --git a/unit/test-a2dp.c b/unit/test-a2dp.c
new file mode 100644
index 000000000..2f7bd7bbc
--- /dev/null
+++ b/unit/test-a2dp.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <dbus/dbus.h>
+#include <glib.h>
+
+#include "profiles/audio/a2dp-helpers.h"
+
+static DBusMessage *new_method_call(void)
+{
+ return dbus_message_new_method_call("org.bluez.test",
+ "/org/bluez/test",
+ "org.bluez.test",
+ "Test");
+}
+
+static void append_byte_array(DBusMessage *msg, const uint8_t *data, int size)
+{
+ DBusMessageIter iter;
+ DBusMessageIter array;
+
+ dbus_message_iter_init_append(msg, &iter);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_BYTE_AS_STRING,
+ &array);
+ dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE,
+ &data, size);
+ dbus_message_iter_close_container(&iter, &array);
+}
+
+static void append_string_array(DBusMessage *msg)
+{
+ DBusMessageIter iter;
+ DBusMessageIter array;
+ const char *value = "not-bytes";
+
+ dbus_message_iter_init_append(msg, &iter);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_STRING_AS_STRING,
+ &array);
+ dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &value);
+ dbus_message_iter_close_container(&iter, &array);
+}
+
+static void test_capabilities_array_accepts_byte_array(void)
+{
+ DBusMessage *msg = new_method_call();
+ DBusMessageIter iter;
+ const uint8_t bytes[] = { 0x11, 0x22, 0x33 };
+ uint8_t *caps = NULL;
+ int size = 0;
+
+ g_assert_nonnull(msg);
+
+ append_byte_array(msg, bytes, sizeof(bytes));
+ g_assert_true(dbus_message_iter_init(msg, &iter));
+ g_assert_true(a2dp_parse_capabilities_array(&iter, &caps, &size));
+ g_assert_cmpint(size, ==, 3);
+ g_assert_nonnull(caps);
+ g_assert_cmpint(memcmp(caps, bytes, sizeof(bytes)), ==, 0);
+
+ dbus_message_unref(msg);
+}
+
+static void test_capabilities_array_rejects_wrong_element_type(void)
+{
+ DBusMessage *msg = new_method_call();
+ DBusMessageIter iter;
+ uint8_t *caps = (void *) 0x01;
+ int size = 1;
+
+ g_assert_nonnull(msg);
+
+ append_string_array(msg);
+ g_assert_true(dbus_message_iter_init(msg, &iter));
+ g_assert_false(a2dp_parse_capabilities_array(&iter, &caps, &size));
+ g_assert_null(caps);
+ g_assert_cmpint(size, ==, 0);
+
+ dbus_message_unref(msg);
+}
+
+static void test_capabilities_array_rejects_empty_array(void)
+{
+ DBusMessage *msg = new_method_call();
+ DBusMessageIter iter;
+ uint8_t *caps = (void *) 0x01;
+ int size = 1;
+
+ g_assert_nonnull(msg);
+
+ append_byte_array(msg, NULL, 0);
+ g_assert_true(dbus_message_iter_init(msg, &iter));
+ g_assert_false(a2dp_parse_capabilities_array(&iter, &caps, &size));
+ g_assert_cmpint(size, ==, 0);
+
+ dbus_message_unref(msg);
+}
+
+static void test_capabilities_array_rejects_missing_iter(void)
+{
+ uint8_t *caps = (void *) 0x01;
+ int size = 1;
+
+ g_assert_false(a2dp_parse_capabilities_array(NULL, &caps, &size));
+}
+
+static void test_capabilities_array_rejects_non_array(void)
+{
+ DBusMessage *msg = new_method_call();
+ DBusMessageIter iter;
+ const char *value = "not-array";
+ uint8_t *caps = (void *) 0x01;
+ int size = 1;
+
+ g_assert_nonnull(msg);
+
+ dbus_message_iter_init_append(msg, &iter);
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &value);
+ g_assert_true(dbus_message_iter_init(msg, &iter));
+ g_assert_false(a2dp_parse_capabilities_array(&iter, &caps, &size));
+ g_assert_null(caps);
+ g_assert_cmpint(size, ==, 0);
+
+ dbus_message_unref(msg);
+}
+
+static void assert_endpoint(const char *value, uint8_t expected_type,
+ uint8_t expected_codec,
+ bool expected_delay,
+ const uint8_t *expected_caps,
+ size_t expected_size)
+{
+ uint8_t type = 0xff;
+ uint8_t codec = 0xff;
+ bool delay_reporting = true;
+ uint8_t caps[128];
+ size_t size = 0;
+
+ memset(caps, 0xa5, sizeof(caps));
+
+ g_assert_true(a2dp_parse_persisted_endpoint(value, &type, &codec,
+ &delay_reporting,
+ caps, sizeof(caps),
+ &size));
+ g_assert_cmpint(type, ==, expected_type);
+ g_assert_cmpint(codec, ==, expected_codec);
+ g_assert_cmpint(delay_reporting, ==, expected_delay);
+ g_assert_cmpuint(size, ==, expected_size);
+ g_assert_cmpint(memcmp(caps, expected_caps, expected_size), ==, 0);
+}
+
+static void test_endpoint_parser_accepts_current_format(void)
+{
+ const uint8_t caps[] = { 0x11, 0x22, 0x33 };
+
+ assert_endpoint("00:40:01:112233", 0x00, 0x40, true, caps,
+ sizeof(caps));
+}
+
+static void test_endpoint_parser_accepts_old_format(void)
+{
+ const uint8_t caps[] = { 0xaa, 0xbb };
+
+ assert_endpoint("01:02:aabb", 0x01, 0x02, false, caps, sizeof(caps));
+}
+
+static void assert_endpoint_rejected(const char *value)
+{
+ uint8_t type = 0xff;
+ uint8_t codec = 0xff;
+ bool delay_reporting = true;
+ uint8_t caps[4] = { 0xa5, 0xa5, 0xa5, 0xa5 };
+ size_t size = 7;
+
+ g_assert_false(a2dp_parse_persisted_endpoint(value, &type, &codec,
+ &delay_reporting,
+ caps, sizeof(caps),
+ &size));
+ g_assert_cmpint(caps[0], ==, 0xa5);
+ g_assert_cmpint(caps[1], ==, 0xa5);
+ g_assert_cmpint(caps[2], ==, 0xa5);
+ g_assert_cmpint(caps[3], ==, 0xa5);
+}
+
+static void test_endpoint_parser_rejects_invalid_fields(void)
+{
+ assert_endpoint_rejected(NULL);
+ assert_endpoint_rejected("");
+ assert_endpoint_rejected("00:40");
+ assert_endpoint_rejected("00:40:");
+ assert_endpoint_rejected("00:40:01:");
+ assert_endpoint_rejected("00:40:02:aabb");
+ assert_endpoint_rejected("00:40:01:aab");
+ assert_endpoint_rejected("00:40:01:aazz");
+ assert_endpoint_rejected("00:40:01:aa:bb");
+ assert_endpoint_rejected("00:40:aabb:");
+ assert_endpoint_rejected("xx:40:01:aabb");
+}
+
+static void test_endpoint_parser_rejects_missing_output_buffer(void)
+{
+ uint8_t type;
+ uint8_t codec;
+ bool delay_reporting;
+ size_t size;
+
+ g_assert_false(a2dp_parse_persisted_endpoint("00:40:01:aabb",
+ &type, &codec,
+ &delay_reporting,
+ NULL, 0, &size));
+}
+
+static void test_endpoint_parser_rejects_oversized_caps(void)
+{
+ char value[sizeof("00:40:01:") + 16];
+
+ memset(value, 'a', sizeof(value));
+ memcpy(value, "00:40:01:", strlen("00:40:01:"));
+ value[sizeof(value) - 1] = '\0';
+
+ assert_endpoint_rejected(value);
+}
+
+static void test_endpoint_parser_fuzz_cases_keep_bounds(void)
+{
+ static const char alphabet[] = "0123456789abcdefABCDEF:gZ";
+ unsigned int i;
+
+ for (i = 0; i < 4096; i++) {
+ char value[16];
+ uint8_t type;
+ uint8_t codec;
+ bool delay_reporting;
+ uint8_t caps[6] = { 0, 0, 0, 0, 0xcc, 0xdd };
+ size_t size;
+ size_t len = i % (sizeof(value) - 1);
+ size_t j;
+
+ for (j = 0; j < len; j++)
+ value[j] = alphabet[(i + j * 7) %
+ (sizeof(alphabet) - 1)];
+ value[len] = '\0';
+
+ a2dp_parse_persisted_endpoint(value, &type, &codec,
+ &delay_reporting, caps, 4,
+ &size);
+ g_assert_cmpint(caps[4], ==, 0xcc);
+ g_assert_cmpint(caps[5], ==, 0xdd);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ g_test_init(&argc, &argv, NULL);
+
+ g_test_add_func("/a2dp/capabilities/byte-array",
+ test_capabilities_array_accepts_byte_array);
+ g_test_add_func("/a2dp/capabilities/wrong-element-type",
+ test_capabilities_array_rejects_wrong_element_type);
+ g_test_add_func("/a2dp/capabilities/empty-array",
+ test_capabilities_array_rejects_empty_array);
+ g_test_add_func("/a2dp/capabilities/missing-iter",
+ test_capabilities_array_rejects_missing_iter);
+ g_test_add_func("/a2dp/capabilities/non-array",
+ test_capabilities_array_rejects_non_array);
+ g_test_add_func("/a2dp/endpoint/current-format",
+ test_endpoint_parser_accepts_current_format);
+ g_test_add_func("/a2dp/endpoint/old-format",
+ test_endpoint_parser_accepts_old_format);
+ g_test_add_func("/a2dp/endpoint/invalid-fields",
+ test_endpoint_parser_rejects_invalid_fields);
+ g_test_add_func("/a2dp/endpoint/missing-output-buffer",
+ test_endpoint_parser_rejects_missing_output_buffer);
+ g_test_add_func("/a2dp/endpoint/oversized-caps",
+ test_endpoint_parser_rejects_oversized_caps);
+ g_test_add_func("/a2dp/endpoint/fuzz-bounds",
+ test_endpoint_parser_fuzz_cases_keep_bounds);
+
+ return g_test_run();
+}
--
2.43.0
^ permalink raw reply related
* [PATCH BlueZ 0/2] audio: harden A2DP parser handling
From: Geraldo Netto @ 2026-06-20 19:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Geraldo Netto
This series hardens the A2DP persisted endpoint parsing path and adds
focused unit coverage around the parser behavior. It also splits the
parser helpers out of a2dp.c to keep the endpoint parsing code easier to
test and review.
These are my first BlueZ patches, so feedback on style, structure, or
submission format is welcome.
Geraldo Netto (2):
audio: harden a2dp parsers
audio: reduce a2dp parser complexity
Makefile.am | 7 +
Makefile.plugins | 1 +
profiles/audio/a2dp-helpers.c | 167 ++++++++++++++++++++
profiles/audio/a2dp-helpers.h | 20 +++
profiles/audio/a2dp.c | 86 +++++-----
unit/test-a2dp.c | 288 ++++++++++++++++++++++++++++++++++
6 files changed, 520 insertions(+), 49 deletions(-)
create mode 100644 profiles/audio/a2dp-helpers.c
create mode 100644 profiles/audio/a2dp-helpers.h
create mode 100644 unit/test-a2dp.c
--
2.43.0
^ permalink raw reply
* Re: [syzbot] [bluetooth?] KASAN: slab-use-after-free Write in bt_accept_dequeue
From: Hillf Danton @ 2026-06-20 11:20 UTC (permalink / raw)
To: Safa Karakuş
Cc: syzbot, linux-bluetooth, linux-kernel, luiz.dentz, marcel,
syzkaller-bugs
In-Reply-To: <6a35e17e.6813c476.3c3d96.0001.GAE@google.com>
> Date: Fri, 19 Jun 2026 17:40:30 -0700
> Hello,
>
> syzbot found the following issue on:
>
> HEAD commit: 8c13415c8a43 Merge tag 'media/v7.2-1' of git://git.kernel...
> git tree: upstream
> console output: https://syzkaller.appspot.com/x/log.txt?x=12f94d56580000
> kernel config: https://syzkaller.appspot.com/x/.config?x=6a86d80fdec25236
> dashboard link: https://syzkaller.appspot.com/bug?extid=674ff7e4d7fdfd572afc
> compiler: gcc (Debian 14.2.0-19) 14.2.0, GNU ld (GNU Binutils for Debian) 2.44
>
> Unfortunately, I don't have any reproducer for this issue yet.
>
> Downloadable assets:
> disk image: https://storage.googleapis.com/syzbot-assets/3ecc105c1b32/disk-8c13415c.raw.xz
> vmlinux: https://storage.googleapis.com/syzbot-assets/cda6390db722/vmlinux-8c13415c.xz
> kernel image: https://storage.googleapis.com/syzbot-assets/5f3d326d7d36/bzImage-8c13415c.xz
>
> IMPORTANT: if you fix the issue, please add the following tag to the commit:
> Reported-by: syzbot+674ff7e4d7fdfd572afc@syzkaller.appspotmail.com
>
> ==================================================================
> BUG: KASAN: slab-use-after-free in instrument_atomic_read_write include/linux/instrumented.h:112 [inline]
> BUG: KASAN: slab-use-after-free in atomic_fetch_add_relaxed include/linux/atomic/atomic-instrumented.h:252 [inline]
> BUG: KASAN: slab-use-after-free in __refcount_add include/linux/refcount.h:283 [inline]
> BUG: KASAN: slab-use-after-free in __refcount_inc include/linux/refcount.h:366 [inline]
> BUG: KASAN: slab-use-after-free in refcount_inc include/linux/refcount.h:383 [inline]
> BUG: KASAN: slab-use-after-free in sock_hold include/net/sock.h:839 [inline]
> BUG: KASAN: slab-use-after-free in bt_accept_dequeue+0x6bd/0x920 net/bluetooth/af_bluetooth.c:348
> Write of size 4 at addr ffff888036a17080 by task syz.2.3018/19569
>
> CPU: 0 UID: 0 PID: 19569 Comm: syz.2.3018 Tainted: G L syzkaller #0 PREEMPT(full)
> Tainted: [L]=SOFTLOCKUP
> Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
> Call Trace:
> <TASK>
> __dump_stack lib/dump_stack.c:94 [inline]
> dump_stack_lvl+0x100/0x190 lib/dump_stack.c:120
> print_address_description mm/kasan/report.c:378 [inline]
> print_report+0x13d/0x4b0 mm/kasan/report.c:482
> kasan_report+0xdf/0x1c0 mm/kasan/report.c:595
> check_region_inline mm/kasan/generic.c:186 [inline]
> kasan_check_range+0x10f/0x1e0 mm/kasan/generic.c:200
> instrument_atomic_read_write include/linux/instrumented.h:112 [inline]
> atomic_fetch_add_relaxed include/linux/atomic/atomic-instrumented.h:252 [inline]
> __refcount_add include/linux/refcount.h:283 [inline]
> __refcount_inc include/linux/refcount.h:366 [inline]
> refcount_inc include/linux/refcount.h:383 [inline]
> sock_hold include/net/sock.h:839 [inline]
> bt_accept_dequeue+0x6bd/0x920 net/bluetooth/af_bluetooth.c:348
Sounds like ab1513597c6c ("Bluetooth: fix UAF in l2cap_sock_cleanup_listen()
vs l2cap_conn_del()") fails to fix the root cause.
> l2cap_sock_cleanup_listen+0x47/0x4d0 net/bluetooth/l2cap_sock.c:1517
> l2cap_sock_release+0x69/0x280 net/bluetooth/l2cap_sock.c:1466
> __sock_release+0xb3/0x260 net/socket.c:710
> sock_close+0x1c/0x30 net/socket.c:1501
> __fput+0x3ff/0xb50 fs/file_table.c:512
> task_work_run+0x150/0x240 kernel/task_work.c:233
> get_signal+0x1bd/0x21e0 kernel/signal.c:2810
> arch_do_signal_or_restart+0x91/0x7e0 arch/x86/kernel/signal.c:337
> __exit_to_user_mode_loop kernel/entry/common.c:66 [inline]
> exit_to_user_mode_loop+0x139/0x6f0 kernel/entry/common.c:101
> __exit_to_user_mode_prepare include/linux/irq-entry-common.h:207 [inline]
> syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:230 [inline]
> syscall_exit_to_user_mode include/linux/entry-common.h:318 [inline]
> do_syscall_64+0x666/0x870 arch/x86/entry/syscall_64.c:100
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
> RIP: 0033:0x7fc6feb9ce59
> Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
> RSP: 002b:00007fc6ff9a4028 EFLAGS: 00000246 ORIG_RAX: 0000000000000120
> RAX: fffffffffffffe00 RBX: 00007fc6fee15fa0 RCX: 00007fc6feb9ce59
> RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000005
> RBP: 00007fc6fec32e6f R08: 0000000000000000 R09: 0000000000000000
> R10: 0000000000000800 R11: 0000000000000246 R12: 0000000000000000
> R13: 00007fc6fee16038 R14: 00007fc6fee15fa0 R15: 00007ffdf85f63a8
> </TASK>
>
> Allocated by task 19587:
> kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
> kasan_save_track+0x14/0x30 mm/kasan/common.c:78
> poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
> __kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415
> kasan_kmalloc include/linux/kasan.h:263 [inline]
> __do_kmalloc_node mm/slub.c:5334 [inline]
> __kmalloc_noprof+0x302/0x840 mm/slub.c:5347
> _kmalloc_noprof include/linux/slab.h:973 [inline]
> sk_prot_alloc+0x10b/0x2a0 net/core/sock.c:2252
> sk_alloc+0x36/0xe80 net/core/sock.c:2308
> bt_sock_alloc+0x3b/0x3c0 net/bluetooth/af_bluetooth.c:148
> l2cap_sock_alloc.constprop.0+0x33/0x1e0 net/bluetooth/l2cap_sock.c:1983
> l2cap_sock_new_connection_cb+0x10f/0x260 net/bluetooth/l2cap_sock.c:1562
> l2cap_connect_cfm+0x4e2/0xf80 net/bluetooth/l2cap_core.c:7481
> hci_connect_cfm include/net/bluetooth/hci_core.h:2136 [inline]
> hci_remote_features_evt+0x4f4/0x9b0 net/bluetooth/hci_event.c:3764
> hci_event_func net/bluetooth/hci_event.c:7800 [inline]
> hci_event_packet+0x8e9/0xcd0 net/bluetooth/hci_event.c:7851
> hci_rx_work+0x451/0xfc0 net/bluetooth/hci_core.c:4039
> process_one_work+0xa23/0x1940 kernel/workqueue.c:3322
> process_scheduled_works kernel/workqueue.c:3405 [inline]
> worker_thread+0x5ef/0xe50 kernel/workqueue.c:3486
> kthread+0x370/0x450 kernel/kthread.c:436
> ret_from_fork+0x72b/0xd50 arch/x86/kernel/process.c:158
> ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
>
> Freed by task 19569:
> kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
> kasan_save_track+0x14/0x30 mm/kasan/common.c:78
> kasan_save_free_info+0x3b/0x70 mm/kasan/generic.c:584
> poison_slab_object mm/kasan/common.c:253 [inline]
> __kasan_slab_free+0x5f/0x80 mm/kasan/common.c:285
> kasan_slab_free include/linux/kasan.h:235 [inline]
> slab_free_hook mm/slub.c:2700 [inline]
> slab_free mm/slub.c:6310 [inline]
> kfree+0x22b/0x6c0 mm/slub.c:6625
> sk_prot_free net/core/sock.c:2291 [inline]
> __sk_destruct+0x88c/0xab0 net/core/sock.c:2391
> sk_destruct+0xc8/0xf0 net/core/sock.c:2419
> __sk_free+0xf4/0x3e0 net/core/sock.c:2430
> sk_free+0x61/0x90 net/core/sock.c:2441
> sock_put include/net/sock.h:2020 [inline]
> bt_accept_unlink+0x22a/0x370 net/bluetooth/af_bluetooth.c:267
> bt_accept_dequeue+0x791/0x920 net/bluetooth/af_bluetooth.c:336
> l2cap_sock_cleanup_listen+0x47/0x4d0 net/bluetooth/l2cap_sock.c:1517
> l2cap_sock_release+0x69/0x280 net/bluetooth/l2cap_sock.c:1466
> __sock_release+0xb3/0x260 net/socket.c:710
> sock_close+0x1c/0x30 net/socket.c:1501
> __fput+0x3ff/0xb50 fs/file_table.c:512
> task_work_run+0x150/0x240 kernel/task_work.c:233
> get_signal+0x1bd/0x21e0 kernel/signal.c:2810
> arch_do_signal_or_restart+0x91/0x7e0 arch/x86/kernel/signal.c:337
> __exit_to_user_mode_loop kernel/entry/common.c:66 [inline]
> exit_to_user_mode_loop+0x139/0x6f0 kernel/entry/common.c:101
> __exit_to_user_mode_prepare include/linux/irq-entry-common.h:207 [inline]
> syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:230 [inline]
> syscall_exit_to_user_mode include/linux/entry-common.h:318 [inline]
> do_syscall_64+0x666/0x870 arch/x86/entry/syscall_64.c:100
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> The buggy address belongs to the object at ffff888036a17000
> which belongs to the cache kmalloc-2k of size 2048
> The buggy address is located 128 bytes inside of
> freed 2048-byte region [ffff888036a17000, ffff888036a17800)
>
> The buggy address belongs to the physical page:
> page: refcount:0 mapcount:0 mapping:0000000000000000 index:0xffff888036a16000 pfn:0x36a10
> head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
> flags: 0xfff00000000240(workingset|head|node=0|zone=1|lastcpupid=0x7ff)
> page_type: f5(slab)
> raw: 00fff00000000240 ffff88813fe3b000 ffffea000132c210 ffffea00009e2810
> raw: ffff888036a16000 0000000800080005 00000000f5000000 0000000000000000
> head: 00fff00000000240 ffff88813fe3b000 ffffea000132c210 ffffea00009e2810
> head: ffff888036a16000 0000000800080005 00000000f5000000 0000000000000000
> head: 00fff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
> head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
> page dumped because: kasan: bad access detected
> page_owner tracks the page as allocated
> page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 10, tgid 10 (kworker/0:1), ts 48507424327, free_ts 27518573283
> set_page_owner include/linux/page_owner.h:32 [inline]
> post_alloc_hook+0xfd/0x120 mm/page_alloc.c:1853
> prep_new_page mm/page_alloc.c:1861 [inline]
> get_page_from_freelist+0x812/0x3d20 mm/page_alloc.c:3941
> __alloc_frozen_pages_noprof+0x27c/0x2b60 mm/page_alloc.c:5221
> alloc_slab_page mm/slub.c:3289 [inline]
> allocate_slab mm/slub.c:3404 [inline]
> new_slab+0xa2/0x670 mm/slub.c:3447
> refill_objects+0xe3/0x430 mm/slub.c:7241
> refill_sheaf mm/slub.c:2827 [inline]
> __pcs_replace_empty_main+0x375/0x660 mm/slub.c:4692
> alloc_from_pcs mm/slub.c:4790 [inline]
> slab_alloc_node mm/slub.c:4924 [inline]
> __do_kmalloc_node mm/slub.c:5333 [inline]
> __kmalloc_node_track_caller_noprof+0x6b5/0x890 mm/slub.c:5438
> kmalloc_reserve+0xe8/0x350 net/core/skbuff.c:637
> __alloc_skb+0x185/0x710 net/core/skbuff.c:715
> alloc_skb include/linux/skbuff.h:1386 [inline]
> mld_newpack.isra.0+0x18e/0xa20 net/ipv6/mcast.c:1773
> add_grhead+0x299/0x340 net/ipv6/mcast.c:1884
> add_grec+0x1389/0x1930 net/ipv6/mcast.c:2023
> mld_send_cr net/ipv6/mcast.c:2148 [inline]
> mld_ifc_work+0x3c5/0xc10 net/ipv6/mcast.c:2694
> process_one_work+0xa23/0x1940 kernel/workqueue.c:3322
> process_scheduled_works kernel/workqueue.c:3405 [inline]
> worker_thread+0x5ef/0xe50 kernel/workqueue.c:3486
> kthread+0x370/0x450 kernel/kthread.c:436
> page last free pid 5380 tgid 5380 stack trace:
> reset_page_owner include/linux/page_owner.h:25 [inline]
> __free_pages_prepare mm/page_alloc.c:1397 [inline]
> __free_frozen_pages+0x794/0x10a0 mm/page_alloc.c:2938
> qlink_free mm/kasan/quarantine.c:163 [inline]
> qlist_free_all+0x47/0xf0 mm/kasan/quarantine.c:179
> kasan_quarantine_reduce+0x1a0/0x1f0 mm/kasan/quarantine.c:286
> __kasan_slab_alloc+0x69/0x90 mm/kasan/common.c:350
> kasan_slab_alloc include/linux/kasan.h:253 [inline]
> slab_post_alloc_hook mm/slub.c:4610 [inline]
> slab_alloc_node mm/slub.c:4939 [inline]
> kmem_cache_alloc_noprof+0x241/0x6d0 mm/slub.c:4946
> vm_area_alloc+0x1f/0x160 mm/vma_init.c:32
> __mmap_new_vma mm/vma.c:2547 [inline]
> __mmap_region+0x104d/0x2dd0 mm/vma.c:2771
> mmap_region+0x35d/0x620 mm/vma.c:2857
> do_mmap+0xc63/0x12f0 mm/mmap.c:560
> vm_mmap_pgoff+0x29e/0x470 mm/util.c:581
> ksys_mmap_pgoff+0xe4/0x610 mm/mmap.c:606
> __do_sys_mmap arch/x86/kernel/sys_x86_64.c:89 [inline]
> __se_sys_mmap arch/x86/kernel/sys_x86_64.c:82 [inline]
> __x64_sys_mmap+0x125/0x190 arch/x86/kernel/sys_x86_64.c:82
> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
> do_syscall_64+0x115/0x870 arch/x86/entry/syscall_64.c:94
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> Memory state around the buggy address:
> ffff888036a16f80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
> ffff888036a17000: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
> >ffff888036a17080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
> ^
> ffff888036a17100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
> ffff888036a17180: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
> ==================================================================
^ permalink raw reply
* [PATCH] Bluetooth: btrtl: set error code when RTL_SEC_PROJ read fails
From: Yinhao Hu @ 2026-06-20 10:56 UTC (permalink / raw)
To: marcel
Cc: luiz.dentz, linux-bluetooth, dzm91, hust-os-kernel-patches,
Yinhao Hu
btrtl_initialize() returns ERR_PTR(ret) at the err_free label, so every
path that jumps there must leave a negative error code in ret.
The RTL_SEC_PROJ register read stored its result in a separate variable
rc and jumped to err_free on failure without updating ret. At that point
ret is still 0 from the previous successful read, so btrtl_initialize()
returns ERR_PTR(0), i.e. NULL. btrtl_setup_realtek() only checks
IS_ERR(), then passes the NULL pointer to btrtl_download_firmware(),
which dereferences it:
Oops: general protection fault
RIP: btrtl_download_firmware+0x39
btrtl_setup_realtek
btusb_setup_realtek
hci_dev_open_sync
Read the register into ret directly and drop the now-redundant rc so the
failure propagates as a negative error pointer.
Fixes: cd8dbd9ef600 ("Bluetooth: btrtl: Avoid loading the config file on security chips")
Signed-off-by: Yinhao Hu <dddddd@hust.edu.cn>
---
drivers/bluetooth/btrtl.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/drivers/bluetooth/btrtl.c b/drivers/bluetooth/btrtl.c
index 62f9d4df3a4f..eb6fdf8592c2 100644
--- a/drivers/bluetooth/btrtl.c
+++ b/drivers/bluetooth/btrtl.c
@@ -1073,7 +1073,6 @@ struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev,
u16 hci_rev, lmp_subver;
u8 hci_ver, lmp_ver, chip_type = 0;
int ret;
- int rc;
u8 key_id;
u8 reg_val[2];
@@ -1185,8 +1184,8 @@ struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev,
goto err_free;
}
- rc = btrtl_vendor_read_reg16(hdev, RTL_SEC_PROJ, reg_val);
- if (rc < 0)
+ ret = btrtl_vendor_read_reg16(hdev, RTL_SEC_PROJ, reg_val);
+ if (ret < 0)
goto err_free;
key_id = reg_val[0];
--
2.43.0
^ permalink raw reply related
* [syzbot] [bluetooth?] KASAN: slab-use-after-free Write in bt_accept_dequeue
From: syzbot @ 2026-06-20 0:40 UTC (permalink / raw)
To: linux-bluetooth, linux-kernel, luiz.dentz, marcel, syzkaller-bugs
Hello,
syzbot found the following issue on:
HEAD commit: 8c13415c8a43 Merge tag 'media/v7.2-1' of git://git.kernel...
git tree: upstream
console output: https://syzkaller.appspot.com/x/log.txt?x=12f94d56580000
kernel config: https://syzkaller.appspot.com/x/.config?x=6a86d80fdec25236
dashboard link: https://syzkaller.appspot.com/bug?extid=674ff7e4d7fdfd572afc
compiler: gcc (Debian 14.2.0-19) 14.2.0, GNU ld (GNU Binutils for Debian) 2.44
Unfortunately, I don't have any reproducer for this issue yet.
Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/3ecc105c1b32/disk-8c13415c.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/cda6390db722/vmlinux-8c13415c.xz
kernel image: https://storage.googleapis.com/syzbot-assets/5f3d326d7d36/bzImage-8c13415c.xz
IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+674ff7e4d7fdfd572afc@syzkaller.appspotmail.com
==================================================================
BUG: KASAN: slab-use-after-free in instrument_atomic_read_write include/linux/instrumented.h:112 [inline]
BUG: KASAN: slab-use-after-free in atomic_fetch_add_relaxed include/linux/atomic/atomic-instrumented.h:252 [inline]
BUG: KASAN: slab-use-after-free in __refcount_add include/linux/refcount.h:283 [inline]
BUG: KASAN: slab-use-after-free in __refcount_inc include/linux/refcount.h:366 [inline]
BUG: KASAN: slab-use-after-free in refcount_inc include/linux/refcount.h:383 [inline]
BUG: KASAN: slab-use-after-free in sock_hold include/net/sock.h:839 [inline]
BUG: KASAN: slab-use-after-free in bt_accept_dequeue+0x6bd/0x920 net/bluetooth/af_bluetooth.c:348
Write of size 4 at addr ffff888036a17080 by task syz.2.3018/19569
CPU: 0 UID: 0 PID: 19569 Comm: syz.2.3018 Tainted: G L syzkaller #0 PREEMPT(full)
Tainted: [L]=SOFTLOCKUP
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0x100/0x190 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:378 [inline]
print_report+0x13d/0x4b0 mm/kasan/report.c:482
kasan_report+0xdf/0x1c0 mm/kasan/report.c:595
check_region_inline mm/kasan/generic.c:186 [inline]
kasan_check_range+0x10f/0x1e0 mm/kasan/generic.c:200
instrument_atomic_read_write include/linux/instrumented.h:112 [inline]
atomic_fetch_add_relaxed include/linux/atomic/atomic-instrumented.h:252 [inline]
__refcount_add include/linux/refcount.h:283 [inline]
__refcount_inc include/linux/refcount.h:366 [inline]
refcount_inc include/linux/refcount.h:383 [inline]
sock_hold include/net/sock.h:839 [inline]
bt_accept_dequeue+0x6bd/0x920 net/bluetooth/af_bluetooth.c:348
l2cap_sock_cleanup_listen+0x47/0x4d0 net/bluetooth/l2cap_sock.c:1517
l2cap_sock_release+0x69/0x280 net/bluetooth/l2cap_sock.c:1466
__sock_release+0xb3/0x260 net/socket.c:710
sock_close+0x1c/0x30 net/socket.c:1501
__fput+0x3ff/0xb50 fs/file_table.c:512
task_work_run+0x150/0x240 kernel/task_work.c:233
get_signal+0x1bd/0x21e0 kernel/signal.c:2810
arch_do_signal_or_restart+0x91/0x7e0 arch/x86/kernel/signal.c:337
__exit_to_user_mode_loop kernel/entry/common.c:66 [inline]
exit_to_user_mode_loop+0x139/0x6f0 kernel/entry/common.c:101
__exit_to_user_mode_prepare include/linux/irq-entry-common.h:207 [inline]
syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:230 [inline]
syscall_exit_to_user_mode include/linux/entry-common.h:318 [inline]
do_syscall_64+0x666/0x870 arch/x86/entry/syscall_64.c:100
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fc6feb9ce59
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fc6ff9a4028 EFLAGS: 00000246 ORIG_RAX: 0000000000000120
RAX: fffffffffffffe00 RBX: 00007fc6fee15fa0 RCX: 00007fc6feb9ce59
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000005
RBP: 00007fc6fec32e6f R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000800 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fc6fee16038 R14: 00007fc6fee15fa0 R15: 00007ffdf85f63a8
</TASK>
Allocated by task 19587:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__do_kmalloc_node mm/slub.c:5334 [inline]
__kmalloc_noprof+0x302/0x840 mm/slub.c:5347
_kmalloc_noprof include/linux/slab.h:973 [inline]
sk_prot_alloc+0x10b/0x2a0 net/core/sock.c:2252
sk_alloc+0x36/0xe80 net/core/sock.c:2308
bt_sock_alloc+0x3b/0x3c0 net/bluetooth/af_bluetooth.c:148
l2cap_sock_alloc.constprop.0+0x33/0x1e0 net/bluetooth/l2cap_sock.c:1983
l2cap_sock_new_connection_cb+0x10f/0x260 net/bluetooth/l2cap_sock.c:1562
l2cap_connect_cfm+0x4e2/0xf80 net/bluetooth/l2cap_core.c:7481
hci_connect_cfm include/net/bluetooth/hci_core.h:2136 [inline]
hci_remote_features_evt+0x4f4/0x9b0 net/bluetooth/hci_event.c:3764
hci_event_func net/bluetooth/hci_event.c:7800 [inline]
hci_event_packet+0x8e9/0xcd0 net/bluetooth/hci_event.c:7851
hci_rx_work+0x451/0xfc0 net/bluetooth/hci_core.c:4039
process_one_work+0xa23/0x1940 kernel/workqueue.c:3322
process_scheduled_works kernel/workqueue.c:3405 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3486
kthread+0x370/0x450 kernel/kthread.c:436
ret_from_fork+0x72b/0xd50 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Freed by task 19569:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
kasan_save_free_info+0x3b/0x70 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5f/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2700 [inline]
slab_free mm/slub.c:6310 [inline]
kfree+0x22b/0x6c0 mm/slub.c:6625
sk_prot_free net/core/sock.c:2291 [inline]
__sk_destruct+0x88c/0xab0 net/core/sock.c:2391
sk_destruct+0xc8/0xf0 net/core/sock.c:2419
__sk_free+0xf4/0x3e0 net/core/sock.c:2430
sk_free+0x61/0x90 net/core/sock.c:2441
sock_put include/net/sock.h:2020 [inline]
bt_accept_unlink+0x22a/0x370 net/bluetooth/af_bluetooth.c:267
bt_accept_dequeue+0x791/0x920 net/bluetooth/af_bluetooth.c:336
l2cap_sock_cleanup_listen+0x47/0x4d0 net/bluetooth/l2cap_sock.c:1517
l2cap_sock_release+0x69/0x280 net/bluetooth/l2cap_sock.c:1466
__sock_release+0xb3/0x260 net/socket.c:710
sock_close+0x1c/0x30 net/socket.c:1501
__fput+0x3ff/0xb50 fs/file_table.c:512
task_work_run+0x150/0x240 kernel/task_work.c:233
get_signal+0x1bd/0x21e0 kernel/signal.c:2810
arch_do_signal_or_restart+0x91/0x7e0 arch/x86/kernel/signal.c:337
__exit_to_user_mode_loop kernel/entry/common.c:66 [inline]
exit_to_user_mode_loop+0x139/0x6f0 kernel/entry/common.c:101
__exit_to_user_mode_prepare include/linux/irq-entry-common.h:207 [inline]
syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:230 [inline]
syscall_exit_to_user_mode include/linux/entry-common.h:318 [inline]
do_syscall_64+0x666/0x870 arch/x86/entry/syscall_64.c:100
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff888036a17000
which belongs to the cache kmalloc-2k of size 2048
The buggy address is located 128 bytes inside of
freed 2048-byte region [ffff888036a17000, ffff888036a17800)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0xffff888036a16000 pfn:0x36a10
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0xfff00000000240(workingset|head|node=0|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 00fff00000000240 ffff88813fe3b000 ffffea000132c210 ffffea00009e2810
raw: ffff888036a16000 0000000800080005 00000000f5000000 0000000000000000
head: 00fff00000000240 ffff88813fe3b000 ffffea000132c210 ffffea00009e2810
head: ffff888036a16000 0000000800080005 00000000f5000000 0000000000000000
head: 00fff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 10, tgid 10 (kworker/0:1), ts 48507424327, free_ts 27518573283
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0xfd/0x120 mm/page_alloc.c:1853
prep_new_page mm/page_alloc.c:1861 [inline]
get_page_from_freelist+0x812/0x3d20 mm/page_alloc.c:3941
__alloc_frozen_pages_noprof+0x27c/0x2b60 mm/page_alloc.c:5221
alloc_slab_page mm/slub.c:3289 [inline]
allocate_slab mm/slub.c:3404 [inline]
new_slab+0xa2/0x670 mm/slub.c:3447
refill_objects+0xe3/0x430 mm/slub.c:7241
refill_sheaf mm/slub.c:2827 [inline]
__pcs_replace_empty_main+0x375/0x660 mm/slub.c:4692
alloc_from_pcs mm/slub.c:4790 [inline]
slab_alloc_node mm/slub.c:4924 [inline]
__do_kmalloc_node mm/slub.c:5333 [inline]
__kmalloc_node_track_caller_noprof+0x6b5/0x890 mm/slub.c:5438
kmalloc_reserve+0xe8/0x350 net/core/skbuff.c:637
__alloc_skb+0x185/0x710 net/core/skbuff.c:715
alloc_skb include/linux/skbuff.h:1386 [inline]
mld_newpack.isra.0+0x18e/0xa20 net/ipv6/mcast.c:1773
add_grhead+0x299/0x340 net/ipv6/mcast.c:1884
add_grec+0x1389/0x1930 net/ipv6/mcast.c:2023
mld_send_cr net/ipv6/mcast.c:2148 [inline]
mld_ifc_work+0x3c5/0xc10 net/ipv6/mcast.c:2694
process_one_work+0xa23/0x1940 kernel/workqueue.c:3322
process_scheduled_works kernel/workqueue.c:3405 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3486
kthread+0x370/0x450 kernel/kthread.c:436
page last free pid 5380 tgid 5380 stack trace:
reset_page_owner include/linux/page_owner.h:25 [inline]
__free_pages_prepare mm/page_alloc.c:1397 [inline]
__free_frozen_pages+0x794/0x10a0 mm/page_alloc.c:2938
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x47/0xf0 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x1a0/0x1f0 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x69/0x90 mm/kasan/common.c:350
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4610 [inline]
slab_alloc_node mm/slub.c:4939 [inline]
kmem_cache_alloc_noprof+0x241/0x6d0 mm/slub.c:4946
vm_area_alloc+0x1f/0x160 mm/vma_init.c:32
__mmap_new_vma mm/vma.c:2547 [inline]
__mmap_region+0x104d/0x2dd0 mm/vma.c:2771
mmap_region+0x35d/0x620 mm/vma.c:2857
do_mmap+0xc63/0x12f0 mm/mmap.c:560
vm_mmap_pgoff+0x29e/0x470 mm/util.c:581
ksys_mmap_pgoff+0xe4/0x610 mm/mmap.c:606
__do_sys_mmap arch/x86/kernel/sys_x86_64.c:89 [inline]
__se_sys_mmap arch/x86/kernel/sys_x86_64.c:82 [inline]
__x64_sys_mmap+0x125/0x190 arch/x86/kernel/sys_x86_64.c:82
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x115/0x870 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Memory state around the buggy address:
ffff888036a16f80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ffff888036a17000: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff888036a17080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff888036a17100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff888036a17180: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.
syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.
If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title
If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)
If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report
If you want to undo deduplication, reply with:
#syz undup
^ permalink raw reply
* [bluez/bluez]
From: BluezTestBot @ 2026-06-19 18:22 UTC (permalink / raw)
To: linux-bluetooth
Branch: refs/heads/1098168
Home: https://github.com/bluez/bluez
To unsubscribe from these emails, change your notification settings at https://github.com/bluez/bluez/settings/notifications
^ permalink raw reply
* [bluez/bluez]
From: BluezTestBot @ 2026-06-19 16:45 UTC (permalink / raw)
To: linux-bluetooth
Branch: refs/heads/1113283
Home: https://github.com/bluez/bluez
To unsubscribe from these emails, change your notification settings at https://github.com/bluez/bluez/settings/notifications
^ permalink raw reply
* [bluez/bluez] 9305ae: shared/rap: Fix Mode 0 step serialization
From: prathibhamadugonde @ 2026-06-19 16:45 UTC (permalink / raw)
To: linux-bluetooth
Branch: refs/heads/master
Home: https://github.com/bluez/bluez
Commit: 9305ae767ac401498c0ed8b1a3abf1484d44f0ff
https://github.com/bluez/bluez/commit/9305ae767ac401498c0ed8b1a3abf1484d44f0ff
Author: Prathibha Madugonde <prathibha.madugonde@oss.qualcomm.com>
Date: 2026-06-19 (Fri, 19 Jun 2026)
Changed paths:
M src/shared/rap.c
Log Message:
-----------
shared/rap: Fix Mode 0 step serialization
Replace raw struct byte dump with field-by-field serialization
that conditionally includes init_measured_freq_offset only for
the Initiator role, matching the RAS wire format (5 bytes for
Initiator, 3 bytes for Reflector)
To unsubscribe from these emails, change your notification settings at https://github.com/bluez/bluez/settings/notifications
^ permalink raw reply
* Re: [PATCH BlueZ v1] shared/rap: Fix Mode 0 step serialization
From: patchwork-bot+bluetooth @ 2026-06-19 15:50 UTC (permalink / raw)
To: Prathibha Madugonde
Cc: linux-bluetooth, luiz.dentz, quic_mohamull, quic_hbandi,
quic_anubhavg
In-Reply-To: <20260618075529.98419-1-prathm@qti.qualcomm.com>
Hello:
This patch was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Thu, 18 Jun 2026 13:25:29 +0530 you wrote:
> From: Prathibha Madugonde <prathibha.madugonde@oss.qualcomm.com>
>
> Replace raw struct byte dump with field-by-field serialization
> that conditionally includes init_measured_freq_offset only for
> the Initiator role, matching the RAS wire format (5 bytes for
> Initiator, 3 bytes for Reflector)
>
> [...]
Here is the summary with links:
- [BlueZ,v1] shared/rap: Fix Mode 0 step serialization
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=9305ae767ac4
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* Re: [PATCH] Bluetooth: btmtksdio: fix infinite loop in btmtksdio_txrx_work()
From: Tomasz Figa @ 2026-06-19 14:53 UTC (permalink / raw)
To: Takashi Iwai
Cc: Sean Wang, Sergey Senozhatsky, Marcel Holtmann,
Luiz Augusto von Dentz, Mark-yw Chen, Sean Wang, linux-bluetooth,
linux-kernel, linux-arm-kernel, linux-mediatek, stable
In-Reply-To: <87jyruiomq.wl-tiwai@suse.de>
On Fri, Jun 19, 2026 at 11:36 PM Takashi Iwai <tiwai@suse.de> wrote:
>
> On Fri, 19 Jun 2026 16:17:31 +0200,
> Tomasz Figa wrote:
> >
> >
> > On Fri, Jun 19, 2026 at 10:27 PM Takashi Iwai <tiwai@suse.de> wrote:
> > >
> > > On Wed, 10 Jun 2026 08:52:31 +0200,
> > > Sean Wang wrote:
> > > >
> > > > Hi,
> > > >
> > > > On Tue, Jun 9, 2026 at 7:19 AM Sergey Senozhatsky
> > > > <senozhatsky@chromium.org> wrote:
> > > > >
> > > > > Every once in a while we see a hung btmtksdio_flush() task:
> > > > >
> > > > > INFO: task kworker/u17:0:189 blocked for more than 122 seconds.
> > > > > __cancel_work_timer+0x3f4/0x460
> > > > > cancel_work_sync+0x1c/0x2c
> > > > > btmtksdio_flush+0x2c/0x40
> > > > > hci_dev_open_sync+0x10c4/0x2190
> > > > > [..]
> > > > >
> > > > > It all boils down to incorrect time_is_before_jiffies() usage in
> > > > > btmtksdio_txrx_work(). The btmtksdio_txrx_work() loop is expected
> > > > > to be terminated if running for longer than 5*HZ. However the
> > > > > timeout check is twisted: time_is_before_jiffies(old_jiffies + 5*HZ)
> > > > > evaluates to true when old_jiffies + 5*HZ is in the past i.e. when a
> > > > > timeout has occurred. Using OR with time_is_before_jiffies
> > (txrx_timeout)
> > > > > means that:
> > > > > - before the 5-second timeout: the condition is `int_status || false`,
> > > > > so it loops as long as there are pending interrupts.
> > > > > - after the 5-second timeout: the condition becomes `int_status || true
> > `,
> > > > > which is always true.
> > > > >
> > > > > When the loop becomes infinite btmtksdio_txrx_work() loop never
> > > > > terminates and never releases the SDIO host.
> > > > >
> > > > > Fix loop termination condition to actually enforce a 5*HZ timeout.
> > > > >
> > > > > Fixes: 26270bc189ea4 ("Bluetooth: btmtksdio: move interrupt service to
> > work")
> > > > > Cc: stable@vger.kernel.org
> > > > > Signed-off-by: Sergey Senozhatsky <senozhatsky@chromium.org>
> > > > > ---
> > > > > drivers/bluetooth/btmtksdio.c | 2 +-
> > > > > 1 file changed, 1 insertion(+), 1 deletion(-)
> > > > >
> > > > > diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/
> > btmtksdio.c
> > > > > index 5b0fab7b89b5..c6f80c419e90 100644
> > > > > --- a/drivers/bluetooth/btmtksdio.c
> > > > > +++ b/drivers/bluetooth/btmtksdio.c
> > > > > @@ -620,7 +620,7 @@ static void btmtksdio_txrx_work(struct work_struct
> > *work)
> > > > > if (btmtksdio_rx_packet(bdev, rx_size) < 0)
> > > > > bdev->hdev->stat.err_rx++;
> > > > > }
> > > > > - } while (int_status || time_is_before_jiffies(txrx_timeout));
> > > > > + } while (int_status && time_is_after_jiffies(txrx_timeout));
> > > >
> > > > yes, loop continues only while there is interrupt work and the timeout
> > > > deadline is still in the future
> > >
> > > I stumbled on this while backporting to distro kernels, and I wonder
> > > whether this change is correct.
> > >
> > > IIUC, this essentially makes the loop exiting right after the first
> > > cycle; the patch changed from time_is_before_jiffies() to *_after_*(),
> > > not only the logical OR to AND, and *_after_*() returns false, so the
> > > whole condition becomes false, too.
> >
> > The intention is for the loop to keep running as long as there is still an
> > interrupt left to handle (int_status != 0) and the timeout has not elapsed
> > (jiffies < txrx_timeout).
> >
> > Note that time_is_after_jiffies(x) returns true if x > jiffies (or jiffies <
> > x):
> >
> > /**
> > * time_is_after_jiffies - return true if a is after jiffies
> > * @a: time (unsigned long) to compare to jiffies
> > *
> > * Return: %true is time a is after jiffies, otherwise %false.
> > */
> > #define time_is_after_jiffies(a) time_before(jiffies, a)
> >
> > Or am I missing something?
>
> Doh, scratch my comment. It's enough confusing about time_after() vs
> time_is_after_jiffies(). Too hot here to review something today :-<
>
> Sorry for the noise!
Haha, no worries, it got me too! (In our internal discussion with
Sergey) I had to look up the definition and think about it for quite a
while to ensure it was really what we needed. ;)
Best,
Tomasz
^ permalink raw reply
* Re: [PATCH] Bluetooth: btmtksdio: fix infinite loop in btmtksdio_txrx_work()
From: Takashi Iwai @ 2026-06-19 14:35 UTC (permalink / raw)
To: Tomasz Figa
Cc: Takashi Iwai, Sean Wang, Sergey Senozhatsky, Marcel Holtmann,
Luiz Augusto von Dentz, Mark-yw Chen, Sean Wang, linux-bluetooth,
linux-kernel, linux-arm-kernel, linux-mediatek, stable
In-Reply-To: <CAAFQd5DyDQo9vBH80YYQBW7Bgf64F1m9q44-jhf1cc75XYpftA@mail.gmail.com>
On Fri, 19 Jun 2026 16:17:31 +0200,
Tomasz Figa wrote:
>
>
> On Fri, Jun 19, 2026 at 10:27 PM Takashi Iwai <tiwai@suse.de> wrote:
> >
> > On Wed, 10 Jun 2026 08:52:31 +0200,
> > Sean Wang wrote:
> > >
> > > Hi,
> > >
> > > On Tue, Jun 9, 2026 at 7:19 AM Sergey Senozhatsky
> > > <senozhatsky@chromium.org> wrote:
> > > >
> > > > Every once in a while we see a hung btmtksdio_flush() task:
> > > >
> > > > INFO: task kworker/u17:0:189 blocked for more than 122 seconds.
> > > > __cancel_work_timer+0x3f4/0x460
> > > > cancel_work_sync+0x1c/0x2c
> > > > btmtksdio_flush+0x2c/0x40
> > > > hci_dev_open_sync+0x10c4/0x2190
> > > > [..]
> > > >
> > > > It all boils down to incorrect time_is_before_jiffies() usage in
> > > > btmtksdio_txrx_work(). The btmtksdio_txrx_work() loop is expected
> > > > to be terminated if running for longer than 5*HZ. However the
> > > > timeout check is twisted: time_is_before_jiffies(old_jiffies + 5*HZ)
> > > > evaluates to true when old_jiffies + 5*HZ is in the past i.e. when a
> > > > timeout has occurred. Using OR with time_is_before_jiffies
> (txrx_timeout)
> > > > means that:
> > > > - before the 5-second timeout: the condition is `int_status || false`,
> > > > so it loops as long as there are pending interrupts.
> > > > - after the 5-second timeout: the condition becomes `int_status || true
> `,
> > > > which is always true.
> > > >
> > > > When the loop becomes infinite btmtksdio_txrx_work() loop never
> > > > terminates and never releases the SDIO host.
> > > >
> > > > Fix loop termination condition to actually enforce a 5*HZ timeout.
> > > >
> > > > Fixes: 26270bc189ea4 ("Bluetooth: btmtksdio: move interrupt service to
> work")
> > > > Cc: stable@vger.kernel.org
> > > > Signed-off-by: Sergey Senozhatsky <senozhatsky@chromium.org>
> > > > ---
> > > > drivers/bluetooth/btmtksdio.c | 2 +-
> > > > 1 file changed, 1 insertion(+), 1 deletion(-)
> > > >
> > > > diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/
> btmtksdio.c
> > > > index 5b0fab7b89b5..c6f80c419e90 100644
> > > > --- a/drivers/bluetooth/btmtksdio.c
> > > > +++ b/drivers/bluetooth/btmtksdio.c
> > > > @@ -620,7 +620,7 @@ static void btmtksdio_txrx_work(struct work_struct
> *work)
> > > > if (btmtksdio_rx_packet(bdev, rx_size) < 0)
> > > > bdev->hdev->stat.err_rx++;
> > > > }
> > > > - } while (int_status || time_is_before_jiffies(txrx_timeout));
> > > > + } while (int_status && time_is_after_jiffies(txrx_timeout));
> > >
> > > yes, loop continues only while there is interrupt work and the timeout
> > > deadline is still in the future
> >
> > I stumbled on this while backporting to distro kernels, and I wonder
> > whether this change is correct.
> >
> > IIUC, this essentially makes the loop exiting right after the first
> > cycle; the patch changed from time_is_before_jiffies() to *_after_*(),
> > not only the logical OR to AND, and *_after_*() returns false, so the
> > whole condition becomes false, too.
>
> The intention is for the loop to keep running as long as there is still an
> interrupt left to handle (int_status != 0) and the timeout has not elapsed
> (jiffies < txrx_timeout).
>
> Note that time_is_after_jiffies(x) returns true if x > jiffies (or jiffies <
> x):
>
> /**
> * time_is_after_jiffies - return true if a is after jiffies
> * @a: time (unsigned long) to compare to jiffies
> *
> * Return: %true is time a is after jiffies, otherwise %false.
> */
> #define time_is_after_jiffies(a) time_before(jiffies, a)
>
> Or am I missing something?
Doh, scratch my comment. It's enough confusing about time_after() vs
time_is_after_jiffies(). Too hot here to review something today :-<
Sorry for the noise!
Takashi
^ permalink raw reply
* Re: [PATCH] Bluetooth: btmtksdio: fix infinite loop in btmtksdio_txrx_work()
From: Tomasz Figa @ 2026-06-19 14:20 UTC (permalink / raw)
To: Takashi Iwai
Cc: Sean Wang, Sergey Senozhatsky, Marcel Holtmann,
Luiz Augusto von Dentz, Mark-yw Chen, Sean Wang, linux-bluetooth,
linux-kernel, linux-arm-kernel, linux-mediatek, stable
In-Reply-To: <87tsqyirsn.wl-tiwai@suse.de>
[Resending without HTML... Damn Gmail]
On Fri, Jun 19, 2026 at 10:27 PM Takashi Iwai <tiwai@suse.de> wrote:
>
> On Wed, 10 Jun 2026 08:52:31 +0200,
> Sean Wang wrote:
> >
> > Hi,
> >
> > On Tue, Jun 9, 2026 at 7:19 AM Sergey Senozhatsky
> > <senozhatsky@chromium.org> wrote:
> > >
> > > Every once in a while we see a hung btmtksdio_flush() task:
> > >
> > > INFO: task kworker/u17:0:189 blocked for more than 122 seconds.
> > > __cancel_work_timer+0x3f4/0x460
> > > cancel_work_sync+0x1c/0x2c
> > > btmtksdio_flush+0x2c/0x40
> > > hci_dev_open_sync+0x10c4/0x2190
> > > [..]
> > >
> > > It all boils down to incorrect time_is_before_jiffies() usage in
> > > btmtksdio_txrx_work(). The btmtksdio_txrx_work() loop is expected
> > > to be terminated if running for longer than 5*HZ. However the
> > > timeout check is twisted: time_is_before_jiffies(old_jiffies + 5*HZ)
> > > evaluates to true when old_jiffies + 5*HZ is in the past i.e. when a
> > > timeout has occurred. Using OR with time_is_before_jiffies(txrx_timeout)
> > > means that:
> > > - before the 5-second timeout: the condition is `int_status || false`,
> > > so it loops as long as there are pending interrupts.
> > > - after the 5-second timeout: the condition becomes `int_status || true`,
> > > which is always true.
> > >
> > > When the loop becomes infinite btmtksdio_txrx_work() loop never
> > > terminates and never releases the SDIO host.
> > >
> > > Fix loop termination condition to actually enforce a 5*HZ timeout.
> > >
> > > Fixes: 26270bc189ea4 ("Bluetooth: btmtksdio: move interrupt service to work")
> > > Cc: stable@vger.kernel.org
> > > Signed-off-by: Sergey Senozhatsky <senozhatsky@chromium.org>
> > > ---
> > > drivers/bluetooth/btmtksdio.c | 2 +-
> > > 1 file changed, 1 insertion(+), 1 deletion(-)
> > >
> > > diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c
> > > index 5b0fab7b89b5..c6f80c419e90 100644
> > > --- a/drivers/bluetooth/btmtksdio.c
> > > +++ b/drivers/bluetooth/btmtksdio.c
> > > @@ -620,7 +620,7 @@ static void btmtksdio_txrx_work(struct work_struct *work)
> > > if (btmtksdio_rx_packet(bdev, rx_size) < 0)
> > > bdev->hdev->stat.err_rx++;
> > > }
> > > - } while (int_status || time_is_before_jiffies(txrx_timeout));
> > > + } while (int_status && time_is_after_jiffies(txrx_timeout));
> >
> > yes, loop continues only while there is interrupt work and the timeout
> > deadline is still in the future
>
> I stumbled on this while backporting to distro kernels, and I wonder
> whether this change is correct.
>
> IIUC, this essentially makes the loop exiting right after the first
> cycle; the patch changed from time_is_before_jiffies() to *_after_*(),
> not only the logical OR to AND, and *_after_*() returns false, so the
> whole condition becomes false, too.
The intention is for the loop to keep running as long as there is
still an interrupt left to handle (int_status != 0) and the timeout
has not elapsed (jiffies < txrx_timeout).
Note that time_is_after_jiffies(x) returns true if x > jiffies (or jiffies < x):
/**
* time_is_after_jiffies - return true if a is after jiffies
* @a: time (unsigned long) to compare to jiffies
*
* Return: %true is time a is after jiffies, otherwise %false.
*/
#define time_is_after_jiffies(a) time_before(jiffies, a)
Or am I missing something?
Best,
Tomasz
^ permalink raw reply
* Re: [PATCH] Bluetooth: btmtksdio: fix infinite loop in btmtksdio_txrx_work()
From: Takashi Iwai @ 2026-06-19 13:27 UTC (permalink / raw)
To: Sean Wang
Cc: Sergey Senozhatsky, Marcel Holtmann, Luiz Augusto von Dentz,
Mark-yw Chen, Sean Wang, Tomasz Figa, linux-bluetooth,
linux-kernel, linux-arm-kernel, linux-mediatek, stable
In-Reply-To: <CAGp9LzpBUReZtrTEKgUr-+yvB+3tcs5hw7ziC4WaMRFNa2AYpg@mail.gmail.com>
On Wed, 10 Jun 2026 08:52:31 +0200,
Sean Wang wrote:
>
> Hi,
>
> On Tue, Jun 9, 2026 at 7:19 AM Sergey Senozhatsky
> <senozhatsky@chromium.org> wrote:
> >
> > Every once in a while we see a hung btmtksdio_flush() task:
> >
> > INFO: task kworker/u17:0:189 blocked for more than 122 seconds.
> > __cancel_work_timer+0x3f4/0x460
> > cancel_work_sync+0x1c/0x2c
> > btmtksdio_flush+0x2c/0x40
> > hci_dev_open_sync+0x10c4/0x2190
> > [..]
> >
> > It all boils down to incorrect time_is_before_jiffies() usage in
> > btmtksdio_txrx_work(). The btmtksdio_txrx_work() loop is expected
> > to be terminated if running for longer than 5*HZ. However the
> > timeout check is twisted: time_is_before_jiffies(old_jiffies + 5*HZ)
> > evaluates to true when old_jiffies + 5*HZ is in the past i.e. when a
> > timeout has occurred. Using OR with time_is_before_jiffies(txrx_timeout)
> > means that:
> > - before the 5-second timeout: the condition is `int_status || false`,
> > so it loops as long as there are pending interrupts.
> > - after the 5-second timeout: the condition becomes `int_status || true`,
> > which is always true.
> >
> > When the loop becomes infinite btmtksdio_txrx_work() loop never
> > terminates and never releases the SDIO host.
> >
> > Fix loop termination condition to actually enforce a 5*HZ timeout.
> >
> > Fixes: 26270bc189ea4 ("Bluetooth: btmtksdio: move interrupt service to work")
> > Cc: stable@vger.kernel.org
> > Signed-off-by: Sergey Senozhatsky <senozhatsky@chromium.org>
> > ---
> > drivers/bluetooth/btmtksdio.c | 2 +-
> > 1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c
> > index 5b0fab7b89b5..c6f80c419e90 100644
> > --- a/drivers/bluetooth/btmtksdio.c
> > +++ b/drivers/bluetooth/btmtksdio.c
> > @@ -620,7 +620,7 @@ static void btmtksdio_txrx_work(struct work_struct *work)
> > if (btmtksdio_rx_packet(bdev, rx_size) < 0)
> > bdev->hdev->stat.err_rx++;
> > }
> > - } while (int_status || time_is_before_jiffies(txrx_timeout));
> > + } while (int_status && time_is_after_jiffies(txrx_timeout));
>
> yes, loop continues only while there is interrupt work and the timeout
> deadline is still in the future
I stumbled on this while backporting to distro kernels, and I wonder
whether this change is correct.
IIUC, this essentially makes the loop exiting right after the first
cycle; the patch changed from time_is_before_jiffies() to *_after_*(),
not only the logical OR to AND, and *_after_*() returns false, so the
whole condition becomes false, too.
thanks,
Takashi
^ permalink raw reply
* Re: [PATCH v2] Bluetooth: btusb: Add support for Quectel NCM865
From: Frank Tornack @ 2026-06-19 12:49 UTC (permalink / raw)
To: Pauli Virtanen; +Cc: linux-bluetooth
In-Reply-To: <9b4395d8a6fd8abafaa74a4fd7542a0a1d2ae40f.camel@t-online.de>
Hi all
is there any update on this topic or open questions?
Thank you very much
--
Frank Tornack <f-tornack@t-online.de>
Privat
-------- Ursprüngliche Nachricht --------
Von: Frank Tornack <f-tornack@t-online.de>
Antwort an: f-tornack@t-online.de
An: Pauli Virtanen <pav@iki.fi>
Kopie: linux-bluetooth@vger.kernel.org
Betreff: Re: [PATCH v2] Bluetooth: btusb: Add support for Quectel
NCM865
Datum: 25.12.2025 08:10:10
Hi Pauli
The reason for adding it to btusb_table is that without this entry, the
device (2c7c:0130) is being detected but defaults to a generic QCA
"Rome" configuration (0x190200), which lacks ISO/LE Audio support.
It is definitely loading the btusb driver, but without the explicit ID
match, the capabilities don't align with the actual WCN785x hardware.
Just a quick heads-up: I'm currently on vacation, so my response time
might be a bit slow. I'm mainly checking my mails and doing tests on
rainy days when I'm staying indoors.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox