Thread (28 messages) flat view 28 messages, 3 authors, 12d ago

Re: [PATCH v4 01/12] Input: xbox_gip - Add new driver for Xbox GIP

From: sashiko-bot@kernel.org
Date: 2026-08-22 02:38:44

Thank you for your contribution! Sashiko AI review found 17 potential issue(s) to consider:
- [High] Deadlock caused by calling `cancel_delayed_work_sync()` while holding a mutex required by the work function.
- [High] Lifecycle mismatch causing devres memory leak due to allocating against the parent USB device instead of the USB interface.
- [High] Active OUT URBs are freed without being killed, causing use-after-free and DMA corruption.
- [High] Lock inversion during devres cleanup triggers an AB-BA deadlock between `attachment->lock` and `cancel_work_sync`.
- [High] Buffer over-read in `gip_receive_fragment` for non-initial fragments due to missing bounds check.
- [High] Probe error path leaves an active IN URB running, leading to a Use-After-Free.
- [High] Dangling pointer left in `attachment->input` upon `input_register_device` failure.
- [High] Blind dereference of potential `ERR_PTR` returned from `gip_ensure_attachment`.
- [High] Undefined behavior from left shifting by 64 or more bits in `gip_decode_length`.
- [High] NULL pointer dereference when `in_fragment_data` allocation fails.
- [High] Missing NULL check after `devm_kmalloc` in `gip_send_raw_message`.
- [High] Use-After-Free due to LED/Battery callbacks operating on explicitly freed `attachment` structure.
- [High] Sleeping in atomic context by calling `usb_free_coherent` while holding a spinlock.
- [High] Memory leak of URBs and DMA buffers due to `return` combined with dead `goto` error handling.
- [High] Heap buffer overflow in `gip_receive_fragment` due to bypassed bounds check on exact offset match.
- [Medium] Bitwise OR logic typo in `gip_parse_preferred_types_metadata` ignores the high byte of the string length.
- [Medium] Memory leak of `attachment->uniq` caused by repeated `HELLO_DEVICE` messages.
--
--- Patch [1]: [PATCH v4 01/12] Input: xbox_gip - Add new driver for Xbox GIP ---
commit 8f3bdd937c628e28382af94b64282af26f6cbd22
Author: Vicki Pfau [off-list ref]
Subject: Input: xbox_gip - Add new driver for Xbox GIP

This commit introduces a new Gaming Input Protocol (GIP) driver for Xbox
One and Series controllers. It supports parsing message lengths, metadata,
and handles basic gamepad input functionality along with controller quirks.
quoted hunk ↗ jump to hunk
diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
--- /dev/null
+++ b/drivers/input/joystick/gip/gip-core.c
@@ -0,0 +1,2569 @@
+static int gip_decode_length(uint64_t *length, const uint8_t *bytes, int num_bytes)
+{
+	*length = 0;
+	int offset;
+
+	for (offset = 0; offset < num_bytes; offset++) {
+		uint8_t byte = bytes[offset];
+
+		*length |= (byte & 0x7full) << (offset * 7);
[Severity: High]
Does this loop need a bounds check to prevent shifting by 64 or more bits?

If a device sends 10 or more consecutive bytes with the 0x80 bit set,
offset will reach 10, causing the shift to become 70. This results in
undefined behavior in C.

[ ... ]
+static int gip_send_raw_message(struct gip_attachment *attachment,
+	uint8_t message_type, uint8_t flags, uint8_t seq, const uint8_t *bytes,
+	int num_bytes)
+{
[ ... ]
+		fragment->total_length = num_bytes;
+		fragment->data = devm_kmalloc(to_gip_device(attachment),
+			fragment->total_length, GFP_ATOMIC);
+		fragment->fragment_offset = 0;
+		memcpy(fragment->data, bytes, num_bytes);
[Severity: High]
Is there a missing NULL check for the devm_kmalloc() return value?

If the atomic memory allocation fails under memory pressure, the subsequent
memcpy() will result in a NULL pointer dereference.

[ ... ]
+static int gip_parse_preferred_types_metadata(struct device *dev,
+	struct gip_device_metadata *dev_metadata, const uint8_t *bytes,
+	int length, int buffer_offset)
+{
[ ... ]
+	buffer_offset++;
+	for (i = 0; i < dev_metadata->num_preferred_types; i++) {
+		if (buffer_offset + 2 >= length)
+			return -EINVAL;
+
+		count = bytes[buffer_offset];
+		count |= bytes[buffer_offset];
[Severity: Medium]
Is there a typo in the bitwise OR assignment for the count?

By OR-ing the exact same byte against itself, the high byte of the 16-bit
length is ignored (it looks like it should read buffer_offset + 1). This
could cause strings larger than 255 bytes to parse incorrectly as 0,
desynchronizing the buffer offset.

[ ... ]
+static bool gip_handle_command_hello_device(struct gip_attachment *attachment,
+	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
[ ... ]
+	attachment->firmware_major_version = le16_to_cpu(message->firmware_major_version);
+	attachment->firmware_minor_version = le16_to_cpu(message->firmware_minor_version);
+	attachment->vendor_id = le16_to_cpu(message->vendor_id);
+	attachment->product_id = le16_to_cpu(message->product_id);
+	attachment->uniq = devm_kasprintf(to_gip_device(attachment),
+		GFP_KERNEL, "%llx", le64_to_cpu(message->device_id));
[Severity: Medium]
Could this cause a memory leak if the device repeatedly sends HELLO_DEVICE
messages?

The attachment->uniq pointer is assigned a newly allocated string each time
this command is handled, but any previously allocated string is not freed
prior to being overwritten.

[ ... ]
+int gip_setup_input_device(struct gip_attachment *attachment)
+{
[ ... ]
+	rcu_assign_pointer(attachment->input, input);
+	rc = input_register_device(input);
+	if (rc)
+		goto err_free_device;
+
+	rc = gip_guide_led_probe(attachment, &input->dev);
[ ... ]
+err_free_device:
+	input_free_device(input);
+	return rc;
+}
[Severity: High]
Should attachment->input be cleared to NULL in the error path?

If input_register_device() fails, input_free_device() is called, but
attachment->input remains set. If an interrupt arrives afterwards,
gip_handle_ll_input_report() could dereference the freed dangling pointer.

[ ... ]
+static void gip_reset_metadata(struct gip_attachment *attachment)
+{
+	struct input_dev *input;
+
+	if (attachment->got_metadata != GIP_METADATA_GOT)
+		return;
+
+	gip_metadata_free(to_gip_device(attachment), &attachment->metadata);
+	rcu_read_lock();
+	input = rcu_dereference(attachment->input);
+	rcu_read_unlock();
+	if (input) {
+		rcu_assign_pointer(attachment->input, NULL);
+		synchronize_rcu();
+		input_unregister_device(input);
+	}
[Severity: High]
Could this lead to an AB-BA deadlock between attachment->lock and
the power supply's changed_work cancelation?

When a new message fragment is processed, gip_receive_message() holds
attachment->lock. It might call into gip_reset_metadata(), which triggers
input_unregister_device(). This synchronously releases devres resources,
unregistering the battery power supply and flushing its work queue via
cancel_work_sync().

If the power supply worker thread is simultaneously executing
gip_battery_get_property(), it will attempt to acquire attachment->lock,
creating a deadlock.

[ ... ]
+static int gip_receive_fragment(struct gip_attachment *attachment,
+	const struct gip_header *header, int offset,
+	uint64_t *fragment_offset, uint16_t *bytes_remaining, uint8_t *bytes,
+	int num_bytes)
+{
[ ... ]
+		attachment->in_fragment_data = devm_kmalloc(to_gip_device(attachment),
+			attachment->in_total_length, GFP_KERNEL);
+		if (!attachment->in_fragment_data)
+			return -ENOMEM;
[Severity: High]
Does this error path leave the attachment in an inconsistent state?

If the allocation fails, the function returns -ENOMEM but leaves
attachment->in_fragment_message set to a valid message type. A subsequent
non-initial fragment matching the type would pass validation and call
memcpy() into the NULL in_fragment_data pointer.

[ ... ]
+		if (*fragment_offset != attachment->in_fragment_offset) {
[ ... ]
+			if (header->length && *fragment_offset != attachment->in_total_length) {
+				gip_warn_ratelimited(attachment,
+					"Received out of sequence fragment: claimed %llu, expected %d\n",
+					*fragment_offset, attachment->in_fragment_offset);
+				gip_acknowledge(attachment, header,
+					attachment->in_fragment_offset,
+					attachment->in_total_length - attachment->in_fragment_offset);
+				return -EPROTO;
+			}
+			gip_dbg_ratelimited(attachment,
+				"Received erroneous final fragment offset, proceeding anyway\n");
+			*bytes_remaining = 0;
+		} else if (*fragment_offset + header->length > attachment->in_total_length) {
+			gip_warn(attachment,
+				"Received too long fragment, %llu exceeds %d\n",
+				*fragment_offset + header->length, attachment->in_total_length);
+			gip_fragment_failed(attachment, header);
+			return -EINVAL;
+		} else {
+			*bytes_remaining = attachment->in_total_length -
+				(*fragment_offset + header->length);
+		}
+		if (header->length != 0)
+			memcpy(&attachment->in_fragment_data[*fragment_offset],
+				&bytes[offset], header->length);
[Severity: High]
Can this logic result in a heap buffer overflow?

If a device provides *fragment_offset == attachment->in_total_length and
header->length > 0, the first if block is evaluated. Since it exits the
block without returning, it skips the else if bounds check entirely and
proceeds to memcpy(), writing header->length bytes past the end of the
heap allocation.

[Severity: High]
Is there a missing bounds check for non-initial fragments?

For initial fragments (GIP_FLAG_INIT_FRAG), the driver checks if
header->length > num_bytes - offset. However, for non-initial fragments,
this validation appears to be missing before calling memcpy(). This allows
the device to specify a header->length larger than the actual received
packet, reading adjacent kernel heap memory into in_fragment_data.

[ ... ]
+	cancel_delayed_work_sync(&attachment->in_fragment_timeout);
+	schedule_delayed_work(&attachment->in_fragment_timeout, HZ);
+
+	return rc;
+}
[Severity: High]
Can this lead to an AB-BA deadlock with the work function?

gip_receive_fragment() is called from gip_receive_message() while holding
attachment->lock. It then calls cancel_delayed_work_sync(). If the delayed
work gip_fragment_timeout() is currently executing, it will attempt to
acquire attachment->lock, deadlocking the system.

[ ... ]
+static int gip_receive_message(struct gip_device *device, uint8_t *bytes,
+	int num_bytes)
+{
[ ... ]
+	is_fragment = header.flags & GIP_FLAG_FRAGMENT;
+	attachment_index = header.flags & GIP_FLAG_ATTACHMENT_MASK;
+	attachment = gip_ensure_attachment(device, attachment_index);
+
+	print_hex_dump_debug(KBUILD_MODNAME ": Received message: ",
+		DUMP_PREFIX_OFFSET, 16, 1, bytes, num_bytes, false);
+
+	guard(mutex)(&attachment->lock);
[Severity: High]
Does gip_ensure_attachment() need to be checked for an ERR_PTR return?

If memory allocation within gip_ensure_attachment() fails, it returns
ERR_PTR(-ENOMEM). Blindly calling guard(mutex)(&attachment->lock) with
an ERR_PTR will trigger a kernel panic.

[ ... ]
+static int gip_init_output(struct gip_interface *intf,
+	struct usb_endpoint_descriptor *ep_out)
+{
[ ... ]
+		intf->out_queue[i].data = usb_alloc_coherent(udev, intf->mtu, GFP_KERNEL,
+			&intf->out_queue[i].urb->transfer_dma);
+
+		if (!intf->out_queue[i].data) {
+			return -ENOMEM;
+			goto err_free_urbs;
+		}
[Severity: High]
Does the early return cause a memory leak on the error path?

The code executes return -ENOMEM; immediately, bypassing the goto statement.
This bypasses the err_free_urbs cleanup logic, leaking all URBs and buffers
allocated in previous loop iterations.

[ ... ]
+static int gip_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
[ ... ]
+	gip = devm_kzalloc(&udev->dev, sizeof(*gip), GFP_KERNEL);
+	if (!gip)
+		return -ENOMEM;
+
+	gip->udev = udev;
+	gip->data.device = gip;
[Severity: High]
Are devres allocations incorrectly tied to the parent USB device?

The structure is allocated using devm_kzalloc(&udev->dev, ...). Since a
usb_driver is bound to the usb_interface, its devres allocations must be
tied to &intf->dev. Because they are tied to the parent device, unbinding
the driver via sysfs does not trigger devres cleanup, permanently leaking
memory until the physical device is unplugged.

[ ... ]
+	rc = gip_interface_init(&gip->data);
+	if (rc) {
+		devm_kfree(to_gip_device(gip), gip);
+		return rc;
+	}
+	/* Don't init audio interface -- we aren't using it yet */
+
+	usb_set_intfdata(intf, gip);
+
+	/* Pre-create the first attachment, as it should always exist */
+	attachment = gip_ensure_attachment(gip, 0);
+	if (IS_ERR(attachment))
+		return PTR_ERR(attachment);
+
+	return 0;
+}
[Severity: High]
Are we leaking the running URB if gip_ensure_attachment() fails?

The call to gip_interface_init() submits urb_in. If ensuring the first
attachment subsequently fails, the function returns without killing the
active URB. When the hardware interrupt fires, the USB core will attempt
to access the freed gip structure.

[ ... ]
+static int gip_shutdown(struct gip_device *device)
+{
[ ... ]
+	for (i = 0; i < MAX_ATTACHMENTS; i++) {
+		struct gip_attachment *attachment = device->attachments[i];
+		struct input_dev *input;
+
+		if (!attachment)
+			continue;
+
+		scoped_guard (mutex, &attachment->lock) {
+			cancel_delayed_work_sync(&attachment->metadata_next);
+			cancel_delayed_work_sync(&attachment->in_fragment_timeout);
[Severity: High]
Does this scoped_guard introduce a deadlock?

The scoped_guard acquires attachment->lock, and then calls
cancel_delayed_work_sync(). If the work functions (gip_retry_metadata or
gip_fragment_timeout) are running concurrently, they attempt to acquire
this same lock, causing an AB-BA deadlock during driver shutdown.

[ ... ]
+static void gip_disconnect(struct usb_interface *intf)
+{
[ ... ]
+	usb_kill_urb(gip->data.urb_in);
+	if (gip->audio.intf)
+		usb_kill_urb(gip->audio.urb_in);
+
+	gip_shutdown(gip);
+
+	spin_lock_irqsave(&gip->message_lock, flags);
+	gip_deinit_input(&gip->data);
+	gip_deinit_output(&gip->data);
[Severity: High]
Should the active OUT URBs be killed before de-initializing output?

Only the IN URBs are killed via usb_kill_urb() prior to cleanup.
gip_deinit_output() frees the URBs and their DMA buffers via
usb_free_coherent(). If the host controller is actively transmitting
these buffers, it will cause DMA corruption and crash the USB core.

[Severity: High]
Is it safe to call usb_free_coherent() while holding a spinlock?

gip_deinit_input() and gip_deinit_output() invoke usb_free_coherent()
to free DMA buffers. This function can involve TLB flushes and IOMMU
operations that may sleep. Calling it while holding gip->message_lock
and with interrupts disabled violates atomic context rules.

[ ... ]
+	for (i = 0; i < MAX_ATTACHMENTS; i++) {
+		struct gip_attachment *attachment = gip->attachments[i];
+
+		if (!attachment)
+			continue;
+
+		gip_reset_attachment(attachment);
+
+		devm_kfree(to_gip_device(attachment), attachment->uniq);
+		devm_kfree(to_gip_device(attachment), attachment);
+	}
+
+	devm_kfree(to_gip_device(gip), gip);
+}
[Severity: High]
Can explicit freeing of the attachment cause a Use-After-Free?

The attachment memory is explicitly freed here via devm_kfree(). However,
the LED and Battery devices were registered with &input->dev as their
devres parent. If userspace holds the input device open, devres cleanup
is delayed. A subsequent sysfs modification to the LED state will invoke
gip_guide_led_set(), which dereferences the freed attachment structure.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260822021140.1149546-1-vi@endrift.com?part=1
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help