Re: [PATCH] Input: evdev: drain queued events before reporting device removal
[email protected] Wed, 29 Jul 2026 09:39:08 +0000
| Newsgroups | org.kernel.vger.linux-input |
|---|---|
| Message-ID | <[email protected]> |
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: New issues: - [High] The patch breaks zero-length O_NONBLOCK reads by returning 0 instead of -EAGAIN when the queue is empty, altering userspace ABI. - [High] A TOCTOU race between lockless queue checks and device death drops synthetic key-up events, defeating the patch's purpose. Pre-existing issues: - [High] Uninitialized kernel stack memory is leaked to user-space due to missing padding initialization in `struct input_event` on architectures like SPARC64. -- --- Patch [1]: [PATCH] Input: evdev: drain queued events before reporting device removal --- commit 0c2e6f64ca8c023851919612ec0b8e230776b440 Author: zhangliuyang.zly <[email protected]> Subject: Input: evdev: drain queued events before reporting device removal This commit modifies the evdev read path to drain the per-client event queue before reporting -ENODEV when a device is removed. It aims to prevent dropping synthetic key-up events generated during device unregistration. > diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c > --- a/drivers/input/evdev.c > +++ b/drivers/input/evdev.c [ ... ] > @@ -565,20 +565,38 @@ static ssize_t evdev_read(struct file *file, char __user *buffer, > if (count != 0 && count < input_event_size()) > return -EINVAL; > > - for (;;) { > + /* > + * count == 0 is special - no IO is done but we still check for > + * error conditions, preserving the historical behavior. > + */ > + if (count == 0) { > + if (!evdev->exist || client->revoked) > + return -ENODEV; > + > + return 0; > + } [Severity: High] Does this change alter the historical behavior for non-blocking reads? Previously, if count == 0 and the file descriptor was set to O_NONBLOCK with an empty queue, the code would return -EAGAIN. By moving the count == 0 check above the loop and unconditionally returning 0, applications checking for queue status using a zero-length read might now incorrectly assume events are available. > > + for (;;) { > + /* > + * A revoked client must not consume any more events. > + */ > + if (client->revoked) > + return -ENODEV; > + > + /* > + * The input core may queue synthetic release events during > + * device unregister before evdev_disconnect() marks the evdev > + * node dead. Do not drop those already queued events by > + * returning -ENODEV too early. Drain the client queue first and > + * report -ENODEV only when there is nothing left to read. > + */ > + if (client->packet_head == client->tail) { > + if (!evdev->exist) > + return -ENODEV; [Severity: High] Might this lockless conditional block still drop synthetic key-up events due to a race condition? Because these checks are performed locklessly without memory barriers, a thread could evaluate packet_head == tail as true. If a context switch occurs and the input core queues synthetic events and subsequently marks evdev->exist as false, when this thread resumes it will evaluate !evdev->exist as true. This would result in returning -ENODEV and missing the newly queued synthetic events entirely, defeating the original purpose of the patch. > + > + if (file->f_flags & O_NONBLOCK) > + return -EAGAIN; > + } > > while (read + input_event_size() <= count && > evdev_fetch_next_event(client, &event)) { > > - /* > - * count == 0 is special - no IO is done but we check > - * for error conditions (see above). > - */ > - if (count == 0) > - break; > + if (input_event_to_user(buffer + read, &event)) > + return -EFAULT; [Severity: High] This is a pre-existing issue, but could this leak uninitialized kernel stack memory to userspace? In evdev_pass_values(), the input_event structure is allocated on the stack but its padding fields (such as the 32-bit padding on SPARC64) are not fully initialized before being copied into the client buffer. When evdev_read() fetches these events via evdev_fetch_next_event() and subsequently copies them to userspace using input_event_to_user(): evdev_pass_values() { struct input_event event; ... event.type = v->type; ... __pass_event(client, &event); } The uninitialized padding bytes are copied out here, potentially leaking sensitive kernel data. -- Sashiko AI review ยท https://sashiko.dev/#/patchset/[email protected]?part=1