windows: Allow device GUIDs missing terminating characters or separators

Allow REG_MULTI_SZ strings with multiple GUIDs, and use the first GUID
for enumerating the device interfaces.

This solves issues with e.g. a composite device with four WinUSB
interfaces, where the device implements "Extended Properties OS Feature
Descriptor" with multiple GUIDs. An attempt to open the device fails
with an error LIBUSB_ERROR_NOT_FOUND (-5).

Fixes #1307

Signed-off-by: Tormod Volden <debian.tormod@gmail.com>
This commit is contained in:
Petr Pazourek
2023-10-10 20:14:40 +02:00
committed by Tormod Volden
parent d66ffcdd12
commit fdab67b14a
3 changed files with 161 additions and 57 deletions

View File

@@ -1450,6 +1450,137 @@ static int set_hid_interface(struct libusb_context *ctx, struct libusb_device *d
return LIBUSB_SUCCESS;
}
// get the n-th device interface GUID indexed by guid_number
static int get_guid(struct libusb_context *ctx, char *dev_id, HDEVINFO *dev_info, SP_DEVINFO_DATA *dev_info_data,
int guid_number, GUID **if_guid)
{
DWORD size, reg_type;
HKEY key;
char *guid_string, *new_guid_string;
char *guid, *guid_term;
LONG s;
int pass, guids_left;
int err = LIBUSB_SUCCESS;
key = pSetupDiOpenDevRegKey(*dev_info, dev_info_data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
if (key == INVALID_HANDLE_VALUE) {
usbi_warn(ctx, "Cannot get the additional GUIDs for '%s'", dev_id);
return LIBUSB_ERROR_ACCESS;
}
// Reserve buffer large enough to hold one GUID with two terminating characters
size = MAX_GUID_STRING_LENGTH + 1;
// Allocate memory for storing the guid_string with two extra terminating characters
// This is necessary for parsing the REG_MULTI_SZ type below
guid_string = malloc(size + 2);
if (guid_string == NULL) {
usbi_err(ctx, "failed to alloc guid_string");
return LIBUSB_ERROR_NO_MEM;
}
// The 1st pass tries to get the guid. If it fails due to ERROR_MORE_DATA
// then reallocate enough memory for the 2nd pass
for (pass = 0; pass < 2; pass++) {
// Look for both DeviceInterfaceGUIDs *and* DeviceInterfaceGUID, in that order
// If multiple GUIDs, find the n-th that is indexed by guid_number
s = pRegQueryValueExA(key, "DeviceInterfaceGUIDs", NULL, &reg_type,
(LPBYTE)guid_string, &size);
if (s == ERROR_FILE_NOT_FOUND)
s = pRegQueryValueExA(key, "DeviceInterfaceGUID", NULL, &reg_type,
(LPBYTE)guid_string, &size);
if (s == ERROR_SUCCESS) {
// The GUID was read successfully
break;
} else if (s == ERROR_FILE_NOT_FOUND) {
usbi_warn(ctx, "no DeviceInterfaceGUID registered for '%s'", dev_id);
err = LIBUSB_ERROR_ACCESS;
goto exit;
} else if (s == ERROR_MORE_DATA) {
if (pass == 1) {
// Previous pass should have allocated enough memory, but reading failed
usbi_warn(ctx, "unexpected error from pRegQueryValueExA for '%s'", dev_id);
err = LIBUSB_ERROR_OTHER;
goto exit;
}
new_guid_string = realloc((void *)guid_string, size + 2);
if (new_guid_string == NULL) {
usbi_err(ctx, "failed to realloc guid string");
err = LIBUSB_ERROR_NO_MEM;
goto exit;
}
guid_string = new_guid_string;
} else {
usbi_warn(ctx, "unexpected error from pRegQueryValueExA for '%s'", dev_id);
err = LIBUSB_ERROR_ACCESS;
goto exit;
}
}
// https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryvalueexa#remarks
// - "string may not have been stored with the proper terminating null characters"
// - The following GUIDs should be consider as valid:
// "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}\0", "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}",
// "{xxx.....xx}\0\0\0", "{xxx.....xx}\0{xxx.....xx}\0{xxx.....xx}\0",
// "{xxx.....xx}\0{xxx.....xx}\0{xxx.....xx}", "{xxx.....xx}{xxx.....xx}{xxx.....xx}",
// "{xxx.....xx}\0{xxx.....xx}\0{xxx.....xx}\0\0\0\0"
if ((reg_type == REG_SZ ) || (reg_type == REG_MULTI_SZ)) {
/* Get the n-th GUID indexed by guid_number since the DeviceInterfaceGUIDs may
contain more GUIDs */
guid = guid_string;
// Add two terminating chars for not overrunning the allocated memory while iterating
guid[size] = '\0';
guid[size + 1] = '\0';
// Iterate the GUIDs in the guid string
guids_left = guid_number;
while (guids_left) {
guid = strchr(guid, '}');
if (guid == NULL) {
usbi_warn(ctx, "no GUID with index %d registered for '%s'", guid_number, dev_id);
err = LIBUSB_ERROR_ACCESS;
goto exit;
}
guid++;
// Skip the terminating char if available
if (*guid == '\0') {
guid++;
}
guids_left--;
}
// Add terminating char to the string
guid_term = strchr(guid, '}');
if (guid_term == NULL) {
usbi_warn(ctx, "no GUID with index %d registered for '%s'", guid_number, dev_id);
err = LIBUSB_ERROR_ACCESS;
goto exit;
}
// Terminate the current guid string to handle the variant without separators
guid_term++;
*guid_term = '\0';
} else {
usbi_warn(ctx, "unexpected type of DeviceInterfaceGUID for '%s'", dev_id);
err = LIBUSB_ERROR_ACCESS;
goto exit;
}
*if_guid = malloc(sizeof(GUID));
if (*if_guid == NULL) {
usbi_err(ctx, "failed to alloc if_guid");
err = LIBUSB_ERROR_NO_MEM;
goto exit;
}
if (!string_to_guid(guid, *if_guid)) {
usbi_warn(ctx, "device '%s' has malformed DeviceInterfaceGUID string '%s', skipping", dev_id, guid);
free(*if_guid);
*if_guid = NULL;
err = LIBUSB_ERROR_NO_MEM;
goto exit;
}
exit:
pRegCloseKey(key);
free(guid_string);
return err;
}
/*
* get_device_list: libusb backend device enumeration function
*/
@@ -1469,12 +1600,10 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_
struct winusb_device_priv *priv, *parent_priv;
char *dev_interface_path = NULL;
unsigned long session_id;
DWORD size, port_nr, reg_type, install_state;
DWORD size, port_nr, install_state;
uint8_t bus_number = 0;
HKEY key;
char guid_string[MAX_GUID_STRING_LENGTH];
GUID *if_guid;
LONG s;
#define HUB_PASS 0
#define DEV_PASS 1
#define HCD_PASS 2
@@ -1635,62 +1764,37 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_
usbi_info(ctx, "libusb will not be able to access it");
}
// ...and to add the additional device interface GUIDs
key = pSetupDiOpenDevRegKey(*dev_info, &dev_info_data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
if (key == INVALID_HANDLE_VALUE)
break;
// Look for both DeviceInterfaceGUIDs *and* DeviceInterfaceGUID, in that order
// If multiple GUIDs just process the first and ignore the others
size = sizeof(guid_string);
s = pRegQueryValueExA(key, "DeviceInterfaceGUIDs", NULL, &reg_type,
(LPBYTE)guid_string, &size);
if (s == ERROR_FILE_NOT_FOUND)
s = pRegQueryValueExA(key, "DeviceInterfaceGUID", NULL, &reg_type,
(LPBYTE)guid_string, &size);
pRegCloseKey(key);
if (s == ERROR_FILE_NOT_FOUND) {
break; /* no DeviceInterfaceGUID registered */
} else if (s != ERROR_SUCCESS && s != ERROR_MORE_DATA) {
usbi_warn(ctx, "unexpected error from pRegQueryValueExA for '%s'", dev_id);
break;
}
// https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryvalueexa#remarks
// - "string may not have been stored with the proper terminating null characters"
// - "Note that REG_MULTI_SZ strings could have two terminating null characters"
if ((reg_type == REG_SZ && size >= sizeof(guid_string) - sizeof(char))
|| (reg_type == REG_MULTI_SZ && size >= sizeof(guid_string) - 2 * sizeof(char))) {
if (nb_guids == guid_size) {
new_guid_list = realloc((void *)guid_list, (guid_size + GUID_SIZE_STEP) * sizeof(void *));
if (new_guid_list == NULL) {
usbi_err(ctx, "failed to realloc guid list");
LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
r = get_guid(ctx, dev_id, dev_info, &dev_info_data, 0, &if_guid);
if (r == LIBUSB_SUCCESS) {
// Check if we've already seen this GUID
for (j = EXT_PASS; j < nb_guids; j++) {
if (memcmp(guid_list[j], if_guid, sizeof(*if_guid)) == 0)
break;
}
if (j == nb_guids) {
usbi_dbg(ctx, "extra GUID: %s", guid_to_string(if_guid, guid_string));
// Extend the guid_list capacity if needed
if (nb_guids == guid_size) {
new_guid_list = realloc((void *)guid_list, (guid_size + GUID_SIZE_STEP) * sizeof(void *));
if (new_guid_list == NULL) {
usbi_err(ctx, "failed to realloc guid list");
free(if_guid);
LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
}
guid_list = new_guid_list;
guid_size += GUID_SIZE_STEP;
}
guid_list = new_guid_list;
guid_size += GUID_SIZE_STEP;
}
if_guid = malloc(sizeof(*if_guid));
if (if_guid == NULL) {
usbi_err(ctx, "failed to alloc if_guid");
LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
}
if (!string_to_guid(guid_string, if_guid)) {
usbi_warn(ctx, "device '%s' has malformed DeviceInterfaceGUID string '%s', skipping", dev_id, guid_string);
free(if_guid);
guid_list[nb_guids++] = if_guid;
} else {
// Check if we've already seen this GUID
for (j = EXT_PASS; j < nb_guids; j++) {
if (memcmp(guid_list[j], if_guid, sizeof(*if_guid)) == 0)
break;
}
if (j == nb_guids) {
usbi_dbg(ctx, "extra GUID: %s", guid_string);
guid_list[nb_guids++] = if_guid;
} else {
// Duplicate, ignore
free(if_guid);
}
// Duplicate, ignore
free(if_guid);
}
} else if (r == LIBUSB_ERROR_ACCESS) {
r = LIBUSB_SUCCESS;
} else if (r == LIBUSB_ERROR_NO_MEM) {
LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
} else {
usbi_warn(ctx, "unexpected type/size of DeviceInterfaceGUID for '%s'", dev_id);
usbi_warn(ctx, "unexpected error during getting DeviceInterfaceGUID for '%s'", dev_id);
}
break;
case HID_PASS:

View File

@@ -33,7 +33,7 @@
#define MAX_USB_STRING_LENGTH 128
#define MAX_HID_REPORT_SIZE 1024
#define MAX_HID_DESCRIPTOR_SIZE 256
#define MAX_GUID_STRING_LENGTH 40
#define MAX_GUID_STRING_LENGTH 39
#define MAX_PATH_LENGTH 256
#define MAX_KEY_LENGTH 256
#define LIST_SEPARATOR ';'

View File

@@ -1 +1 @@
#define LIBUSB_NANO 11815
#define LIBUSB_NANO 11816