[PATCH BlueZ v2 2/3] input: add HID gamepad quirk fallback for broken SDP records

Pakrohk <[email protected]>
Newsgroups org.kernel.vger.linux-bluetooth
Message-ID <[email protected]>
From: Pakrohk <[email protected]>

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 <[email protected]>
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;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.