summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Nicolás Nistal2026-09-15 14:16:59 -0400
committerGravatar Nikias Bassen2026-11-06 22:42:35 +0800
commit3301fe7d834a485b368cd7edf20f7535d6d598b4 (patch)
treee2cf641b2051b20d74368bc76acb1e9128c793be
parent42f2c26463da466f42d207b9c0094df0d351d47a (diff)
downloadlibirecovery-3301fe7d834a485b368cd7edf20f7535d6d598b4.tar.gz
libirecovery-3301fe7d834a485b368cd7edf20f7535d6d598b4.tar.bz2
Make the iSerialNumber string read robust to slow or failing devicesHEADmaster
libusb_get_string_descriptor_ascii() uses a fixed 1 s timeout for the control transfer. The Apple T1 (iBridge1,1) in recovery mode answers the iSerialNumber read only intermittently and often later than that, so the read returns LIBUSB_ERROR_TIMEOUT. Losing it costs the device identity and the AP and SEP nonces, and in the hotplug add handler it meant giving up on the device: IRECV_DEVICE_ADD was never emitted and callers concluded that nothing was attached. - When the libusb helper fails, retry the descriptor read a few times with libirecovery's own USB_TIMEOUT. - On Linux, when the read still yields no iBoot string, fall back to the serial string the kernel already cached for the same bus and device number under /sys/bus/usb/devices. Used both when opening a device and in the hotplug add handler; never fatal, and a no-op on other platforms. - Only assume the legacy CPID 0x8900 when the iBoot string is non-empty but carries no CPID field. An empty string means the read failed, and claiming 0x8900 there mis-identifies every modern part. Behaviour for devices that answer the first read promptly is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
-rw-r--r--src/libirecovery.c145
1 files changed, 138 insertions, 7 deletions
diff --git a/src/libirecovery.c b/src/libirecovery.c
index d6c72a5..72d8072 100644
--- a/src/libirecovery.c
+++ b/src/libirecovery.c
@@ -24,6 +24,7 @@
#endif
#include <stdio.h>
+#include <dirent.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@@ -819,13 +820,53 @@ static int iokit_get_string_descriptor_ascii(irecv_client_t client, uint8_t desc
}
#endif
+/* Fetch a string descriptor with libirecovery's own USB_TIMEOUT and a few
+ * retries. libusb_get_string_descriptor_ascii() hardcodes a 1 s timeout, which
+ * some parts miss: the Apple T1/iBridge in recovery answers this control
+ * transfer only intermittently and well beyond 1 s, returning
+ * LIBUSB_ERROR_TIMEOUT (-7). Losing that read costs us the device identity AND
+ * the AP/SEP nonces, so it is worth waiting for. */
+static int irecv_get_string_descriptor_ascii_slow(irecv_client_t client, uint8_t desc_index, unsigned char *buffer, int size)
+{
+ unsigned char data[256];
+ int attempt, di, si, ret = -1;
+
+ for (attempt = 0; attempt < 3; attempt++) {
+ memset(data, 0, sizeof(data));
+ memset(buffer, 0, size);
+
+ ret = irecv_usb_control_transfer(client, 0x80, 0x06, (0x03 << 8) | desc_index, 0,
+ data, sizeof(data) - 1, USB_TIMEOUT);
+ if (ret < 0) {
+ debug("%s: attempt %d: control transfer failed (%d)\n", __func__, attempt + 1, ret);
+ continue;
+ }
+ if (data[1] != 0x03 || data[0] > ret) {
+ debug("%s: attempt %d: malformed descriptor\n", __func__, attempt + 1);
+ ret = -1;
+ continue;
+ }
+ for (di = 0, si = 2; si < data[0]; si += 2) {
+ if (di >= (size - 1)) break;
+ buffer[di++] = data[si + 1] ? '?' : data[si];
+ }
+ buffer[di] = 0;
+ debug("%s: got %d bytes on attempt %d\n", __func__, di, attempt + 1);
+ return di;
+ }
+ return ret;
+}
+
static int irecv_get_string_descriptor_ascii(irecv_client_t client, uint8_t desc_index, unsigned char * buffer, int size)
{
#ifndef _WIN32
#ifdef HAVE_IOKIT
return iokit_get_string_descriptor_ascii(client, desc_index, buffer, size);
#else
- return libusb_get_string_descriptor_ascii(client->handle, desc_index, buffer, size);
+ int r = libusb_get_string_descriptor_ascii(client->handle, desc_index, buffer, size);
+ if (r >= 0) return r;
+ debug("%s: libusb helper failed (%d); retrying with USB_TIMEOUT\n", __func__, r);
+ return irecv_get_string_descriptor_ascii_slow(client, desc_index, buffer, size);
#endif
#else
irecv_error_t ret;
@@ -872,8 +913,12 @@ static void irecv_load_device_info_from_iboot_string(irecv_client_t client, cons
if (ptr != NULL) {
sscanf(ptr, "CPID:%x", &client->device_info.cpid);
client->device_info.have_cpid = 1;
- } else {
- // early iOS 1 doesn't identify itself
+ } else if (iboot_string[0] != '\0') {
+ // early iOS 1 doesn't identify itself. Only assume that when the
+ // device actually said something; an EMPTY string means the
+ // descriptor read failed, and silently claiming to be an 0x8900
+ // part then mis-identifies every modern device (e.g. an Apple T1
+ // reports CPID:8002 but would come back as an iPhone 2G).
client->device_info.cpid = 0x8900;
}
@@ -2121,6 +2166,66 @@ static irecv_error_t iokit_open_with_ecid(irecv_client_t* pclient, uint64_t ecid
#ifndef _WIN32
#ifndef HAVE_IOKIT
+
+/* Read the USB serial string the kernel already cached for this device.
+ * Linux only; returns the length, or 0 if unavailable. Never fatal. */
+static int irecv_linux_sysfs_serial(struct libusb_device_handle *handle, char *out, size_t out_len)
+{
+#ifdef __linux__
+ libusb_device *dev;
+ uint8_t bus, addr;
+ DIR *d;
+ struct dirent *e;
+ int got = 0;
+
+ if (!handle || !out || out_len == 0) return 0;
+ dev = libusb_get_device(handle);
+ if (!dev) return 0;
+ bus = libusb_get_bus_number(dev);
+ addr = libusb_get_device_address(dev);
+
+ d = opendir("/sys/bus/usb/devices");
+ if (!d) return 0;
+ while ((e = readdir(d)) != NULL) {
+ char path[512];
+ FILE *f;
+ int b = -1, a = -1;
+
+ if (e->d_name[0] == '.') continue;
+
+ snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/busnum", e->d_name);
+ f = fopen(path, "r");
+ if (!f) continue;
+ if (fscanf(f, "%d", &b) != 1) { fclose(f); continue; }
+ fclose(f);
+
+ snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/devnum", e->d_name);
+ f = fopen(path, "r");
+ if (!f) continue;
+ if (fscanf(f, "%d", &a) != 1) { fclose(f); continue; }
+ fclose(f);
+
+ if (b != (int)bus || a != (int)addr) continue;
+
+ snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/serial", e->d_name);
+ f = fopen(path, "r");
+ if (!f) break;
+ if (fgets(out, (int)out_len, f)) {
+ size_t n = strlen(out);
+ while (n && (out[n-1] == '\n' || out[n-1] == '\r')) out[--n] = '\0';
+ got = (int)n;
+ }
+ fclose(f);
+ break;
+ }
+ closedir(d);
+ return got;
+#else
+ (void)handle; (void)out; (void)out_len;
+ return 0;
+#endif
+}
+
static irecv_error_t libusb_usb_open_handle_with_descriptor_and_ecid(irecv_client_t *pclient, struct libusb_device_handle *usb_handle, struct libusb_device_descriptor *usb_descriptor, uint64_t ecid)
{
irecv_client_t client = (irecv_client_t) malloc(sizeof(struct irecv_client_private));
@@ -2136,8 +2241,21 @@ static irecv_error_t libusb_usb_open_handle_with_descriptor_and_ecid(irecv_clien
if (client->mode != KIS_PRODUCT_ID) {
char serial_str[256];
+ int slen;
memset(serial_str, 0, sizeof(serial_str));
- irecv_get_string_descriptor_ascii(client, usb_descriptor->iSerialNumber, (unsigned char*)serial_str, sizeof(serial_str)-1);
+ slen = irecv_get_string_descriptor_ascii(client, usb_descriptor->iSerialNumber, (unsigned char*)serial_str, sizeof(serial_str)-1);
+ if (slen <= 0 || strstr(serial_str, "CPID:") == NULL) {
+ /* Some parts (observed on the Apple T1/iBridge in recovery) do
+ * not answer the string-descriptor control transfer here even
+ * though the kernel enumerated the very same string at attach.
+ * Fall back to the kernel's cached copy rather than proceeding
+ * with an unidentified device. */
+ debug("string descriptor read gave %d bytes; trying kernel copy\n", slen);
+ memset(serial_str, 0, sizeof(serial_str));
+ if (irecv_linux_sysfs_serial(usb_handle, serial_str, sizeof(serial_str)) > 0) {
+ debug("recovered serial from kernel\n");
+ }
+ }
irecv_load_device_info_from_iboot_string(client, serial_str);
}
@@ -2857,10 +2975,23 @@ static void* _irecv_handle_device_add(void *userdata)
product_id = client->mode;
} else {
+ memset(serial_str, 0, sizeof(serial_str));
libusb_error = libusb_get_string_descriptor_ascii(usb_handle, devdesc.iSerialNumber, (unsigned char*)serial_str, sizeof(serial_str)-1);
- if (libusb_error < 0) {
- debug("%s: Failed to get string descriptor: %s\n", __func__, libusb_error_name(libusb_error));
- return 0;
+ if (libusb_error < 0 || strstr(serial_str, "CPID:") == NULL) {
+ /* The Apple T1/iBridge in recovery times out on this control
+ * transfer (LIBUSB_ERROR_TIMEOUT). Bailing out here means no
+ * IRECV_DEVICE_ADD is ever emitted and callers conclude that no
+ * device is attached. The kernel already enumerated the same
+ * string at attach, so fall back to its copy. */
+ debug("%s: string descriptor read failed (%s); trying kernel copy\n",
+ __func__, libusb_error_name(libusb_error));
+ memset(serial_str, 0, sizeof(serial_str));
+ if (irecv_linux_sysfs_serial(usb_handle, serial_str, sizeof(serial_str)) <= 0) {
+ debug("%s: no kernel copy either; giving up on this device\n", __func__);
+ libusb_close(usb_handle);
+ return 0;
+ }
+ debug("%s: recovered serial from kernel\n", __func__);
}
libusb_close(usb_handle);
}