Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: wyibo6109@gmail.com
Cc: linux-input@vger.kernel.org, robh@kernel.org,
	conor+dt@kernel.org, devicetree@vger.kernel.org,
	dmitry.torokhov@gmail.com
Subject: Re: [PATCH v2 2/2] Input: add OnePlus/OPPO Pad pogo-pin keyboard serdev driver
Date: Sat, 10 Oct 2026 14:48:26 +0000	[thread overview]
Message-ID: <sashiko-outbox-166671@kernel.org> (raw)
In-Reply-To: <20261010143836.173883-3-wyibo6109@gmail.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] input: UAF due to lockless quitting checks and incorrect work cancellation order
- [Medium] input: Data race on link state variables due to missing state_lock in RX path
- [Medium] input: Race condition and missing memory barriers in TX/RX completion logic
--

commit 6e3d28332505730268c90370cf0f07792b89214b
Author: Yibo Wang <wyibo6109@gmail.com>

Input: add OnePlus/OPPO Pad pogo-pin keyboard serdev driver

This adds a serdev driver for the Tinno keyboard MCU used by OnePlus/OPPO Pad
smart keyboards. It implements a framed protocol over a half-duplex UART for
input events, heartbeat, and backlight control.

> diff --git a/drivers/input/keyboard/oneplus-pogo-kbd.c b/drivers/input/keyboard/oneplus-pogo-kbd.c
> index 0000000000000..298f0eff52116
> --- /dev/null
> +++ b/drivers/input/keyboard/oneplus-pogo-kbd.c
[ ... ]
> +static int pogo_xfer(struct pogo_kbd *kb, u8 cmd, const u8 *payload, u8 len,
> +		     u8 *resp, unsigned int resp_size)
> +{
[ ... ]
> +	mutex_lock(&kb->io_lock);
> +	for (attempt = 0; attempt < POGO_XFER_RETRIES; attempt++) {
> +		bool echoed;
> +
> +		/*
> +		 * Drop any late echo/reply of the previous attempt: with
> +		 * tx_pending cleared the RX path ignores frames until the
> +		 * transfer below re-arms, so a delayed reply cannot complete
> +		 * the fresh iteration or race the resp buffer.
> +		 */
> +		WRITE_ONCE(kb->tx_pending, false);
> +		reinit_completion(&kb->echo_done);
> +		reinit_completion(&kb->resp_done);
> +		kb->tx_cmd = cmd;
> +		WRITE_ONCE(kb->tx_pending, true);

[Severity: Medium]
Since WRITE_ONCE does not emit a hardware memory barrier, could the CPU reorder
these stores on weakly-ordered architectures?

This might allow the RX thread to see tx_pending == true while still reading
the old tx_cmd, opening a race condition.

[ ... ]
> +static void pogo_handle_sync(struct pogo_kbd *kb, const u8 *p, unsigned int len)
> +{
> +	bool replug = false;
> +
> +	if (len < 2)
> +		return;
> +
> +	if (p[0] == OWB_SYNC_PLUG_IN && p[1] == 0x02 && len >= 9) {
> +		kb->brand = p[2];
> +		memcpy(kb->mac, &p[3], POGO_MAC_LEN);
> +		/*
> +		 * The MCU repeats this frame until the host has talked to it;
> +		 * only a plug-in frame after regular heartbeats is a re-plug.
> +		 */
> +		replug = kb->connected && kb->hb_seen;
> +	} else if (p[0] == OWB_SYNC_HEARTBEAT && p[1] == 0x02 && len >= 10) {
> +		kb->brand = p[3];
> +		memcpy(kb->mac, &p[4], POGO_MAC_LEN);
> +		kb->hb_seen = true;
> +	}
> +
> +	if (!kb->connected || replug) {
> +		kb->connected = true;
> +		kb->hb_seen = false;
> +		kb->retry_ms = POGO_RETRY_MIN_MS;

[Severity: Medium]
Are these link state variables modified without holding kb->state_lock here?

Because pogo_handle_sync() runs in the RX worker context, it can execute
concurrently with workqueues running pogo_power_off() or pogo_power_on().
This data race could corrupt the driver's state machine logic.

> +		if (!READ_ONCE(kb->quitting))
> +			schedule_work(&kb->plug_work);

[Severity: High]
Does this lockless quitting check allow a use-after-free?

If the RX thread evaluates !READ_ONCE(kb->quitting) as true but is preempted
before calling schedule_work(), pogo_remove() might finish cancelling plug_work
and return.

The RX thread would then resume and queue plug_work after it has been
cancelled, leading to a use-after-free when devres frees kb.

> +	}
> +}
> +
> +static void pogo_handle_frame(struct pogo_kbd *kb, const u8 *f, unsigned int n)
> +{
[ ... ]
> +	switch (cmd) {
> +	case OWB_CMD_KEYS:
[ ... ]
> +	case OWB_CMD_SYNC_UPLOAD:
> +		pogo_handle_sync(kb, payload, len);
> +		break;
> +	default:
> +		if (cmd == OWB_CMD_GENERAL_ACK && len >= 4 &&
> +		    payload[0] == OWB_GEN_BATTERY_STATUS)
> +			kb->battery_level = payload[2];
> +
> +		if (READ_ONCE(kb->tx_pending) && cmd == kb->tx_cmd + 1) {

[Severity: Medium]
Is there a missing acquire barrier (smp_load_acquire) when reading tx_pending
and tx_cmd here?

Furthermore, since the RX thread's check is not protected by a lock, it could
be preempted between evaluating tx_pending and reading tx_cmd, which might
result in erroneously completing a new transfer using an old response.

> +			/* reply layout for callers: cmd, len, payload */
> +			kb->resp_len = min_t(unsigned int, len + 2, sizeof(kb->resp));
> +			memcpy(kb->resp, &f[3], kb->resp_len);
> +			complete(&kb->resp_done);
> +		} else {
> +			dev_dbg(kb->dev, "unhandled frame cmd 0x%02x len %u\n",
> +				cmd, len);
> +		}
> +		break;
> +	}
> +}
[ ... ]
> +static void pogo_hb_work(struct work_struct *work)
> +{
[ ... ]
> +	/* re-arm: retry while the wake line still reports a keyboard */
> +	if (!READ_ONCE(kb->quitting))
> +		schedule_delayed_work(&kb->detect_work,
> +				      msecs_to_jiffies(kb->retry_ms));

[Severity: High]
Could hb_work queue detect_work after it has been cancelled?

Since pogo_remove() cancels detect_work before hb_work, if hb_work executes
concurrently with teardown, this lockless check allows schedule_delayed_work()
to run after cancel_delayed_work_sync(&kb->detect_work) has completed.

This leaves detect_work permanently queued, leading to another use-after-free
when kb is freed.

> +	kb->retry_ms = min(kb->retry_ms * 2, POGO_RETRY_MAX_MS);
> +}
[ ... ]
> +static void pogo_remove(struct serdev_device *serdev)
> +{
> +	struct pogo_kbd *kb = serdev_device_get_drvdata(serdev);
> +
> +	/*
> +	 * Stop the world first: with quitting set, the RX path and the
> +	 * works stop queueing each other, so the cancels below cannot be
> +	 * undone again.  The wake IRQ is disabled (and any in-flight ISR
> +	 * finished) before detect_work is cancelled.
> +	 */
> +	WRITE_ONCE(kb->quitting, true);
> +	pogo_wake_irq_enable(kb, false);
> +	cancel_delayed_work_sync(&kb->detect_work);
> +	cancel_delayed_work_sync(&kb->hb_work);
> +	cancel_work_sync(&kb->plug_work);

[Severity: High]
Does this cancellation order correctly prevent works from being queued?

As noted above, cancelling detect_work before hb_work permits hb_work to
re-queue detect_work during teardown.

Additionally, the RX path's lockless check against kb->quitting means plug_work
can be queued after cancel_work_sync() completes, due to delayed devres serdev
closure.

> +
> +	/* unregisters the input devices, the last possible led_work queuer */
> +	pogo_power_off(kb);
> +
> +	/* flush a led_work queued before the inputs went away */
> +	cancel_work_sync(&kb->led_work);
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261010143836.173883-1-wyibo6109@gmail.com?part=2

      reply	other threads:[~2026-10-10 14:48 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-09 12:20 [PATCH 0/2] Input: add support for OnePlus/OPPO Pad pogo-pin keyboards wyibo6109
2026-10-09 12:20 ` [PATCH 1/2] dt-bindings: input: add OnePlus pogo-pin keyboard binding wyibo6109
2026-10-09 12:28   ` sashiko-bot
2026-10-09 13:22   ` Krzysztof Kozlowski
2026-10-09 12:20 ` [PATCH 2/2] Input: add OnePlus/OPPO Pad pogo-pin keyboard serdev driver wyibo6109
2026-10-09 12:38   ` sashiko-bot
2026-10-10 14:38 ` [PATCH v2 0/2] Input: add support for OnePlus/OPPO Pad pogo-pin keyboards wyibo6109
2026-10-10 14:38   ` [PATCH v2 1/2] dt-bindings: input: add OnePlus pogo-pin keyboard wyibo6109
2026-10-10 14:47     ` sashiko-bot
2026-10-10 14:38   ` [PATCH v2 2/2] Input: add OnePlus/OPPO Pad pogo-pin keyboard serdev driver wyibo6109
2026-10-10 14:48     ` sashiko-bot [this message]

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=sashiko-outbox-166671@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dmitry.torokhov@gmail.com \
    --cc=linux-input@vger.kernel.org \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=wyibo6109@gmail.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