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
prev parent 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