Linux Input/HID development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Vicki Pfau" <vi@endrift.com>
Cc: dmitry.torokhov@gmail.com, linux-input@vger.kernel.org
Subject: Re: [PATCH v4 01/12] Input: xbox_gip - Add new driver for Xbox GIP
Date: Sat, 22 Aug 2026 02:38:43 +0000	[thread overview]
Message-ID: <20260822023843.F14931F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260822021140.1149546-2-vi@endrift.com>

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 <vi@endrift.com>
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.

> 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

  reply	other threads:[~2026-08-22  2:38 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-22  2:11 [PATCH v4 00/12] Input: xbox_gip - Add new driver for Xbox GIP Vicki Pfau
2026-08-22  2:11 ` [PATCH v4 01/12] " Vicki Pfau
2026-08-22  2:38   ` sashiko-bot [this message]
2026-08-22  2:11 ` [PATCH v4 02/12] Input: xpad - Remove Xbox One support Vicki Pfau
2026-08-22  2:41   ` sashiko-bot
2026-08-22  2:11 ` [PATCH v4 03/12] Input: xbox_gip - Add controllable LED support Vicki Pfau
2026-08-22  2:37   ` sashiko-bot
2026-08-22  2:11 ` [PATCH v4 04/12] Input: xbox_gip - Add HID relaying Vicki Pfau
2026-08-22  2:38   ` sashiko-bot
2026-08-22  2:11 ` [PATCH v4 05/12] Input: xbox_gip - Add battery support Vicki Pfau
2026-08-22  2:38   ` sashiko-bot
2026-08-22  2:11 ` [PATCH v4 06/12] Input: xbox_gip - Allow sending fragmented and ACME messages Vicki Pfau
2026-08-22  2:39   ` sashiko-bot
2026-08-22  2:11 ` [PATCH v4 07/12] Input: xbox_gip - Add security implementation from xone Vicki Pfau
2026-08-22  2:35   ` sashiko-bot
2026-08-24 21:49   ` Eric Biggers
2026-08-28  1:40     ` Vicki Pfau
2026-08-28  1:50       ` Eric Biggers
2026-08-28  1:51         ` Vicki Pfau
2026-08-22  2:11 ` [PATCH v4 08/12] Input: xbox_gip - Add arcade stick support Vicki Pfau
2026-08-22  2:38   ` sashiko-bot
2026-08-22  2:11 ` [PATCH v4 09/12] Input: xbox_gip - Add support for PDP guitar controllers Vicki Pfau
2026-08-22  2:30   ` sashiko-bot
2026-08-22  2:11 ` [PATCH v4 10/12] Input: Add ABS_CLUTCH, HANDBRAKE, and SHIFTER Vicki Pfau
2026-08-22  2:38   ` sashiko-bot
2026-08-22  2:11 ` [PATCH v4 11/12] HID: Map more automobile simulation inputs Vicki Pfau
2026-08-22  2:11 ` [PATCH v4 12/12] Input: xbox_gip - Add wheel support Vicki Pfau
2026-08-22  2:54   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260822023843.F14931F000E9@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=dmitry.torokhov@gmail.com \
    --cc=linux-input@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=vi@endrift.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox