Linux Input/HID development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Shengzhuo Wei" <me@cherr.cc>
Cc: linux-input@vger.kernel.org, dmitry.torokhov@gmail.com
Subject: Re: [PATCH v2] HID: hid-oxp: fix UAF on pending work in remove()
Date: Tue, 04 Aug 2026 10:06:18 +0000	[thread overview]
Message-ID: <20260804100619.59A541F00A3D@smtp.kernel.org> (raw)
In-Reply-To: <20260804-oxp-fix-v2-1-b2d56e4c8a2c@cherr.cc>

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:

New issues:
- [High] Global workqueues permanently disabled on inert interface removal

Pre-existing issues:
- [High] Use-After-Free on devres-managed led_classdev in probe error path
- [High] Uninitialized work struct access during early raw events
- [High] State corruption and Use-After-Free due to static global drvdata
- [High] NULL Pointer Dereference in early raw event handling
- [Medium] Out-of-Bounds Read in raw event handler
--

--- Patch [1]: [PATCH v2] HID: hid-oxp: fix UAF on pending work in remove() ---
commit 3701229efb152cb11ba2c2b9567116728fd2754a
Author: Shengzhuo Wei <me@cherr.cc>

HID: hid-oxp: fix UAF on pending work in remove()

The patch updates the hid-oxp driver to use disable_delayed_work_sync()
instead of cancel_delayed_work() in the remove path to prevent workers from
firing after device teardown. It also delays the initialization and arming
of the oxp_mcu_init worker in probe to prevent it from pending during a probe
failure.

Link: https://lore.kernel.org/r/20260804-oxp-fix-v1-1-51a4fe787167@cherr.cc

> diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c
> --- a/drivers/hid/hid-oxp.c
> +++ b/drivers/hid/hid-oxp.c
> @@ -1501,14 +1501,14 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
>  	drvdata.gamepad_mode = OXP_GP_MODE_XINPUT;
>  	drvdata.rumble_intensity = 5;
>  
> -	INIT_DELAYED_WORK(&drvdata.oxp_mcu_init, oxp_mcu_init_fn);
> -	mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
> -
>  	ret = devm_device_add_group(&hdev->dev, &oxp_cfg_attrs_group);
>  	if (ret)
>  		return dev_err_probe(&hdev->dev, ret,
>  				     "Failed to attach configuration attributes\n");
>  
> +	INIT_DELAYED_WORK(&drvdata.oxp_mcu_init, oxp_mcu_init_fn);
> +	mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
> +

[Severity: High]
This is a pre-existing issue, but does moving the initialization to the end
of probe widen a race window with raw events?

Since hid_hw_start() and hid_hw_open() are called earlier in oxp_hid_probe(),
the hardware is active and raw events can arrive before probe completes. If a
device sends a status report early, oxp_hid_raw_event_gen_2() calls
mod_delayed_work() on drvdata.oxp_mcu_init.

Because the delayed work is now initialized at the very end of oxp_cfg_probe(),
could an early event queue an uninitialized work item with a null function
pointer, leading to a kernel panic when the timer fires?

[Severity: High]
This is a pre-existing issue, but does the probe error path leak the pending
work if we fail earlier?

If oxp_cfg_probe() fails after devm_led_classdev_multicolor_register(), for
example during devm_device_add_group(), it returns without canceling
drvdata.oxp_rgb_queue:

oxp_cfg_probe() {
    ...
    ret = devm_device_add_group(drvdata.led_mc->led_cdev.dev,
                                &oxp_rgb_attr_group);
    if (ret)
        return dev_err_probe(...);
}

Since the LED class device is briefly registered, userspace or udev can
trigger oxp_rgb_brightness_set(), queueing the delayed work. When probe
returns an error, devres frees the led_classdev. Could the pending work
execute and dereference the freed device pointer?

[Severity: High]
This is a pre-existing issue, but does the driver support multiple devices
safely?

The driver uses a single static global drvdata struct to store per-device
state:

static struct oxp_hid_cfg drvdata;

In oxp_cfg_probe(), this global state is overwritten for each probed device:

oxp_cfg_probe() {
    hid_set_drvdata(hdev, &drvdata);
    mutex_init(&drvdata.cfg_mutex);
    drvdata.hdev = hdev;
    ...
}

If multiple OneXPlayer devices are connected, the second call will overwrite
drvdata.hdev and reinitialize cfg_mutex, destroying the mutex state for the
first device. Could unplugging one device then leave dangling pointers and
corrupted work structures for the remaining device?

[Severity: High]
This is a pre-existing issue, but could early raw events trigger a null
pointer dereference?

Because hid_hw_start() and hid_hw_open() are called before
oxp_cfg_probe(), raw events can be routed to oxp_hid_raw_event_gen_2()
before drvdata.led_mc is initialized.

Since drvdata is zero-initialized, an early event matching
OXP_FID_GEN2_STATUS_EVENT and OXP_GET_PROPERTY would access
drvdata.led_mc:

oxp_hid_raw_event_gen_2() {
    ...
    led_mc->led_cdev.brightness = rgb_rep->brightness / 4 *
                                  led_mc->led_cdev.max_brightness;
}

Will this dereference a null pointer if probe hasn't completed?

[Severity: Medium]
This is a pre-existing issue, but does the raw event handler verify the
report size?

The HID core passes the actual report size to the raw event handler, but
oxp_hid_raw_event() formats OXP_PACKET_SIZE bytes:

oxp_hid_raw_event() {
    ...
    dev_dbg(&hdev->dev, "raw event data: [%*ph]\n", OXP_PACKET_SIZE, data);
}

oxp_hid_raw_event_gen_2() also accesses specific offsets like data[3] without
checking the boundaries.

If a malicious USB device sends a short HID report, could these handlers
read beyond the allocated buffer and leak uninitialized memory to dmesg?

>  	return 0;
>  }

[ ... ]

> @@ -1552,9 +1552,9 @@ static int oxp_hid_probe(struct hid_device *hdev,
>  
>  static void oxp_hid_remove(struct hid_device *hdev)
>  {
> -	cancel_delayed_work(&drvdata.oxp_rgb_queue);
> -	cancel_delayed_work(&drvdata.oxp_btn_queue);
> -	cancel_delayed_work(&drvdata.oxp_mcu_init);
> +	disable_delayed_work_sync(&drvdata.oxp_rgb_queue);
> +	disable_delayed_work_sync(&drvdata.oxp_btn_queue);
> +	disable_delayed_work_sync(&drvdata.oxp_mcu_init);

[Severity: High]
Do these calls to disable_delayed_work_sync() permanently break the workers?

Because the driver successfully binds to all interfaces of the USB device
and uses a single global drvdata structure, unbinding any inert interface
will trigger oxp_hid_remove().

Since disable_delayed_work_sync() is used, the works are disabled. However,
enable_delayed_work() is never called anywhere in the driver. Could
removing an inert interface permanently break RGB updates, button mappings,
and MCU initialization for the active gamepad interface until it is rebound?

>  	hid_hw_close(hdev);
>  	hid_hw_stop(hdev);
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260804-oxp-fix-v2-1-b2d56e4c8a2c@cherr.cc?part=1

  reply	other threads:[~2026-08-04 10:06 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-04  9:50 [PATCH v2] HID: hid-oxp: fix UAF on pending work in remove() Shengzhuo Wei
2026-08-04 10:06 ` sashiko-bot [this message]
2026-08-04 20:06 ` Shengzhuo Wei
2026-08-04 21:24   ` Derek John Clark
2026-08-05  4:53     ` Dmitry Torokhov
2026-08-05  8:01       ` Shengzhuo Wei

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=20260804100619.59A541F00A3D@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=dmitry.torokhov@gmail.com \
    --cc=linux-input@vger.kernel.org \
    --cc=me@cherr.cc \
    --cc=sashiko-reviews@lists.linux.dev \
    /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