From: Tzung-Bi Shih <tzungbi@kernel.org>
To: bleung@chromium.org
Cc: chrome-platform@lists.linux.dev, tzungbi@kernel.org,
dawidn@google.com, stable@vger.kernel.org
Subject: [PATCH 2/2] platform/chrome: cros_ec_chardev: Fix UAF of ec_dev
Date: Thu, 3 Jul 2025 11:35:09 +0000 [thread overview]
Message-ID: <20250703113509.2511758-3-tzungbi@kernel.org> (raw)
In-Reply-To: <20250703113509.2511758-1-tzungbi@kernel.org>
The lifecycle of misc device and `ec_dev` are independent. It is
possible that the `ec_dev` is used after free.
The following script shows the concept:
: import fcntl
: import os
: import struct
: import time
:
: EC_CMD_HELLO = 0x1
:
: fd = os.open('/dev/cros_fp', os.O_RDONLY)
: s = struct.pack('IIIIII', 0, EC_CMD_HELLO, 4, 4, 0, 0)
: fcntl.ioctl(fd, 0xc014ec00, s)
:
: time.sleep(1)
: open('/sys/bus/spi/drivers/cros-ec-spi/unbind', 'w').write('spi10.0')
: time.sleep(1)
: open('/sys/bus/spi/drivers/cros-ec-spi/bind', 'w').write('spi10.0')
:
: time.sleep(3)
: fcntl.ioctl(fd, 0xc014ec00, s) <--- The UAF happens here.
:
: os.close(fd)
Set `ec_dev` to NULL to let the misc device know the underlying
protocol device is gone.
Fixes: eda2e30c6684 ("mfd / platform: cros_ec: Miscellaneous character device to talk with the EC")
Cc: stable@vger.kernel.org
Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
drivers/platform/chrome/cros_ec_chardev.c | 65 +++++++++++++++++++----
1 file changed, 56 insertions(+), 9 deletions(-)
diff --git a/drivers/platform/chrome/cros_ec_chardev.c b/drivers/platform/chrome/cros_ec_chardev.c
index 5c858d30dd52..87c800c30f31 100644
--- a/drivers/platform/chrome/cros_ec_chardev.c
+++ b/drivers/platform/chrome/cros_ec_chardev.c
@@ -11,11 +11,14 @@
*/
#include <linux/init.h>
+#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/fs.h>
+#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/platform_data/cros_ec_chardev.h>
#include <linux/platform_data/cros_ec_commands.h>
@@ -31,7 +34,14 @@
/* Arbitrary bounded size for the event queue */
#define CROS_MAX_EVENT_LEN PAGE_SIZE
+/* This protects 'chardev_list' */
+static DEFINE_MUTEX(chardev_lock);
+static LIST_HEAD(chardev_list);
+
struct chardev_priv {
+ struct list_head list;
+ /* This protects 'ec_dev' */
+ struct mutex lock;
struct cros_ec_dev *ec_dev;
struct notifier_block notifier;
wait_queue_head_t wait_event;
@@ -176,9 +186,14 @@ static int cros_ec_chardev_open(struct inode *inode, struct file *filp)
if (ret) {
dev_err(ec_dev->dev, "failed to register event notifier\n");
kfree(priv);
+ return ret;
}
- return ret;
+ mutex_init(&priv->lock);
+ INIT_LIST_HEAD(&priv->list);
+ scoped_guard(mutex, &chardev_lock)
+ list_add_tail(&priv->list, &chardev_list);
+ return 0;
}
static __poll_t cros_ec_chardev_poll(struct file *filp, poll_table *wait)
@@ -199,7 +214,6 @@ static ssize_t cros_ec_chardev_read(struct file *filp, char __user *buffer,
char msg[sizeof(struct ec_response_get_version) +
sizeof(CROS_EC_DEV_VERSION)];
struct chardev_priv *priv = filp->private_data;
- struct cros_ec_dev *ec_dev = priv->ec_dev;
size_t count;
int ret;
@@ -233,7 +247,12 @@ static ssize_t cros_ec_chardev_read(struct file *filp, char __user *buffer,
if (*offset != 0)
return 0;
- ret = ec_get_version(ec_dev, msg, sizeof(msg));
+ scoped_guard(mutex, &priv->lock) {
+ if (!priv->ec_dev)
+ return -ENODEV;
+ }
+
+ ret = ec_get_version(priv->ec_dev, msg, sizeof(msg));
if (ret)
return ret;
@@ -249,11 +268,15 @@ static ssize_t cros_ec_chardev_read(struct file *filp, char __user *buffer,
static int cros_ec_chardev_release(struct inode *inode, struct file *filp)
{
struct chardev_priv *priv = filp->private_data;
- struct cros_ec_dev *ec_dev = priv->ec_dev;
struct ec_event *event, *e;
- blocking_notifier_chain_unregister(&ec_dev->ec_dev->event_notifier,
- &priv->notifier);
+ scoped_guard(mutex, &priv->lock) {
+ if (priv->ec_dev)
+ blocking_notifier_chain_unregister(&priv->ec_dev->ec_dev->event_notifier,
+ &priv->notifier);
+ }
+ scoped_guard(mutex, &chardev_lock)
+ list_del(&priv->list);
list_for_each_entry_safe(event, e, &priv->events, node) {
list_del(&event->node);
@@ -341,16 +364,20 @@ static long cros_ec_chardev_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct chardev_priv *priv = filp->private_data;
- struct cros_ec_dev *ec = priv->ec_dev;
if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC)
return -ENOTTY;
+ scoped_guard(mutex, &priv->lock) {
+ if (!priv->ec_dev)
+ return -ENODEV;
+ }
+
switch (cmd) {
case CROS_EC_DEV_IOCXCMD:
- return cros_ec_chardev_ioctl_xcmd(ec, (void __user *)arg);
+ return cros_ec_chardev_ioctl_xcmd(priv->ec_dev, (void __user *)arg);
case CROS_EC_DEV_IOCRDMEM:
- return cros_ec_chardev_ioctl_readmem(ec, (void __user *)arg);
+ return cros_ec_chardev_ioctl_readmem(priv->ec_dev, (void __user *)arg);
case CROS_EC_DEV_IOCEVENTMASK:
priv->event_mask = arg;
return 0;
@@ -394,8 +421,28 @@ static int cros_ec_chardev_probe(struct platform_device *pdev)
static void cros_ec_chardev_remove(struct platform_device *pdev)
{
struct miscdevice *misc = dev_get_drvdata(&pdev->dev);
+ struct chardev_priv *priv;
+ /*
+ * Must deregister the misc device first so that the following
+ * open fops get handled correctly.
+ *
+ * misc device is serialized by `misc_mtx`.
+ * 1) If misc_deregister() gets the lock earlier than misc_open(),
+ * the open fops won't be called as the corresponding misc
+ * device is already destroyed.
+ * 2) If misc_open() gets the lock earlier than misc_deregister(),
+ * the following code block resets the `ec_dev` to prevent
+ * the rest of fops from accessing the obsolete `ec_dev`.
+ */
misc_deregister(misc);
+
+ scoped_guard(mutex, &chardev_lock) {
+ list_for_each_entry(priv, &chardev_list, list) {
+ scoped_guard(mutex, &priv->lock)
+ priv->ec_dev = NULL;
+ }
+ }
}
static const struct platform_device_id cros_ec_chardev_id[] = {
--
2.50.0.727.gbf7dc18ff4-goog
next prev parent reply other threads:[~2025-07-03 11:35 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-07-03 11:35 [PATCH 0/2] platform/chrome: cros_ec_chardev: Fix a possible UAF Tzung-Bi Shih
2025-07-03 11:35 ` [PATCH 1/2] platform/chrome: cros_ec_chardev: Remove redundant struct field Tzung-Bi Shih
2025-07-03 11:35 ` Tzung-Bi Shih [this message]
2025-07-03 11:52 ` [PATCH 2/2] platform/chrome: cros_ec_chardev: Fix UAF of ec_dev Greg KH
2025-07-03 13:14 ` Tzung-Bi Shih
2025-07-03 13:52 ` Greg KH
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=20250703113509.2511758-3-tzungbi@kernel.org \
--to=tzungbi@kernel.org \
--cc=bleung@chromium.org \
--cc=chrome-platform@lists.linux.dev \
--cc=dawidn@google.com \
--cc=stable@vger.kernel.org \
/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