* [PATCH BlueZ v2 0/3] Add HID gamepad quirk fallback for broken SDP records
@ 2026-07-21 14:17 Pakrohk
2026-07-21 14:17 ` [PATCH BlueZ v2 1/3] src: add HMAC-SHA256 to bt_crypto using kernel AF_ALG Pakrohk
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Pakrohk @ 2026-07-21 14:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: luiz.dentz, marcel, Pakrohk
From: Pakrohk <Pakrohk@users.noreply.github.com>
This patch series adds a modular quirk system that provides HID report
descriptor fallbacks for third-party Bluetooth gamepads with incomplete
or malformed SDP records.
Problem:
Third-party Bluetooth HID gamepads (notably TG170W / DualShock 4 v2
clones, VID:PID 054c:09cc) expose incomplete HID SDP records. BlueZ's
extract_hid_record() fails with -ENOENT, preventing HIDP registration.
The Bluetooth link is active but no /dev/input/event* device is created.
Windows and Android handle this gracefully by falling back to known HID
descriptors. BlueZ does not.
Solution:
A two-tier quirk system that activates only when SDP parsing fails for a
specifically matched device. Does not globally weaken HID parsing.
hidp_add_connection()
-> extract_hid_record() // normal SDP path
-> FAILS (-ENOENT)
-> gamepad_quirk_apply() // check built-in + external quirks
-> inject fallback HID descriptor
-> continue HIDP setup normally
Patch [1] adds HMAC-SHA256 to the internal bt_crypto library using the
kernel AF_ALG interface, following the existing cmac(aes) pattern. This
replaces the previous openssl CLI dependency.
Patch [2] adds the core quirk system: built-in quirk for TG170W/DS4 v2,
external quirk profile loader with HMAC signature verification, and the
device.c integration point.
Patch [3] adds bluez-quirkctl, a CLI tool for managing external quirk
profiles (validate, install, list, remove).
External quirk profiles are JSON files installed to
/var/lib/bluez/quirks/ with HMAC-SHA256 signatures, loaded at bluetoothd
startup. This enables community-maintained gamepad support without BlueZ
source modifications.
Testing:
- TG170W / DualShock 4 v2 (054c:09cc): buttons, sticks, touchpad
verified via evtest
- Kernel creates three input devices: Wireless Controller,
Wireless Controller Motion Sensors, Wireless Controller Touchpad
- External profile validation, installation, loading tested end-to-end
- Tampered profiles rejected by signature verification
---
Patch 1: src: add HMAC-SHA256 to bt_crypto using kernel AF_ALG
Patch 2: input: add HID gamepad quirk fallback for broken SDP records
Patch 3: tools: add bluez-quirkctl for managing quirk profiles
--
2.49.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH BlueZ v2 1/3] src: add HMAC-SHA256 to bt_crypto using kernel AF_ALG
2026-07-21 14:17 [PATCH BlueZ v2 0/3] Add HID gamepad quirk fallback for broken SDP records Pakrohk
@ 2026-07-21 14:17 ` Pakrohk
2026-07-21 14:28 ` Luiz Augusto von Dentz
2026-07-21 14:17 ` [PATCH BlueZ v2 2/3] input: add HID gamepad quirk fallback for broken SDP records Pakrohk
2026-07-21 14:17 ` [PATCH BlueZ v2 3/3] tools: add bluez-quirkctl for managing quirk profiles Pakrohk
2 siblings, 1 reply; 6+ messages in thread
From: Pakrohk @ 2026-07-21 14:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: luiz.dentz, marcel, Pakrohk
From: Pakrohk <Pakrohk@users.noreply.github.com>
Add HMAC-SHA256 computation to the bt_crypto library using the Linux
kernel AF_ALG interface. This follows the exact same pattern as the
existing cmac(aes) implementation.
The HMAC-SHA256 function is needed for verifying external quirk profile
signatures. The previous implementation used popen("openssl dgst ...")
which had command injection risks and an openssl CLI dependency.
Using kernel AF_ALG:
- No external library dependencies
- Matches existing BlueZ crypto patterns
- No shell invocation or temp files
- Works in bluetoothd's process context
The hmac(sha256) algorithm is available in all modern Linux kernels
via the crypto API (/proc/crypto).
Signed-off-by: Pakrohk <Pakrohk@users.noreply.github.com>
diff --git a/src/shared/crypto.c b/src/shared/crypto.c
index cb99116..f5883bd 100644
--- a/src/shared/crypto.c
+++ b/src/shared/crypto.c
@@ -69,6 +69,7 @@ struct bt_crypto {
int ecb_aes;
int urandom;
int cmac_aes;
+ int hmac_sha256;
};
static int urandom_setup(void)
@@ -126,6 +127,28 @@ static int cmac_aes_setup(void)
return fd;
}
+static int hmac_sha256_setup(void)
+{
+ struct sockaddr_alg salg;
+ int fd;
+
+ fd = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
+ if (fd < 0)
+ return -1;
+
+ memset(&salg, 0, sizeof(salg));
+ salg.salg_family = AF_ALG;
+ strcpy((char *) salg.salg_type, "hash");
+ strcpy((char *) salg.salg_name, "hmac(sha256)");
+
+ if (bind(fd, (struct sockaddr *) &salg, sizeof(salg)) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
static struct bt_crypto *singleton;
struct bt_crypto *bt_crypto_new(void)
@@ -159,6 +182,16 @@ struct bt_crypto *bt_crypto_new(void)
return NULL;
}
+ singleton->hmac_sha256 = hmac_sha256_setup();
+ if (singleton->hmac_sha256 < 0) {
+ close(singleton->cmac_aes);
+ close(singleton->urandom);
+ close(singleton->ecb_aes);
+ free(singleton);
+ singleton = NULL;
+ return NULL;
+ }
+
return bt_crypto_ref(singleton);
}
@@ -183,6 +216,7 @@ void bt_crypto_unref(struct bt_crypto *crypto)
close(crypto->urandom);
close(crypto->ecb_aes);
close(crypto->cmac_aes);
+ close(crypto->hmac_sha256);
free(crypto);
singleton = NULL;
@@ -1005,3 +1039,35 @@ bool bt_crypto_sirk(struct bt_crypto *crypto, const char *str, uint16_t vendor,
/* Encrypt sirk using k as LTK with sef function */
return bt_crypto_sef(crypto, k, sirk_plaintext, sirk);
}
+
+bool bt_crypto_hmac_sha256(struct bt_crypto *crypto,
+ const uint8_t *key, size_t key_len,
+ const uint8_t *msg, size_t msg_len,
+ uint8_t res[32])
+{
+ ssize_t len;
+ int fd;
+
+ if (!crypto)
+ return false;
+
+ fd = alg_new(crypto->hmac_sha256, key, key_len);
+ if (fd < 0)
+ return false;
+
+ len = send(fd, msg, msg_len, 0);
+ if (len < 0) {
+ close(fd);
+ return false;
+ }
+
+ len = read(fd, res, 32);
+ if (len < 0) {
+ close(fd);
+ return false;
+ }
+
+ close(fd);
+
+ return true;
+}
diff --git a/src/shared/crypto.h b/src/shared/crypto.h
index d85f807..6b234bc 100644
--- a/src/shared/crypto.h
+++ b/src/shared/crypto.h
@@ -62,3 +62,8 @@ bool bt_crypto_sirk(struct bt_crypto *crypto, const char *str, uint16_t vendor,
uint8_t sirk[16]);
bool bt_crypto_rsi(struct bt_crypto *crypto, const uint8_t sirk[16],
uint8_t rsi[6]);
+
+bool bt_crypto_hmac_sha256(struct bt_crypto *crypto,
+ const uint8_t *key, size_t key_len,
+ const uint8_t *msg, size_t msg_len,
+ uint8_t res[32]);
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH BlueZ v2 2/3] input: add HID gamepad quirk fallback for broken SDP records
2026-07-21 14:17 [PATCH BlueZ v2 0/3] Add HID gamepad quirk fallback for broken SDP records Pakrohk
2026-07-21 14:17 ` [PATCH BlueZ v2 1/3] src: add HMAC-SHA256 to bt_crypto using kernel AF_ALG Pakrohk
@ 2026-07-21 14:17 ` Pakrohk
2026-07-21 14:36 ` Luiz Augusto von Dentz
2026-07-21 14:17 ` [PATCH BlueZ v2 3/3] tools: add bluez-quirkctl for managing quirk profiles Pakrohk
2 siblings, 1 reply; 6+ messages in thread
From: Pakrohk @ 2026-07-21 14:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: luiz.dentz, marcel, Pakrohk
From: Pakrohk <Pakrohk@users.noreply.github.com>
Add a modular gamepad quirk system to profiles/input/ that provides HID
report descriptor fallbacks when BlueZ's SDP parser fails.
When extract_hid_record() returns -ENOENT for a known gamepad, the quirk
system checks registered quirks and injects a fallback HID descriptor so
the kernel's HID driver can create an input device.
Two tiers of quirk support:
Built-in quirks:
Hardcoded C entries for known broken controllers. Currently supports
DualShock 4 v2 / TG170W (054c:09cc) with a minimal BT HID descriptor
matching report ID 0x11 (78 bytes, as expected by hid-playstation).
External quirk profiles:
JSON files in /var/lib/bluez/quirks/ with HMAC-SHA256 signatures.
Loaded at bluetoothd startup. Enables community-maintained support
without BlueZ source modifications.
Architecture:
quirk.h - quirk struct definition and dispatch API
quirk.c - quirk registry and dispatch logic
quirk-profile.h/c - external JSON profile loader with HMAC verification
quirks/tg170w.c - built-in quirk for DualShock 4 v2
The quirk only activates when SDP parsing fails AND the device matches
a registered quirk. This does not globally weaken HID parsing.
Multi-factor matching avoids false positives:
match = (vendor_id == 0x054c && product_id == 0x09cc)
|| (device_name == "Wireless Controller"
&& SDP provider contains "Sony")
Signed-off-by: Pakrohk <Pakrohk@users.noreply.github.com>
diff --git a/profiles/input/device.c b/profiles/input/device.c
index 8017e07..0089c3d 100644
--- a/profiles/input/device.c
+++ b/profiles/input/device.c
@@ -1089,11 +1089,8 @@ static int hidp_add_connection(struct input_device *idev)
err = extract_hid_record(idev, req);
if (err < 0) {
- /* Try gamepad quirk fallback for known broken devices */
- if (gamepad_quirk_match(idev)) {
- DBG("HID SDP failed, trying gamepad quirk");
- err = gamepad_quirk_apply(idev, req);
- }
+ DBG("HID SDP failed, trying gamepad quirk");
+ err = gamepad_quirk_apply(idev, req);
}
if (err < 0) {
diff --git a/profiles/input/quirk-profile.c b/profiles/input/quirk-profile.c
index 8ec3835..4240154 100644
--- a/profiles/input/quirk-profile.c
+++ b/profiles/input/quirk-profile.c
@@ -28,22 +28,20 @@
#include "bluetooth/bluetooth.h"
#include "bluetooth/hidp.h"
+#include "bluetooth/sdp.h"
+#include "bluetooth/sdp_lib.h"
#include "src/log.h"
#include "quirk.h"
#include "quirk-profile.h"
-/* Accessors from src/device.h and src/service.h */
-extern struct btd_service *input_device_get_service(
- struct input_device *idev);
-extern struct btd_device *btd_service_get_device(
- const struct btd_service *service);
-extern uint16_t btd_device_get_vendor(struct btd_device *device);
-extern uint16_t btd_device_get_product(struct btd_device *device);
-extern bool device_name_known(struct btd_device *device);
-extern void device_get_name(struct btd_device *device,
- char *name, size_t len);
+#include "gdbus/gdbus.h"
+
+#include "src/device.h"
+#include "src/service.h"
+#include "src/shared/crypto.h"
+#include "device.h"
#define MAX_EXTERNAL_QUIRKS 32
#define HMAC_KEY_PATH QUIRK_PROFILE_DIR "/.hmac_key"
@@ -67,6 +65,7 @@ struct external_quirk {
static struct external_quirk *ext_quirks[MAX_EXTERNAL_QUIRKS + 1];
static int num_ext_quirks;
+static struct bt_crypto *crypto;
/*
* Parse a hex string like "0501 0905" into a byte buffer.
@@ -139,77 +138,23 @@ static char *read_file(const char *path, size_t *out_len)
}
/*
- * Compute HMAC-SHA256 using openssl CLI.
- */
-static char *hmac_sha256(const uint8_t *key, int key_len,
- const void *data, size_t data_len)
-{
- char key_hex[HMAC_KEY_SIZE * 2 + 1];
- char *cmd;
- char *result = NULL;
- FILE *p, *tmpf;
- int i;
- const char *tmpfile = "/tmp/.bluez_quirk_hmac_input";
- char line[256];
- char *eq, *end;
-
- for (i = 0; i < key_len; i++)
- snprintf(key_hex + i * 2, 3, "%02x", key[i]);
- key_hex[key_len * 2] = '\0';
-
- tmpf = fopen(tmpfile, "we");
- if (!tmpf)
- return NULL;
-
- fwrite(data, 1, data_len, tmpf);
- fclose(tmpf);
-
- cmd = malloc(strlen(key_hex) + 128);
- if (!cmd) {
- unlink(tmpfile);
- return NULL;
- }
-
- snprintf(cmd, strlen(key_hex) + 128,
- "openssl dgst -sha256 -hmac '%s' -hex < %s 2>/dev/null",
- key_hex, tmpfile);
-
- p = popen(cmd, "re");
- free(cmd);
- unlink(tmpfile);
-
- if (!p)
- return NULL;
-
- while (fgets(line, sizeof(line), p)) {
- eq = strstr(line, "= ");
- if (eq) {
- eq += 2;
- end = eq + strlen(eq) - 1;
- while (end > eq && (*end == '\n' || *end == '\r'
- || *end == ' '))
- *end-- = '\0';
- result = strdup(eq);
- break;
- }
- }
-
- pclose(p);
- return result;
-}
-
-/*
- * Verify HMAC signature.
+ * Verify HMAC-SHA256 signature using bt_crypto (kernel AF_ALG).
*/
static bool verify_signature(const char *json_path,
const uint8_t *hmac_key, int key_len)
{
char sig_path[1024];
size_t sig_len, json_len;
- char *sig_hex, *json_data, *expected_sig;
+ char *sig_hex, *json_data;
char *end;
+ uint8_t computed[32];
+ char computed_hex[65];
+ int i;
bool valid;
+ if (!crypto)
+ return false;
+
snprintf(sig_path, sizeof(sig_path), "%s" SIG_EXT, json_path);
sig_hex = read_file(sig_path, &sig_len);
@@ -228,21 +173,25 @@ static bool verify_signature(const char *json_path,
return false;
}
- expected_sig = hmac_sha256(hmac_key, key_len, json_data, json_len);
- free(json_data);
-
- if (!expected_sig) {
+ if (!bt_crypto_hmac_sha256(crypto, hmac_key, key_len,
+ (const uint8_t *) json_data, json_len,
+ computed)) {
+ free(json_data);
free(sig_hex);
return false;
}
+ free(json_data);
+
+ for (i = 0; i < 32; i++)
+ snprintf(computed_hex + i * 2, 3, "%02x", computed[i]);
+ computed_hex[64] = '\0';
- valid = (strcmp(sig_hex, expected_sig) == 0);
+ valid = (strcmp(sig_hex, computed_hex) == 0);
if (!valid)
DBG("Signature mismatch for %s", json_path);
free(sig_hex);
- free(expected_sig);
return valid;
}
@@ -516,6 +465,12 @@ int load_external_quirks(const char *dir)
if (!dir)
dir = QUIRK_PROFILE_DIR;
+ if (!crypto) {
+ crypto = bt_crypto_new();
+ if (!crypto)
+ DBG("quirk-profile: failed to init crypto");
+ }
+
d = opendir(dir);
if (!d) {
DBG("quirk-profile: cannot open %s: %s", dir, strerror(errno));
@@ -586,4 +541,9 @@ void free_external_quirks(void)
ext_quirks[i] = NULL;
}
num_ext_quirks = 0;
+
+ if (crypto) {
+ bt_crypto_unref(crypto);
+ crypto = NULL;
+ }
}
diff --git a/profiles/input/quirk.c b/profiles/input/quirk.c
index 053f45b..fa5e7e6 100644
--- a/profiles/input/quirk.c
+++ b/profiles/input/quirk.c
@@ -36,23 +36,6 @@ static struct gamepad_quirk *quirks[] = {
NULL
};
-bool gamepad_quirk_match(struct input_device *idev)
-{
- int i;
-
- /* Check built-in quirks first */
- for (i = 0; quirks[i]; i++) {
- if (quirks[i]->match(idev))
- return true;
- }
-
- /* Then check external (file-based) quirks */
- if (external_quirk_match(idev))
- return true;
-
- return false;
-}
-
int gamepad_quirk_apply(struct input_device *idev,
struct hidp_connadd_req *req)
{
@@ -69,9 +52,5 @@ int gamepad_quirk_apply(struct input_device *idev,
}
/* Try external quirks */
- if (external_quirk_match(idev)) {
- return external_quirk_apply(idev, req);
- }
-
- return -1;
+ return external_quirk_apply(idev, req);
}
diff --git a/profiles/input/quirk.h b/profiles/input/quirk.h
index 08e0bce..f2fc81b 100644
--- a/profiles/input/quirk.h
+++ b/profiles/input/quirk.h
@@ -24,13 +24,10 @@ struct gamepad_quirk {
struct hidp_connadd_req *req);
};
-bool gamepad_quirk_match(struct input_device *idev);
-
int gamepad_quirk_apply(struct input_device *idev,
struct hidp_connadd_req *req);
/* External quirk support (quirk-profile.c) */
-bool external_quirk_match(struct input_device *idev);
int external_quirk_apply(struct input_device *idev,
struct hidp_connadd_req *req);
diff --git a/profiles/input/quirks/tg170w.c b/profiles/input/quirks/tg170w.c
index d3f37a8..d82c3fa 100644
--- a/profiles/input/quirks/tg170w.c
+++ b/profiles/input/quirks/tg170w.c
@@ -38,9 +38,14 @@
#include "bluetooth/sdp.h"
#include "bluetooth/sdp_lib.h"
+#include "gdbus/gdbus.h"
+
#include "src/log.h"
+#include "src/device.h"
+#include "src/service.h"
#include "../quirk.h"
+#include "../device.h"
/*
* DualShock 4 Bluetooth HID Report Descriptor.
@@ -124,25 +129,6 @@ static const uint8_t tg170w_hid_report_descriptor[] = {
#define TG170W_VID 0x054c /* Sony */
#define TG170W_PID 0x09cc /* DualShock 4 v2 */
-/* Opaque structs - we don't pull in heavy headers */
-struct input_device;
-struct btd_device;
-struct btd_service;
-
-/* Declarations from src/device.h and src/service.h */
-extern struct btd_service *input_device_get_service(
- struct input_device *idev);
-extern struct btd_device *btd_service_get_device(
- const struct btd_service *service);
-extern uint16_t btd_device_get_vendor(struct btd_device *device);
-extern uint16_t btd_device_get_product(struct btd_device *device);
-extern const sdp_record_t *btd_device_get_record(
- struct btd_device *device,
- const char *uuid);
-extern bool device_name_known(struct btd_device *device);
-extern void device_get_name(struct btd_device *device,
- char *name, size_t len);
-
static bool tg170w_match(struct input_device *idev)
{
struct btd_service *service;
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH BlueZ v2 3/3] tools: add bluez-quirkctl for managing quirk profiles
2026-07-21 14:17 [PATCH BlueZ v2 0/3] Add HID gamepad quirk fallback for broken SDP records Pakrohk
2026-07-21 14:17 ` [PATCH BlueZ v2 1/3] src: add HMAC-SHA256 to bt_crypto using kernel AF_ALG Pakrohk
2026-07-21 14:17 ` [PATCH BlueZ v2 2/3] input: add HID gamepad quirk fallback for broken SDP records Pakrohk
@ 2026-07-21 14:17 ` Pakrohk
2 siblings, 0 replies; 6+ messages in thread
From: Pakrohk @ 2026-07-21 14:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: luiz.dentz, marcel, Pakrohk
From: Pakrohk <Pakrohk@users.noreply.github.com>
Add bluez-quirkctl, a CLI tool for managing external gamepad quirk
profiles. This tool validates JSON profiles, manages HMAC-SHA256 keys,
and installs/removes signed profiles to the system directory.
Usage:
bluez-quirkctl install <file.json> - validate, sign, install
bluez-quirkctl remove <name> - uninstall a profile
bluez-quirkctl list - list installed profiles
bluez-quirkctl validate <file.json> - check without installing
Security model:
- Install/remove requires root (checked via getuid())
- HMAC key auto-generated in /var/lib/bluez/quirks/.hmac_key (0600)
- Profiles installed to /var/lib/bluez/quirks/ (0644)
- HMAC-SHA256 computed using bt_crypto (kernel AF_ALG)
- Symlinks rejected during loading
- Descriptor size capped at 2048 bytes
The tool links against libbluetooth-internal and libshared-mainloop
for access to bt_crypto_hmac_sha256().
Signed-off-by: Pakrohk <Pakrohk@users.noreply.github.com>
diff --git a/Makefile.tools b/Makefile.tools
index 44da0ce..ecce5b4 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -581,5 +581,6 @@ bin_PROGRAMS += tools/bluez-quirkctl
tools_bluez_quirkctl_SOURCES = tools/quirkctl.c
tools_bluez_quirkctl_CFLAGS = $(JSONC_CFLAGS)
-tools_bluez_quirkctl_LDADD = $(JSONC_LIBS)
+tools_bluez_quirkctl_LDADD = lib/libbluetooth-internal.la \
+ src/libshared-mainloop.la $(JSONC_LIBS)
endif
diff --git a/tools/quirkctl.c b/tools/quirkctl.c
index 5c35e97..93f3207 100644
--- a/tools/quirkctl.c
+++ b/tools/quirkctl.c
@@ -30,10 +30,16 @@
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
+#include <sys/sendfile.h>
#include <pwd.h>
#include <json-c/json.h>
+#include "src/shared/crypto.h"
+
+static const char *prog;
+static struct bt_crypto *crypto;
+
#define QUIRK_DIR "/var/lib/bluez/quirks"
#define USER_QUIRK_DIR "/.config/bluez/quirks"
#define HMAC_KEY_PATH QUIRK_DIR "/.hmac_key"
@@ -42,7 +48,166 @@
#define JSON_EXT ".json"
#define SIG_EXT ".sig"
-static const char *prog;
+/*
+ * Copy file using sendfile(2).
+ */
+static int copy_file(const char *src, const char *dst)
+{
+ int fd_in, fd_out;
+ struct stat st;
+ ssize_t sent;
+
+ fd_in = open(src, O_RDONLY);
+ if (fd_in < 0)
+ return -1;
+
+ if (fstat(fd_in, &st) < 0) {
+ close(fd_in);
+ return -1;
+ }
+
+ fd_out = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (fd_out < 0) {
+ close(fd_in);
+ return -1;
+ }
+
+ sent = sendfile(fd_out, fd_in, NULL, st.st_size);
+ close(fd_in);
+ close(fd_out);
+
+ return (sent == st.st_size) ? 0 : -1;
+}
+
+/*
+ * Load HMAC key from disk. Returns key length or -1 on failure.
+ */
+static int load_hmac_key(uint8_t *key, int max_len)
+{
+ FILE *f;
+ long fsize;
+ char *hex;
+ int key_len, i;
+ char *end;
+
+ f = fopen(HMAC_KEY_PATH, "re");
+ if (!f)
+ return -1;
+
+ fseek(f, 0, SEEK_END);
+ fsize = ftell(f);
+ if (fsize < 0 || fsize > 1024) {
+ fclose(f);
+ return -1;
+ }
+ rewind(f);
+
+ hex = malloc(fsize + 1);
+ if (!hex) {
+ fclose(f);
+ return -1;
+ }
+
+ if (fread(hex, 1, fsize, f) != (size_t)fsize) {
+ free(hex);
+ fclose(f);
+ return -1;
+ }
+ fclose(f);
+ hex[fsize] = '\0';
+
+ end = hex + strlen(hex) - 1;
+ while (end > hex && (*end == '\n' || *end == '\r' || *end == ' '))
+ *end-- = '\0';
+
+ key_len = strlen(hex) / 2;
+ if (key_len > max_len)
+ key_len = max_len;
+
+ for (i = 0; i < key_len; i++) {
+ unsigned int byte;
+ char buf[3];
+
+ buf[0] = hex[i * 2];
+ buf[1] = hex[i * 2 + 1];
+ buf[2] = '\0';
+ if (sscanf(buf, "%2x", &byte) != 1) {
+ free(hex);
+ return -1;
+ }
+ key[i] = (uint8_t)byte;
+ }
+
+ free(hex);
+ return key_len;
+}
+
+/*
+ * Compute HMAC-SHA256 using bt_crypto (kernel AF_ALG).
+ * Returns hex string of 32-byte digest, or NULL on failure.
+ */
+static char *compute_hmac(const char *file_path)
+{
+ FILE *f;
+ long fsize;
+ uint8_t *data;
+ uint8_t key[HMAC_KEY_SIZE];
+ uint8_t digest[32];
+ char hex[HMAC_KEY_SIZE * 2 + 1];
+ char *result;
+ int key_len, i;
+
+ if (!crypto)
+ return NULL;
+
+ /* Read the file */
+ f = fopen(file_path, "re");
+ if (!f)
+ return NULL;
+
+ fseek(f, 0, SEEK_END);
+ fsize = ftell(f);
+ if (fsize < 0 || fsize > 1024 * 1024) {
+ fclose(f);
+ return NULL;
+ }
+ rewind(f);
+
+ data = malloc(fsize);
+ if (!data) {
+ fclose(f);
+ return NULL;
+ }
+
+ if (fread(data, 1, fsize, f) != (size_t)fsize) {
+ free(data);
+ fclose(f);
+ return NULL;
+ }
+ fclose(f);
+
+ /* Load the HMAC key */
+ key_len = load_hmac_key(key, HMAC_KEY_SIZE);
+ if (key_len <= 0) {
+ free(data);
+ return NULL;
+ }
+
+ /* Compute HMAC-SHA256 */
+ if (!bt_crypto_hmac_sha256(crypto, key, key_len, data, fsize, digest)) {
+ free(data);
+ return NULL;
+ }
+ free(data);
+
+ /* Convert to hex string */
+ for (i = 0; i < 32; i++)
+ snprintf(hex + i * 2, 3, "%02x", digest[i]);
+ hex[64] = '\0';
+
+ result = strdup(hex);
+ return result;
+}
static void usage(void)
{
@@ -139,43 +304,6 @@ static int ensure_hmac_key(void)
return 0;
}
-/*
- * Compute HMAC-SHA256 via openssl CLI.
- */
-static char *compute_hmac(const char *file_path)
-{
- char cmd[1024];
- char line[256];
- char *result = NULL;
- FILE *p;
- char *eq;
-
- snprintf(cmd, sizeof(cmd),
- "openssl dgst -sha256 -hmac \"$(cat " HMAC_KEY_PATH ")\" "
- "-hex < '%s' 2>/dev/null", file_path);
-
- p = popen(cmd, "r");
- if (!p)
- return NULL;
-
- while (fgets(line, sizeof(line), p)) {
- eq = strstr(line, "= ");
- if (eq) {
- eq += 2;
- /* trim */
- char *end = eq + strlen(eq) - 1;
- while (end > eq && (*end == '\n' || *end == '\r'
- || *end == ' '))
- *end-- = '\0';
- result = strdup(eq);
- break;
- }
- }
-
- pclose(p);
- return result;
-}
-
/*
* Validate JSON profile structure.
*/
@@ -390,14 +518,10 @@ static int cmd_install(const char *path)
return 1;
}
- {
- char cmd[1024];
- snprintf(cmd, sizeof(cmd), "cp '%s' '%s'", path, dest);
- if (system(cmd) != 0) {
- fprintf(stderr, "Failed to copy profile\n");
- free(name);
- return 1;
- }
+ if (copy_file(path, dest) < 0) {
+ fprintf(stderr, "Failed to copy profile\n");
+ free(name);
+ return 1;
}
if (chmod(dest, 0644) < 0) {
@@ -510,6 +634,8 @@ static int cmd_list(void)
int main(int argc, char *argv[])
{
+ int ret;
+
prog = argv[0];
if (argc < 2) {
@@ -522,7 +648,14 @@ int main(int argc, char *argv[])
fprintf(stderr, "Usage: %s install <file.json>\n", prog);
return 1;
}
- return cmd_install(argv[2]);
+ crypto = bt_crypto_new();
+ if (!crypto) {
+ fprintf(stderr, "Failed to init crypto\n");
+ return 1;
+ }
+ ret = cmd_install(argv[2]);
+ bt_crypto_unref(crypto);
+ return ret;
}
if (strcmp(argv[1], "remove") == 0) {
@@ -533,9 +666,8 @@ int main(int argc, char *argv[])
return cmd_remove(argv[2]);
}
- if (strcmp(argv[1], "list") == 0) {
+ if (strcmp(argv[1], "list") == 0)
return cmd_list();
- }
if (strcmp(argv[1], "validate") == 0) {
if (argc < 3) {
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH BlueZ v2 1/3] src: add HMAC-SHA256 to bt_crypto using kernel AF_ALG
2026-07-21 14:17 ` [PATCH BlueZ v2 1/3] src: add HMAC-SHA256 to bt_crypto using kernel AF_ALG Pakrohk
@ 2026-07-21 14:28 ` Luiz Augusto von Dentz
0 siblings, 0 replies; 6+ messages in thread
From: Luiz Augusto von Dentz @ 2026-07-21 14:28 UTC (permalink / raw)
To: Pakrohk; +Cc: linux-bluetooth, marcel, Pakrohk
Hi Pakrohk,
On Tue, Jul 21, 2026 at 10:17 AM Pakrohk <rhpcir@gmail.com> wrote:
>
> From: Pakrohk <Pakrohk@users.noreply.github.com>
>
> Add HMAC-SHA256 computation to the bt_crypto library using the Linux
> kernel AF_ALG interface. This follows the exact same pattern as the
> existing cmac(aes) implementation.
>
> The HMAC-SHA256 function is needed for verifying external quirk profile
> signatures. The previous implementation used popen("openssl dgst ...")
> which had command injection risks and an openssl CLI dependency.
>
> Using kernel AF_ALG:
> - No external library dependencies
> - Matches existing BlueZ crypto patterns
> - No shell invocation or temp files
> - Works in bluetoothd's process context
Well I guess kernel crypto doesn't want us to add new usage like this
since AF_ALG is to be considered deprecated, even though I have to
agree it is much simpler and probably safer than using an external
crypto library.
> The hmac(sha256) algorithm is available in all modern Linux kernels
> via the crypto API (/proc/crypto).
>
> Signed-off-by: Pakrohk <Pakrohk@users.noreply.github.com>
> diff --git a/src/shared/crypto.c b/src/shared/crypto.c
> index cb99116..f5883bd 100644
> --- a/src/shared/crypto.c
> +++ b/src/shared/crypto.c
> @@ -69,6 +69,7 @@ struct bt_crypto {
> int ecb_aes;
> int urandom;
> int cmac_aes;
> + int hmac_sha256;
> };
>
> static int urandom_setup(void)
> @@ -126,6 +127,28 @@ static int cmac_aes_setup(void)
> return fd;
> }
>
> +static int hmac_sha256_setup(void)
> +{
> + struct sockaddr_alg salg;
> + int fd;
> +
> + fd = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
> + if (fd < 0)
> + return -1;
> +
> + memset(&salg, 0, sizeof(salg));
> + salg.salg_family = AF_ALG;
> + strcpy((char *) salg.salg_type, "hash");
> + strcpy((char *) salg.salg_name, "hmac(sha256)");
> +
> + if (bind(fd, (struct sockaddr *) &salg, sizeof(salg)) < 0) {
> + close(fd);
> + return -1;
> + }
> +
> + return fd;
> +}
> +
> static struct bt_crypto *singleton;
>
> struct bt_crypto *bt_crypto_new(void)
> @@ -159,6 +182,16 @@ struct bt_crypto *bt_crypto_new(void)
> return NULL;
> }
>
> + singleton->hmac_sha256 = hmac_sha256_setup();
> + if (singleton->hmac_sha256 < 0) {
> + close(singleton->cmac_aes);
> + close(singleton->urandom);
> + close(singleton->ecb_aes);
> + free(singleton);
> + singleton = NULL;
> + return NULL;
> + }
> +
> return bt_crypto_ref(singleton);
> }
>
> @@ -183,6 +216,7 @@ void bt_crypto_unref(struct bt_crypto *crypto)
> close(crypto->urandom);
> close(crypto->ecb_aes);
> close(crypto->cmac_aes);
> + close(crypto->hmac_sha256);
>
> free(crypto);
> singleton = NULL;
> @@ -1005,3 +1039,35 @@ bool bt_crypto_sirk(struct bt_crypto *crypto, const char *str, uint16_t vendor,
> /* Encrypt sirk using k as LTK with sef function */
> return bt_crypto_sef(crypto, k, sirk_plaintext, sirk);
> }
> +
> +bool bt_crypto_hmac_sha256(struct bt_crypto *crypto,
> + const uint8_t *key, size_t key_len,
> + const uint8_t *msg, size_t msg_len,
> + uint8_t res[32])
> +{
> + ssize_t len;
> + int fd;
> +
> + if (!crypto)
> + return false;
> +
> + fd = alg_new(crypto->hmac_sha256, key, key_len);
> + if (fd < 0)
> + return false;
> +
> + len = send(fd, msg, msg_len, 0);
> + if (len < 0) {
> + close(fd);
> + return false;
> + }
> +
> + len = read(fd, res, 32);
> + if (len < 0) {
> + close(fd);
> + return false;
> + }
> +
> + close(fd);
> +
> + return true;
> +}
> diff --git a/src/shared/crypto.h b/src/shared/crypto.h
> index d85f807..6b234bc 100644
> --- a/src/shared/crypto.h
> +++ b/src/shared/crypto.h
> @@ -62,3 +62,8 @@ bool bt_crypto_sirk(struct bt_crypto *crypto, const char *str, uint16_t vendor,
> uint8_t sirk[16]);
> bool bt_crypto_rsi(struct bt_crypto *crypto, const uint8_t sirk[16],
> uint8_t rsi[6]);
> +
> +bool bt_crypto_hmac_sha256(struct bt_crypto *crypto,
> + const uint8_t *key, size_t key_len,
> + const uint8_t *msg, size_t msg_len,
> + uint8_t res[32]);
>
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH BlueZ v2 2/3] input: add HID gamepad quirk fallback for broken SDP records
2026-07-21 14:17 ` [PATCH BlueZ v2 2/3] input: add HID gamepad quirk fallback for broken SDP records Pakrohk
@ 2026-07-21 14:36 ` Luiz Augusto von Dentz
0 siblings, 0 replies; 6+ messages in thread
From: Luiz Augusto von Dentz @ 2026-07-21 14:36 UTC (permalink / raw)
To: Pakrohk; +Cc: linux-bluetooth, marcel, Pakrohk
Hi Pakrohk,
On Tue, Jul 21, 2026 at 10:17 AM Pakrohk <rhpcir@gmail.com> wrote:
>
> From: Pakrohk <Pakrohk@users.noreply.github.com>
>
> Add a modular gamepad quirk system to profiles/input/ that provides HID
> report descriptor fallbacks when BlueZ's SDP parser fails.
>
> When extract_hid_record() returns -ENOENT for a known gamepad, the quirk
> system checks registered quirks and injects a fallback HID descriptor so
> the kernel's HID driver can create an input device.
>
> Two tiers of quirk support:
>
> Built-in quirks:
> Hardcoded C entries for known broken controllers. Currently supports
> DualShock 4 v2 / TG170W (054c:09cc) with a minimal BT HID descriptor
> matching report ID 0x11 (78 bytes, as expected by hid-playstation).
>
> External quirk profiles:
> JSON files in /var/lib/bluez/quirks/ with HMAC-SHA256 signatures.
> Loaded at bluetoothd startup. Enables community-maintained support
> without BlueZ source modifications.
>
> Architecture:
> quirk.h - quirk struct definition and dispatch API
> quirk.c - quirk registry and dispatch logic
> quirk-profile.h/c - external JSON profile loader with HMAC verification
> quirks/tg170w.c - built-in quirk for DualShock 4 v2
>
> The quirk only activates when SDP parsing fails AND the device matches
> a registered quirk. This does not globally weaken HID parsing.
>
> Multi-factor matching avoids false positives:
> match = (vendor_id == 0x054c && product_id == 0x09cc)
> || (device_name == "Wireless Controller"
> && SDP provider contains "Sony")
>
> Signed-off-by: Pakrohk <Pakrohk@users.noreply.github.com>
> diff --git a/profiles/input/device.c b/profiles/input/device.c
> index 8017e07..0089c3d 100644
> --- a/profiles/input/device.c
> +++ b/profiles/input/device.c
> @@ -1089,11 +1089,8 @@ static int hidp_add_connection(struct input_device *idev)
>
> err = extract_hid_record(idev, req);
> if (err < 0) {
> - /* Try gamepad quirk fallback for known broken devices */
> - if (gamepad_quirk_match(idev)) {
> - DBG("HID SDP failed, trying gamepad quirk");
> - err = gamepad_quirk_apply(idev, req);
> - }
> + DBG("HID SDP failed, trying gamepad quirk");
> + err = gamepad_quirk_apply(idev, req);
> }
>
> if (err < 0) {
> diff --git a/profiles/input/quirk-profile.c b/profiles/input/quirk-profile.c
> index 8ec3835..4240154 100644
> --- a/profiles/input/quirk-profile.c
> +++ b/profiles/input/quirk-profile.c
> @@ -28,22 +28,20 @@
>
> #include "bluetooth/bluetooth.h"
> #include "bluetooth/hidp.h"
> +#include "bluetooth/sdp.h"
> +#include "bluetooth/sdp_lib.h"
>
> #include "src/log.h"
>
> #include "quirk.h"
> #include "quirk-profile.h"
>
> -/* Accessors from src/device.h and src/service.h */
> -extern struct btd_service *input_device_get_service(
> - struct input_device *idev);
> -extern struct btd_device *btd_service_get_device(
> - const struct btd_service *service);
> -extern uint16_t btd_device_get_vendor(struct btd_device *device);
> -extern uint16_t btd_device_get_product(struct btd_device *device);
> -extern bool device_name_known(struct btd_device *device);
> -extern void device_get_name(struct btd_device *device,
> - char *name, size_t len);
> +#include "gdbus/gdbus.h"
> +
> +#include "src/device.h"
> +#include "src/service.h"
> +#include "src/shared/crypto.h"
> +#include "device.h"
>
> #define MAX_EXTERNAL_QUIRKS 32
> #define HMAC_KEY_PATH QUIRK_PROFILE_DIR "/.hmac_key"
> @@ -67,6 +65,7 @@ struct external_quirk {
>
> static struct external_quirk *ext_quirks[MAX_EXTERNAL_QUIRKS + 1];
> static int num_ext_quirks;
> +static struct bt_crypto *crypto;
>
> /*
> * Parse a hex string like "0501 0905" into a byte buffer.
> @@ -139,77 +138,23 @@ static char *read_file(const char *path, size_t *out_len)
> }
>
> /*
> - * Compute HMAC-SHA256 using openssl CLI.
> - */
> -static char *hmac_sha256(const uint8_t *key, int key_len,
> - const void *data, size_t data_len)
This file doesn't even exist upstream, so how come you're modifying
it? Looks like there is something off with this patch-set, beside it
is saying that it doesn't use openssl so this seem to be a left over
or something.
> -{
> - char key_hex[HMAC_KEY_SIZE * 2 + 1];
> - char *cmd;
> - char *result = NULL;
> - FILE *p, *tmpf;
> - int i;
> - const char *tmpfile = "/tmp/.bluez_quirk_hmac_input";
> - char line[256];
> - char *eq, *end;
> -
> - for (i = 0; i < key_len; i++)
> - snprintf(key_hex + i * 2, 3, "%02x", key[i]);
> - key_hex[key_len * 2] = '\0';
> -
> - tmpf = fopen(tmpfile, "we");
> - if (!tmpf)
> - return NULL;
> -
> - fwrite(data, 1, data_len, tmpf);
> - fclose(tmpf);
> -
> - cmd = malloc(strlen(key_hex) + 128);
> - if (!cmd) {
> - unlink(tmpfile);
> - return NULL;
> - }
> -
> - snprintf(cmd, strlen(key_hex) + 128,
> - "openssl dgst -sha256 -hmac '%s' -hex < %s 2>/dev/null",
> - key_hex, tmpfile);
> -
> - p = popen(cmd, "re");
> - free(cmd);
> - unlink(tmpfile);
> -
> - if (!p)
> - return NULL;
> -
> - while (fgets(line, sizeof(line), p)) {
> - eq = strstr(line, "= ");
> - if (eq) {
> - eq += 2;
> - end = eq + strlen(eq) - 1;
> - while (end > eq && (*end == '\n' || *end == '\r'
> - || *end == ' '))
> - *end-- = '\0';
> - result = strdup(eq);
> - break;
> - }
> - }
> -
> - pclose(p);
> - return result;
> -}
> -
> -/*
> - * Verify HMAC signature.
> + * Verify HMAC-SHA256 signature using bt_crypto (kernel AF_ALG).
> */
> static bool verify_signature(const char *json_path,
> const uint8_t *hmac_key, int key_len)
> {
> char sig_path[1024];
> size_t sig_len, json_len;
> - char *sig_hex, *json_data, *expected_sig;
> + char *sig_hex, *json_data;
> char *end;
> + uint8_t computed[32];
> + char computed_hex[65];
> + int i;
> bool valid;
>
> + if (!crypto)
> + return false;
> +
> snprintf(sig_path, sizeof(sig_path), "%s" SIG_EXT, json_path);
>
> sig_hex = read_file(sig_path, &sig_len);
> @@ -228,21 +173,25 @@ static bool verify_signature(const char *json_path,
> return false;
> }
>
> - expected_sig = hmac_sha256(hmac_key, key_len, json_data, json_len);
> - free(json_data);
> -
> - if (!expected_sig) {
> + if (!bt_crypto_hmac_sha256(crypto, hmac_key, key_len,
> + (const uint8_t *) json_data, json_len,
> + computed)) {
> + free(json_data);
> free(sig_hex);
> return false;
> }
> + free(json_data);
> +
> + for (i = 0; i < 32; i++)
> + snprintf(computed_hex + i * 2, 3, "%02x", computed[i]);
> + computed_hex[64] = '\0';
>
> - valid = (strcmp(sig_hex, expected_sig) == 0);
> + valid = (strcmp(sig_hex, computed_hex) == 0);
>
> if (!valid)
> DBG("Signature mismatch for %s", json_path);
>
> free(sig_hex);
> - free(expected_sig);
> return valid;
> }
>
> @@ -516,6 +465,12 @@ int load_external_quirks(const char *dir)
> if (!dir)
> dir = QUIRK_PROFILE_DIR;
>
> + if (!crypto) {
> + crypto = bt_crypto_new();
> + if (!crypto)
> + DBG("quirk-profile: failed to init crypto");
> + }
> +
> d = opendir(dir);
> if (!d) {
> DBG("quirk-profile: cannot open %s: %s", dir, strerror(errno));
> @@ -586,4 +541,9 @@ void free_external_quirks(void)
> ext_quirks[i] = NULL;
> }
> num_ext_quirks = 0;
> +
> + if (crypto) {
> + bt_crypto_unref(crypto);
> + crypto = NULL;
> + }
> }
> diff --git a/profiles/input/quirk.c b/profiles/input/quirk.c
> index 053f45b..fa5e7e6 100644
> --- a/profiles/input/quirk.c
> +++ b/profiles/input/quirk.c
> @@ -36,23 +36,6 @@ static struct gamepad_quirk *quirks[] = {
> NULL
> };
>
> -bool gamepad_quirk_match(struct input_device *idev)
> -{
> - int i;
> -
> - /* Check built-in quirks first */
> - for (i = 0; quirks[i]; i++) {
> - if (quirks[i]->match(idev))
> - return true;
> - }
> -
> - /* Then check external (file-based) quirks */
> - if (external_quirk_match(idev))
> - return true;
> -
> - return false;
> -}
> -
> int gamepad_quirk_apply(struct input_device *idev,
> struct hidp_connadd_req *req)
> {
> @@ -69,9 +52,5 @@ int gamepad_quirk_apply(struct input_device *idev,
> }
>
> /* Try external quirks */
> - if (external_quirk_match(idev)) {
> - return external_quirk_apply(idev, req);
> - }
> -
> - return -1;
> + return external_quirk_apply(idev, req);
> }
> diff --git a/profiles/input/quirk.h b/profiles/input/quirk.h
> index 08e0bce..f2fc81b 100644
> --- a/profiles/input/quirk.h
> +++ b/profiles/input/quirk.h
> @@ -24,13 +24,10 @@ struct gamepad_quirk {
> struct hidp_connadd_req *req);
> };
>
> -bool gamepad_quirk_match(struct input_device *idev);
> -
> int gamepad_quirk_apply(struct input_device *idev,
> struct hidp_connadd_req *req);
>
> /* External quirk support (quirk-profile.c) */
> -bool external_quirk_match(struct input_device *idev);
> int external_quirk_apply(struct input_device *idev,
> struct hidp_connadd_req *req);
>
> diff --git a/profiles/input/quirks/tg170w.c b/profiles/input/quirks/tg170w.c
> index d3f37a8..d82c3fa 100644
> --- a/profiles/input/quirks/tg170w.c
> +++ b/profiles/input/quirks/tg170w.c
> @@ -38,9 +38,14 @@
> #include "bluetooth/sdp.h"
> #include "bluetooth/sdp_lib.h"
>
> +#include "gdbus/gdbus.h"
> +
> #include "src/log.h"
> +#include "src/device.h"
> +#include "src/service.h"
>
> #include "../quirk.h"
> +#include "../device.h"
>
> /*
> * DualShock 4 Bluetooth HID Report Descriptor.
> @@ -124,25 +129,6 @@ static const uint8_t tg170w_hid_report_descriptor[] = {
> #define TG170W_VID 0x054c /* Sony */
> #define TG170W_PID 0x09cc /* DualShock 4 v2 */
>
> -/* Opaque structs - we don't pull in heavy headers */
> -struct input_device;
> -struct btd_device;
> -struct btd_service;
> -
> -/* Declarations from src/device.h and src/service.h */
> -extern struct btd_service *input_device_get_service(
> - struct input_device *idev);
> -extern struct btd_device *btd_service_get_device(
> - const struct btd_service *service);
> -extern uint16_t btd_device_get_vendor(struct btd_device *device);
> -extern uint16_t btd_device_get_product(struct btd_device *device);
> -extern const sdp_record_t *btd_device_get_record(
> - struct btd_device *device,
> - const char *uuid);
> -extern bool device_name_known(struct btd_device *device);
> -extern void device_get_name(struct btd_device *device,
> - char *name, size_t len);
> -
> static bool tg170w_match(struct input_device *idev)
> {
> struct btd_service *service;
>
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-07-21 14:37 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-21 14:17 [PATCH BlueZ v2 0/3] Add HID gamepad quirk fallback for broken SDP records Pakrohk
2026-07-21 14:17 ` [PATCH BlueZ v2 1/3] src: add HMAC-SHA256 to bt_crypto using kernel AF_ALG Pakrohk
2026-07-21 14:28 ` Luiz Augusto von Dentz
2026-07-21 14:17 ` [PATCH BlueZ v2 2/3] input: add HID gamepad quirk fallback for broken SDP records Pakrohk
2026-07-21 14:36 ` Luiz Augusto von Dentz
2026-07-21 14:17 ` [PATCH BlueZ v2 3/3] tools: add bluez-quirkctl for managing quirk profiles Pakrohk
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox