From: Greg KH <gregkh@linuxfoundation.org>
To: Tzung-Bi Shih <tzungbi@kernel.org>
Cc: bleung@chromium.org, chrome-platform@lists.linux.dev,
dawidn@google.com, stable@vger.kernel.org
Subject: Re: [PATCH 2/2] platform/chrome: cros_ec_chardev: Fix UAF of ec_dev
Date: Thu, 3 Jul 2025 13:52:02 +0200 [thread overview]
Message-ID: <2025070320-gathering-smitten-8909@gregkh> (raw)
In-Reply-To: <20250703113509.2511758-3-tzungbi@kernel.org>
On Thu, Jul 03, 2025 at 11:35:09AM +0000, Tzung-Bi Shih wrote:
> 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)
As you have to be root to do this, it's not that big of a deal :)
But yes, one that people have been talking about and discussing generic
ways of solving for years now, you have seen the plumbers talks about
it, right?
>
> 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);
Having a static list of chardevices feels very odd and driver-specific,
right
> +
> struct chardev_priv {
> + struct list_head list;
> + /* This protects 'ec_dev' */
> + struct mutex lock;
Protects it from what?
You now have two locks in play, one for the structure itself, and one
for the list. Yet how do they interact as the list is a list of the
objects which have their own lock?
Are you _SURE_ you need two locks here? If so, you need to document
these really well.
> 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;
> + }
What prevents ec_dev from changing now, after you have just checked it?
This feels very wrong as:
> +
> 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);
Look, it could have gone away here, right? If not, how?
> 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`.
What "following code block"? What will reset the structure?
totally confused,
greg k-h
next prev parent reply other threads:[~2025-07-03 11:52 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 ` [PATCH 2/2] platform/chrome: cros_ec_chardev: Fix UAF of ec_dev Tzung-Bi Shih
2025-07-03 11:52 ` Greg KH [this message]
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=2025070320-gathering-smitten-8909@gregkh \
--to=gregkh@linuxfoundation.org \
--cc=bleung@chromium.org \
--cc=chrome-platform@lists.linux.dev \
--cc=dawidn@google.com \
--cc=stable@vger.kernel.org \
--cc=tzungbi@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