public inbox for chrome-platform@lists.linux.dev
 help / color / mirror / Atom feed
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

  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