From nobody Sat Oct 4 17:31:10 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 86C0B2E7BD3; Thu, 14 Aug 2025 09:13:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755162783; cv=none; b=fzO67sQf5NmwMIck7opRAoH2bIOkG0hQ9RskyY8NT4MBxc1mpt5P/awpjvcprUrUz3/JL+rATHaek8OWUscyEgHZ+Synp8MZy7hOdo23m+K4MBL0S1azfhvYh4PfGYyGC6a2M9+VVwtUbj9BDMOuYUj63cxNtuaYjSjgpApupaA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755162783; c=relaxed/simple; bh=Y30KNXbgJ12KC38CWWbM6Y8iAA7KjcgOZLUX49FTcvI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=a5X6w3zhWJLYrkmv8qGUbXXZE1XgIhVugw0GlE8TfMfGo2neYNOpCuaKbg0JKpNc0Nea+FYU6lyEfxZIon+sV/TbZwCFKswWiS9n8zfmBvzDeY6aeXw/rxe/6o5wb2D53e68GLV8u5gmvFsd4wLYRwP3rGv8M0QJi+dcf1x2cxQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ShgqzQ75; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ShgqzQ75" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 35CABC4CEF1; Thu, 14 Aug 2025 09:13:02 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1755162783; bh=Y30KNXbgJ12KC38CWWbM6Y8iAA7KjcgOZLUX49FTcvI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ShgqzQ75YETRAvwAr0f4KpCiv9jPSMK2UaxmT/2G5vo22PQLPjc3c3KtZyqy/O83Q WLNAV37JkCZeqY60EWSfjnrKjGFF54Wj47U6eFwQcIWJ6PvXI6Ymf2tbm+DsDie/mK sp+B5VB9PLLF9kbQaM8y9AoYc3J8LkC1QmLBHic8Z2QRhpQiYGt8F7tjU0R3pw+kXP nyXtNPbkKotmFSgZ2c03WKg4WhcJ/WTlp45TIs0taa1KS2n3SWXioEJiASodm4z/Jv Z15kPHLKMMjVf/uNAhBLJnBht+C4vNXbmC0VZZMf0IfoQeDPQuGyEZS0kkWq1q3boo 1DHzuKkQlm2dA== From: Tzung-Bi Shih To: bleung@chromium.org Cc: tzungbi@kernel.org, dawidn@google.com, chrome-platform@lists.linux.dev, akpm@linux-foundation.org, gregkh@linuxfoundation.org, linux-kernel@vger.kernel.org Subject: [PATCH 1/3] lib: Add ref_proxy module Date: Thu, 14 Aug 2025 09:10:18 +0000 Message-ID: <20250814091020.1302888-2-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0.rc1.163.g2494970778-goog In-Reply-To: <20250814091020.1302888-1-tzungbi@kernel.org> References: <20250814091020.1302888-1-tzungbi@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Some resources can be removed asynchronously, for example, resources provided by a hot-pluggable device like USB. When holding a reference to such a resource, it's possible for the resource to be removed and its memory freed, leading to use-after-free errors on subsequent access. Introduce the ref_proxy library to establish weak references to such resources. It allows a resource consumer to safely attempt to access a resource that might be freed at any time by the resource provider. The implementation uses a provider/consumer model built on Sleepable RCU (SRCU) to guarantee safe memory access: - A resource provider allocates a struct ref_proxy_provider and initializes it with a pointer to the resource. - A resource consumer that wants to access the resource allocates a struct ref_proxy handle which holds a reference to the provider. - To access the resource, the consumer uses ref_proxy_get(). This function enters an SRCU read-side critical section and returns the pointer to the resource. If the provider has already freed the resource, it returns NULL. After use, the consumer calls ref_proxy_put() to exit the SRCU critical section. The REF_PROXY_GET() is a convenient helper for doing that. - When the provider needs to remove the resource, it calls ref_proxy_provider_free(). This function sets the internal resource pointer to NULL and then calls synchronize_srcu() to wait for all current readers to finish before the resource can be completely torn down. Signed-off-by: Tzung-Bi Shih --- include/linux/ref_proxy.h | 37 ++++++++ lib/Kconfig | 3 + lib/Makefile | 1 + lib/ref_proxy.c | 184 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 225 insertions(+) create mode 100644 include/linux/ref_proxy.h create mode 100644 lib/ref_proxy.c diff --git a/include/linux/ref_proxy.h b/include/linux/ref_proxy.h new file mode 100644 index 000000000000..16ff29169272 --- /dev/null +++ b/include/linux/ref_proxy.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __LINUX_REF_PROXY_H +#define __LINUX_REF_PROXY_H + +#include + +struct device; +struct ref_proxy; +struct ref_proxy_provider; + +struct ref_proxy_provider *ref_proxy_provider_alloc(void *ref); +void ref_proxy_provider_free(struct ref_proxy_provider *rpp); +struct ref_proxy_provider *devm_ref_proxy_provider_alloc(struct device *de= v, + void *ref); + +struct ref_proxy *ref_proxy_alloc(struct ref_proxy_provider *rpp); +void ref_proxy_free(struct ref_proxy *proxy); +void __rcu *ref_proxy_get(struct ref_proxy *proxy); +void ref_proxy_put(struct ref_proxy *proxy); + +DEFINE_FREE(ref_proxy, struct ref_proxy *, if (_T) ref_proxy_put(_T)) + +#define _REF_PROXY_GET(_proxy, _name, _label, _ref) \ + for (struct ref_proxy *_name __free(ref_proxy) =3D _proxy; \ + (_ref =3D ref_proxy_get(_name)) || true; ({ goto _label; })) \ + if (0) { \ +_label: \ + break; \ + } else + +#define REF_PROXY_GET(_proxy, _ref) \ + _REF_PROXY_GET(_proxy, __UNIQUE_ID(proxy_name), \ + __UNIQUE_ID(label), _ref) + +#endif /* __LINUX_REF_PROXY_H */ + diff --git a/lib/Kconfig b/lib/Kconfig index c483951b624f..18237a766606 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -583,6 +583,9 @@ config STACKDEPOT_MAX_FRAMES default 64 depends on STACKDEPOT =20 +config REF_PROXY + bool + config REF_TRACKER bool depends on STACKTRACE_SUPPORT diff --git a/lib/Makefile b/lib/Makefile index 392ff808c9b9..e8ad6f67cee9 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -258,6 +258,7 @@ KASAN_SANITIZE_stackdepot.o :=3D n KMSAN_SANITIZE_stackdepot.o :=3D n KCOV_INSTRUMENT_stackdepot.o :=3D n =20 +obj-$(CONFIG_REF_PROXY) +=3D ref_proxy.o obj-$(CONFIG_REF_TRACKER) +=3D ref_tracker.o =20 libfdt_files =3D fdt.o fdt_ro.o fdt_wip.o fdt_rw.o fdt_sw.o fdt_strerror.o= \ diff --git a/lib/ref_proxy.c b/lib/ref_proxy.c new file mode 100644 index 000000000000..49940bea651c --- /dev/null +++ b/lib/ref_proxy.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include + +/** + * struct ref_proxy_provider - A handle for resource provider. + * @srcu: The SRCU to protect the resource. + * @ref: The pointer of resource. It can point to anything. + * @kref: The refcount for this handle. + */ +struct ref_proxy_provider { + struct srcu_struct srcu; + void __rcu *ref; + struct kref kref; +}; + +/** + * struct ref_proxy - A handle for resource consumer. + * @rpp: The pointer of resource provider. + * @idx: The index for the RCU critical section. + */ +struct ref_proxy { + struct ref_proxy_provider *rpp; + int idx; +}; + +/** + * ref_proxy_provider_alloc() - Allocate struct ref_proxy_provider. + * @ref: The pointer of resource. + * + * This holds an initial refcount to the struct. + * + * Return: The pointer of struct ref_proxy_provider. NULL on errors. + */ +struct ref_proxy_provider *ref_proxy_provider_alloc(void *ref) +{ + struct ref_proxy_provider *rpp; + + rpp =3D kzalloc(sizeof(*rpp), GFP_KERNEL); + if (!rpp) + return NULL; + + init_srcu_struct(&rpp->srcu); + rcu_assign_pointer(rpp->ref, ref); + synchronize_srcu(&rpp->srcu); + kref_init(&rpp->kref); + + return rpp; +} +EXPORT_SYMBOL(ref_proxy_provider_alloc); + +static void ref_proxy_provider_release(struct kref *kref) +{ + struct ref_proxy_provider *rpp =3D container_of(kref, + struct ref_proxy_provider, kref); + + cleanup_srcu_struct(&rpp->srcu); + kfree(rpp); +} + +/** + * ref_proxy_provider_free() - Free struct ref_proxy_provider. + * @rpp: The pointer of resource provider. + * + * This sets the resource `(struct ref_proxy_provider *)->ref` to NULL to + * indicate the resource has gone. + * + * This drops the refcount to the resource provider. If it is the final + * reference, ref_proxy_provider_release() will be called to free the stru= ct. + */ +void ref_proxy_provider_free(struct ref_proxy_provider *rpp) +{ + rcu_assign_pointer(rpp->ref, NULL); + synchronize_srcu(&rpp->srcu); + kref_put(&rpp->kref, ref_proxy_provider_release); +} +EXPORT_SYMBOL(ref_proxy_provider_free); + +static void devm_ref_proxy_provider_free(void *data) +{ + struct ref_proxy_provider *rpp =3D data; + + ref_proxy_provider_free(rpp); +} + +/** + * devm_ref_proxy_provider_alloc() - Dev-managed ref_proxy_provider_alloc(= ). + * @dev: The device. + * @ref: The pointer of resource. + * + * This holds an initial refcount to the struct. + * + * Return: The pointer of struct ref_proxy_provider. NULL on errors. + */ +struct ref_proxy_provider *devm_ref_proxy_provider_alloc(struct device *de= v, + void *ref) +{ + struct ref_proxy_provider *rpp; + + rpp =3D ref_proxy_provider_alloc(ref); + if (rpp) + if (devm_add_action_or_reset(dev, devm_ref_proxy_provider_free, + rpp)) + return NULL; + + return rpp; +} +EXPORT_SYMBOL(devm_ref_proxy_provider_alloc); + +/** + * ref_proxy_alloc() - Allocate struct ref_proxy_provider. + * @rpp: The pointer of resource provider. + * + * This holds a refcount to the resource provider. + * + * Return: The pointer of struct ref_proxy_provider. NULL on errors. + */ +struct ref_proxy *ref_proxy_alloc(struct ref_proxy_provider *rpp) +{ + struct ref_proxy *proxy; + + proxy =3D kzalloc(sizeof(*proxy), GFP_KERNEL); + if (!proxy) + return NULL; + + proxy->rpp =3D rpp; + kref_get(&rpp->kref); + + return proxy; +} +EXPORT_SYMBOL(ref_proxy_alloc); + +/** + * ref_proxy_free() - Free struct ref_proxy. + * @proxy: The pointer of struct ref_proxy. + * + * This drops a refcount to the resource provider. If it is the final + * reference, ref_proxy_provider_release() will be called to free the stru= ct. + */ +void ref_proxy_free(struct ref_proxy *proxy) +{ + struct ref_proxy_provider *rpp =3D proxy->rpp; + + kref_put(&rpp->kref, ref_proxy_provider_release); + kfree(proxy); +} +EXPORT_SYMBOL(ref_proxy_free); + +/** + * ref_proxy_get() - Get the resource. + * @proxy: The pointer of struct ref_proxy. + * + * This tries to de-reference to the resource and enters a RCU critical + * section. + * + * Return: The pointer to the resource. NULL if the resource has gone. + */ +void __rcu *ref_proxy_get(struct ref_proxy *proxy) +{ + struct ref_proxy_provider *rpp =3D proxy->rpp; + + proxy->idx =3D srcu_read_lock(&rpp->srcu); + return rcu_dereference(rpp->ref); +} +EXPORT_SYMBOL(ref_proxy_get); + +/** + * ref_proxy_put() - Put the resource. + * @proxy: The pointer of struct ref_proxy. + * + * Call this function to indicate the resource is no longer used. It exits + * the RCU critical section. + */ +void ref_proxy_put(struct ref_proxy *proxy) +{ + struct ref_proxy_provider *rpp =3D proxy->rpp; + + srcu_read_unlock(&rpp->srcu, proxy->idx); +} +EXPORT_SYMBOL(ref_proxy_put); --=20 2.51.0.rc1.163.g2494970778-goog From nobody Sat Oct 4 17:31:10 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5C8FA2EA14F; Thu, 14 Aug 2025 09:13:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755162785; cv=none; b=mGZlH9Kne9wfvTmGtW5pW0sEei81srwghvQUbM9jcMGM5aHH0zyihRA2TdqR6EqW0aV8tPtoPvpfjFOBYDQ9kR85l/WuzcnjEqYCjg8VBbYrK81CHil6Rcy5ggdiN0sPpSHUUSaLCM0Qn2ilCVIedpfECNsEzOxWpwB8TfwqtRw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755162785; c=relaxed/simple; bh=q9LD0gI3IQlorPkwhPUPSS6YvP6vOk6wY3/rSaYBFrk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fuELW8vQjHvOQlmIOfj0koZA/FmHrPyzV0MjynzSKc5yICrWGu+HIUisLgS+iRJ/jU9OXNoQ7k1AuHoZtKNI8Ri/mVFpkiv71iyPlPc5Huf27uIxMsGrX349zXmg2QHmrRkRHjBmP3XGVyWoF4dtBDq/NbXGIPgXnf0CTkq4liQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=G6OYOWcP; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="G6OYOWcP" Received: by smtp.kernel.org (Postfix) with ESMTPSA id B2B05C4CEEF; Thu, 14 Aug 2025 09:13:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1755162784; bh=q9LD0gI3IQlorPkwhPUPSS6YvP6vOk6wY3/rSaYBFrk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=G6OYOWcPmO8UYvNy7ppr9hGdmmNx6dNBeuB+AQcxn1Tw6e8AYDFm2UItO1/hHVG8c ierr71Gsc/9PGCQ6+SwvI+Bs/ceICdDsvPCoxvLQLiXEcVatbPKsoBgUk/UdwGwaUT D9B0LpbSH1BCi5b3VF4q6SpaK9Ee3tl2ca+JENSjs/wBzEkE848sYHm6FVxuJVk4WU HKqxt3QJ3ZxA3RVZoSDj6zbCZ2hq7JybFoS/vBxGalGiiJLf2xpDEP38l7gHMoRasM KfSnDvDNQDg8LZ3J0/8ZssduLXIKFIzBo80qskto6smgVUGzllOsbvBvBvKW6xpda6 keWpdM41dpALQ== From: Tzung-Bi Shih To: bleung@chromium.org Cc: tzungbi@kernel.org, dawidn@google.com, chrome-platform@lists.linux.dev, akpm@linux-foundation.org, gregkh@linuxfoundation.org, linux-kernel@vger.kernel.org Subject: [PATCH 2/3] platform/chrome: Protect cros_ec_device lifecycle with ref_proxy Date: Thu, 14 Aug 2025 09:10:19 +0000 Message-ID: <20250814091020.1302888-3-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0.rc1.163.g2494970778-goog In-Reply-To: <20250814091020.1302888-1-tzungbi@kernel.org> References: <20250814091020.1302888-1-tzungbi@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The cros_ec_device can be unregistered when the underlying device is removed. Other kernel drivers that interact with the EC may hold a pointer to the cros_ec_device, creating a risk of a use-after-free error if the EC device is removed while still being referenced. To prevent this, leverage the ref_proxy library and convert the underlying device drivers to resource providers of cros_ec_device. Signed-off-by: Tzung-Bi Shih --- drivers/platform/chrome/Kconfig | 8 ++++++-- drivers/platform/chrome/cros_ec_i2c.c | 5 +++++ drivers/platform/chrome/cros_ec_ishtp.c | 5 +++++ drivers/platform/chrome/cros_ec_lpc.c | 5 +++++ drivers/platform/chrome/cros_ec_rpmsg.c | 5 +++++ drivers/platform/chrome/cros_ec_spi.c | 4 ++++ drivers/platform/chrome/cros_ec_uart.c | 5 +++++ include/linux/platform_data/cros_ec_proto.h | 3 +++ 8 files changed, 38 insertions(+), 2 deletions(-) diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kcon= fig index 2281d6dacc9b..fe7b219093e9 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -88,7 +88,7 @@ config CROS_EC config CROS_EC_I2C tristate "ChromeOS Embedded Controller (I2C)" depends on CROS_EC && I2C - + select REF_PROXY help If you say Y here, you get support for talking to the ChromeOS EC through an I2C bus. This uses a simple byte-level protocol with @@ -98,6 +98,7 @@ config CROS_EC_I2C config CROS_EC_RPMSG tristate "ChromeOS Embedded Controller (rpmsg)" depends on CROS_EC && RPMSG && OF + select REF_PROXY help If you say Y here, you get support for talking to the ChromeOS EC through rpmsg. This uses a simple byte-level protocol with a @@ -111,6 +112,7 @@ config CROS_EC_ISHTP tristate "ChromeOS Embedded Controller (ISHTP)" depends on CROS_EC depends on INTEL_ISH_HID + select REF_PROXY help If you say Y here, you get support for talking to the ChromeOS EC firmware running on Intel Integrated Sensor Hub (ISH), using the @@ -123,7 +125,7 @@ config CROS_EC_ISHTP config CROS_EC_SPI tristate "ChromeOS Embedded Controller (SPI)" depends on CROS_EC && SPI - + select REF_PROXY help If you say Y here, you get support for talking to the ChromeOS EC through a SPI bus, using a byte-level protocol. Since the EC's @@ -133,6 +135,7 @@ config CROS_EC_SPI config CROS_EC_UART tristate "ChromeOS Embedded Controller (UART)" depends on CROS_EC && ACPI && SERIAL_DEV_BUS + select REF_PROXY help If you say Y here, you get support for talking to the ChromeOS EC through a UART, using a byte-level protocol. @@ -144,6 +147,7 @@ config CROS_EC_LPC tristate "ChromeOS Embedded Controller (LPC)" depends on CROS_EC && ACPI && (X86 || COMPILE_TEST) depends on HAS_IOPORT + select REF_PROXY help If you say Y here, you get support for talking to the ChromeOS EC over an LPC bus, including the LPC Microchip EC (MEC) variant. diff --git a/drivers/platform/chrome/cros_ec_i2c.c b/drivers/platform/chrom= e/cros_ec_i2c.c index 38af97cdaab2..d9ecf27585f1 100644 --- a/drivers/platform/chrome/cros_ec_i2c.c +++ b/drivers/platform/chrome/cros_ec_i2c.c @@ -12,6 +12,7 @@ #include #include #include +#include #include =20 #include "cros_ec.h" @@ -296,6 +297,10 @@ static int cros_ec_i2c_probe(struct i2c_client *client) if (!ec_dev) return -ENOMEM; =20 + ec_dev->ref_proxy_provider =3D devm_ref_proxy_provider_alloc(dev, ec_dev); + if (!ec_dev->ref_proxy_provider) + return -ENOMEM; + i2c_set_clientdata(client, ec_dev); ec_dev->dev =3D dev; ec_dev->priv =3D client; diff --git a/drivers/platform/chrome/cros_ec_ishtp.c b/drivers/platform/chr= ome/cros_ec_ishtp.c index 7e7190b30cbb..0b74a5b16b52 100644 --- a/drivers/platform/chrome/cros_ec_ishtp.c +++ b/drivers/platform/chrome/cros_ec_ishtp.c @@ -12,6 +12,7 @@ #include #include #include +#include #include =20 #include "cros_ec.h" @@ -547,6 +548,10 @@ static int cros_ec_dev_init(struct ishtp_cl_data *clie= nt_data) if (!ec_dev) return -ENOMEM; =20 + ec_dev->ref_proxy_provider =3D devm_ref_proxy_provider_alloc(dev, ec_dev); + if (!ec_dev->ref_proxy_provider) + return -ENOMEM; + client_data->ec_dev =3D ec_dev; dev->driver_data =3D ec_dev; =20 diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrom= e/cros_ec_lpc.c index 7d9a78289c96..8b5de1ad5f2f 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -23,6 +23,7 @@ #include #include #include +#include #include =20 #include "cros_ec.h" @@ -641,6 +642,10 @@ static int cros_ec_lpc_probe(struct platform_device *p= dev) if (!ec_dev) return -ENOMEM; =20 + ec_dev->ref_proxy_provider =3D devm_ref_proxy_provider_alloc(dev, ec_dev); + if (!ec_dev->ref_proxy_provider) + return -ENOMEM; + platform_set_drvdata(pdev, ec_dev); ec_dev->dev =3D dev; ec_dev->phys_name =3D dev_name(dev); diff --git a/drivers/platform/chrome/cros_ec_rpmsg.c b/drivers/platform/chr= ome/cros_ec_rpmsg.c index bc2666491db1..c9be9ba63ead 100644 --- a/drivers/platform/chrome/cros_ec_rpmsg.c +++ b/drivers/platform/chrome/cros_ec_rpmsg.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include =20 @@ -220,6 +221,10 @@ static int cros_ec_rpmsg_probe(struct rpmsg_device *rp= dev) if (!ec_dev) return -ENOMEM; =20 + ec_dev->ref_proxy_provider =3D devm_ref_proxy_provider_alloc(dev, ec_dev); + if (!ec_dev->ref_proxy_provider) + return -ENOMEM; + ec_rpmsg =3D devm_kzalloc(dev, sizeof(*ec_rpmsg), GFP_KERNEL); if (!ec_rpmsg) return -ENOMEM; diff --git a/drivers/platform/chrome/cros_ec_spi.c b/drivers/platform/chrom= e/cros_ec_spi.c index 8ca0f854e7ac..b0f33f02ec24 100644 --- a/drivers/platform/chrome/cros_ec_spi.c +++ b/drivers/platform/chrome/cros_ec_spi.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -752,6 +753,9 @@ static int cros_ec_spi_probe(struct spi_device *spi) ec_dev =3D devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL); if (!ec_dev) return -ENOMEM; + ec_dev->ref_proxy_provider =3D devm_ref_proxy_provider_alloc(dev, ec_dev); + if (!ec_dev->ref_proxy_provider) + return -ENOMEM; =20 /* Check for any DT properties */ cros_ec_spi_dt_probe(ec_spi, dev); diff --git a/drivers/platform/chrome/cros_ec_uart.c b/drivers/platform/chro= me/cros_ec_uart.c index 19c179d49c90..ce080b464977 100644 --- a/drivers/platform/chrome/cros_ec_uart.c +++ b/drivers/platform/chrome/cros_ec_uart.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -263,6 +264,10 @@ static int cros_ec_uart_probe(struct serdev_device *se= rdev) if (!ec_dev) return -ENOMEM; =20 + ec_dev->ref_proxy_provider =3D devm_ref_proxy_provider_alloc(dev, ec_dev); + if (!ec_dev->ref_proxy_provider) + return -ENOMEM; + serdev_device_set_drvdata(serdev, ec_dev); init_waitqueue_head(&ec_uart->response.wait_queue); =20 diff --git a/include/linux/platform_data/cros_ec_proto.h b/include/linux/pl= atform_data/cros_ec_proto.h index 3ec24f445c29..9f5c8fb353b6 100644 --- a/include/linux/platform_data/cros_ec_proto.h +++ b/include/linux/platform_data/cros_ec_proto.h @@ -158,6 +158,7 @@ struct cros_ec_command { * @pd: The platform_device used by the mfd driver to interface with the * PD behind an EC. * @panic_notifier: EC panic notifier. + * @ref_proxy_provider: The ref_proxy_provider to this device. */ struct cros_ec_device { /* These are used by other drivers that want to talk to the EC */ @@ -203,6 +204,8 @@ struct cros_ec_device { struct platform_device *pd; =20 struct blocking_notifier_head panic_notifier; + + struct ref_proxy_provider *ref_proxy_provider; }; =20 /** --=20 2.51.0.rc1.163.g2494970778-goog From nobody Sat Oct 4 17:31:10 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D1A512EF644; Thu, 14 Aug 2025 09:13:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755162787; cv=none; b=ZfJcmlsr3/31hnQmtk4wyUiAVEtYUOz/EflTnOztDtLMtTPMIGQXTyDYllZJzraddwF9Z4M7qY+NlkLct4MjfsUaRFiT0NULToTATDRO/Pg4BcqdGb6Y67UHiQBwennv9cHf9iuCCrkOvZwcARMHgxmyl5KmMPrizQkPKDWSeKE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755162787; c=relaxed/simple; bh=TBXf9TlITbl7kntH3GIMV20rolg0uvfkE9lfCdNPb+4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=eqLIUbsmW+987Gsy6BYAKbpJa9q4QTDNS4OEiDigTxZG6uDfYtSkrBv6LZAfv303J9lwBugSX3G22MwKATexp/ztHgufRqQD0mcW3Z5Wgk9oyZn0/r+/brYpvGv+xazUg/CYS7w5IKPsvmiR/dYdD2V4863OJZhmqAvfHYLwwKE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=VN23y8pb; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="VN23y8pb" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 3B21CC4CEFA; Thu, 14 Aug 2025 09:13:05 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1755162786; bh=TBXf9TlITbl7kntH3GIMV20rolg0uvfkE9lfCdNPb+4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=VN23y8pbqqlmt6tFYwlOJ1z1eGvrjZE/jLEjPRXQau6t4UzWNeTwseKrPdi72GaTX 9fBABkCYry94P3y5AQzqeFT+qDeooYGretDmUClN8UxQWfUvl0Ro805EM8EVMmQDwO 2jP9MH/QGK2bSdItJjtJbZjKvWIDnv4spp41+fUGFILXSYpagZMUt5yAvjbPrY1h3Z GlJa+IEzPPy5ekgvUYtzbBkCF8DkHvCPymkP0QUSKk091+4EplYAlQFati/EAIr9Ag 1Are5J8+c22UXAZO/rR5WJb42ps4Z6o/tyKW6EIxQQxFtxYulxAdYtBdd7zMbQ1rPA L1MKgI6WSDJ+w== From: Tzung-Bi Shih To: bleung@chromium.org Cc: tzungbi@kernel.org, dawidn@google.com, chrome-platform@lists.linux.dev, akpm@linux-foundation.org, gregkh@linuxfoundation.org, linux-kernel@vger.kernel.org Subject: [PATCH 3/3] platform/chrome: cros_ec_chardev: Consume cros_ec_device via ref_proxy Date: Thu, 14 Aug 2025 09:10:20 +0000 Message-ID: <20250814091020.1302888-4-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0.rc1.163.g2494970778-goog In-Reply-To: <20250814091020.1302888-1-tzungbi@kernel.org> References: <20250814091020.1302888-1-tzungbi@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The cros_ec_chardev driver provides a character device interface to the ChromeOS EC. A file handle to this device can remain open in userspace even if the underlying EC device is removed. This creates a classic use-after-free vulnerability. Any file operation (ioctl, release, etc.) on the open handle after the EC device has gone would access a stale pointer, leading to a system crash. To prevent this, leverage the ref_proxy library and convert cros_ec_chardev to a resource consumer of cros_ec_device. Signed-off-by: Tzung-Bi Shih --- drivers/platform/chrome/cros_ec_chardev.c | 125 +++++++++++++++------- 1 file changed, 85 insertions(+), 40 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_chardev.c b/drivers/platform/c= hrome/cros_ec_chardev.c index c9d80ad5b57e..f4aa70c8b6d4 100644 --- a/drivers/platform/chrome/cros_ec_chardev.c +++ b/drivers/platform/chrome/cros_ec_chardev.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -32,7 +33,7 @@ #define CROS_MAX_EVENT_LEN PAGE_SIZE =20 struct chardev_priv { - struct cros_ec_device *ec_dev; + struct ref_proxy *ec_dev_proxy; struct notifier_block notifier; wait_queue_head_t wait_event; unsigned long event_mask; @@ -55,6 +56,7 @@ static int ec_get_version(struct chardev_priv *priv, char= *str, int maxlen) }; struct ec_response_get_version *resp; struct cros_ec_command *msg; + struct cros_ec_device __rcu *ec_dev; int ret; =20 msg =3D kzalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL); @@ -64,12 +66,19 @@ static int ec_get_version(struct chardev_priv *priv, ch= ar *str, int maxlen) msg->command =3D EC_CMD_GET_VERSION + priv->cmd_offset; msg->insize =3D sizeof(*resp); =20 - ret =3D cros_ec_cmd_xfer_status(priv->ec_dev, msg); - if (ret < 0) { - snprintf(str, maxlen, - "Unknown EC version, returned error: %d\n", - msg->result); - goto exit; + REF_PROXY_GET(priv->ec_dev_proxy, ec_dev) { + if (!ec_dev) { + ret =3D -ENODEV; + goto exit; + } + + ret =3D cros_ec_cmd_xfer_status(ec_dev, msg); + if (ret < 0) { + snprintf(str, maxlen, + "Unknown EC version, returned error: %d\n", + msg->result); + goto exit; + } } =20 resp =3D (struct ec_response_get_version *)msg->data; @@ -92,22 +101,30 @@ static int cros_ec_chardev_mkbp_event(struct notifier_= block *nb, { struct chardev_priv *priv =3D container_of(nb, struct chardev_priv, notifier); - struct cros_ec_device *ec_dev =3D priv->ec_dev; + struct cros_ec_device __rcu *ec_dev; struct ec_event *event; - unsigned long event_bit =3D 1 << ec_dev->event_data.event_type; - int total_size =3D sizeof(*event) + ec_dev->event_size; + unsigned long event_bit; + int total_size; + + REF_PROXY_GET(priv->ec_dev_proxy, ec_dev) { + if (!ec_dev) + return NOTIFY_DONE; =20 - if (!(event_bit & priv->event_mask) || - (priv->event_len + total_size) > CROS_MAX_EVENT_LEN) - return NOTIFY_DONE; + event_bit =3D 1 << ec_dev->event_data.event_type; + total_size =3D sizeof(*event) + ec_dev->event_size; =20 - event =3D kzalloc(total_size, GFP_KERNEL); - if (!event) - return NOTIFY_DONE; + if (!(event_bit & priv->event_mask) || + (priv->event_len + total_size) > CROS_MAX_EVENT_LEN) + return NOTIFY_DONE; =20 - event->size =3D ec_dev->event_size; - event->event_type =3D ec_dev->event_data.event_type; - memcpy(event->data, &ec_dev->event_data.data, ec_dev->event_size); + event =3D kzalloc(total_size, GFP_KERNEL); + if (!event) + return NOTIFY_DONE; + + event->size =3D ec_dev->event_size; + event->event_type =3D ec_dev->event_data.event_type; + memcpy(event->data, &ec_dev->event_data.data, ec_dev->event_size); + } =20 spin_lock(&priv->wait_event.lock); list_add_tail(&event->node, &priv->events); @@ -166,7 +183,12 @@ static int cros_ec_chardev_open(struct inode *inode, s= truct file *filp) if (!priv) return -ENOMEM; =20 - priv->ec_dev =3D ec_dev; + priv->ec_dev_proxy =3D ref_proxy_alloc(ec_dev->ref_proxy_provider); + if (!priv->ec_dev_proxy) { + ret =3D -ENOMEM; + goto err; + } + priv->cmd_offset =3D ec->cmd_offset; filp->private_data =3D priv; INIT_LIST_HEAD(&priv->events); @@ -178,9 +200,14 @@ static int cros_ec_chardev_open(struct inode *inode, s= truct file *filp) &priv->notifier); if (ret) { dev_err(ec_dev->dev, "failed to register event notifier\n"); - kfree(priv); + goto err; } =20 + return 0; +err: + if (priv->ec_dev_proxy) + ref_proxy_free(priv->ec_dev_proxy); + kfree(priv); return ret; } =20 @@ -251,11 +278,16 @@ 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 =3D filp->private_data; - struct cros_ec_device *ec_dev =3D priv->ec_dev; + struct cros_ec_device __rcu *ec_dev; struct ec_event *event, *e; =20 - blocking_notifier_chain_unregister(&ec_dev->event_notifier, - &priv->notifier); + REF_PROXY_GET(priv->ec_dev_proxy, ec_dev) { + if (ec_dev) + blocking_notifier_chain_unregister( + &ec_dev->event_notifier, + &priv->notifier); + } + ref_proxy_free(priv->ec_dev_proxy); =20 list_for_each_entry_safe(event, e, &priv->events, node) { list_del(&event->node); @@ -273,6 +305,7 @@ static long cros_ec_chardev_ioctl_xcmd(struct chardev_p= riv *priv, void __user *a { struct cros_ec_command *s_cmd; struct cros_ec_command u_cmd; + struct cros_ec_device __rcu *ec_dev; long ret; =20 if (copy_from_user(&u_cmd, arg, sizeof(u_cmd))) @@ -299,10 +332,17 @@ static long cros_ec_chardev_ioctl_xcmd(struct chardev= _priv *priv, void __user *a } =20 s_cmd->command +=3D priv->cmd_offset; - ret =3D cros_ec_cmd_xfer(priv->ec_dev, s_cmd); - /* Only copy data to userland if data was received. */ - if (ret < 0) - goto exit; + REF_PROXY_GET(priv->ec_dev_proxy, ec_dev) { + if (!ec_dev) { + ret =3D -ENODEV; + goto exit; + } + + ret =3D cros_ec_cmd_xfer(ec_dev, s_cmd); + /* Only copy data to userland if data was received. */ + if (ret < 0) + goto exit; + } =20 if (copy_to_user(arg, s_cmd, sizeof(*s_cmd) + s_cmd->insize)) ret =3D -EFAULT; @@ -313,24 +353,29 @@ static long cros_ec_chardev_ioctl_xcmd(struct chardev= _priv *priv, void __user *a =20 static long cros_ec_chardev_ioctl_readmem(struct chardev_priv *priv, void = __user *arg) { - struct cros_ec_device *ec_dev =3D priv->ec_dev; + struct cros_ec_device __rcu *ec_dev; struct cros_ec_readmem s_mem =3D { }; long num; =20 - /* Not every platform supports direct reads */ - if (!ec_dev->cmd_readmem) - return -ENOTTY; + REF_PROXY_GET(priv->ec_dev_proxy, ec_dev) { + if (!ec_dev) + return -ENODEV; =20 - if (copy_from_user(&s_mem, arg, sizeof(s_mem))) - return -EFAULT; + /* Not every platform supports direct reads */ + if (!ec_dev->cmd_readmem) + return -ENOTTY; =20 - if (s_mem.bytes > sizeof(s_mem.buffer)) - return -EINVAL; + if (copy_from_user(&s_mem, arg, sizeof(s_mem))) + return -EFAULT; =20 - num =3D ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes, - s_mem.buffer); - if (num <=3D 0) - return num; + if (s_mem.bytes > sizeof(s_mem.buffer)) + return -EINVAL; + + num =3D ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes, + s_mem.buffer); + if (num <=3D 0) + return num; + } =20 if (copy_to_user((void __user *)arg, &s_mem, sizeof(s_mem))) return -EFAULT; --=20 2.51.0.rc1.163.g2494970778-goog