From nobody Sat Oct 4 04:57:28 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 456FB2D8DCF; Wed, 20 Aug 2025 08:17:27 +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=1755677847; cv=none; b=BOnJFteJYTZZ3nTVoih5KtriKjC4sjOKTGjMa7B0sPKghITJqpntsImT4gmeQFi1EbT1E5Bm0Il5+3brVg0SDoJy8Yc2s4wtwcFRE/kqAwxSrMDG0sSUj9APYuwZE5uRzK7DXYbNGZtoJj/Yt13nAIxmTUgkQeilOQH4eQpZfrY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755677847; c=relaxed/simple; bh=A63TpQVsA53+Mog3Nl5/jOyJ3YYDaPXMVaAOWjYsUes=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=txj+YRvI586sDTYK7HUtNOjWMB8GUphWSChrAzeI6PwTDFjLJdiDg8ykn2tYg4XfjL82G7jmh4VKixCJHDWOd0WquRE+jVAbdtpTTFVzZMeNRqSuA6YNNpTVUohYCjKdm2tixhxoI7yBJx/DSnYP80nh1aRV1cak6Svq1FZmZwQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=WLo5rgvK; 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="WLo5rgvK" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 10E15C4CEEB; Wed, 20 Aug 2025 08:17:24 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1755677846; bh=A63TpQVsA53+Mog3Nl5/jOyJ3YYDaPXMVaAOWjYsUes=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WLo5rgvK0W8JYX20bSJ9o0nnAqZOxoqfz0yXALY2XIodXz2/Rq1+ANJm6LG0lWvxQ 58NwfjJKkp2Mw3uC8MtnErDYQ0NHd2ED7faYNFwpaCf++CW4FLtsGjm0/bCM7f6kLa d7pQn1Lgkx6PhKJ7HNGUaADLpRacpKGIJYya7zCkJI3L+iVWsZBtcv8VmDbpE7N9kY PoTXhFQ/FV/THEzvBrvf0Olpd0r8R1z5EIau+ZsKeXXCE4Rou1Qh9E7wDg2NhNKFDc ZUSW+YoJtcNdyc0DGnuY4eAw9xTF4+2C9M7T7aXmxuMprbINOGtz6AZGRq/6NNEKa5 I2a3JFNcLk15A== From: Tzung-Bi Shih To: Benson Leung , Greg Kroah-Hartman , "Rafael J . Wysocki" , Danilo Krummrich Cc: Jonathan Corbet , Shuah Khan , Dawid Niedzwiecki , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, chrome-platform@lists.linux.dev, linux-kselftest@vger.kernel.org, tzungbi@kernel.org Subject: [PATCH v2 1/5] revocable: Revocable resource management Date: Wed, 20 Aug 2025 08:16:41 +0000 Message-ID: <20250820081645.847919-2-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0.rc1.167.g924127e9c0-goog In-Reply-To: <20250820081645.847919-1-tzungbi@kernel.org> References: <20250820081645.847919-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 revocable 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 revocable_provider and initializes it with a pointer to the resource. - A resource consumer that wants to access the resource allocates a struct revocable which holds a reference to the provider. - To access the resource, the consumer uses revocable_try_access(). 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 revocable_release() to exit the SRCU critical section. The REVOCABLE() is a convenient helper for doing that. - When the provider needs to remove the resource, it calls revocable_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 --- v2: - Rename "ref_proxy" -> "revocable". - Add introduction in kernel-doc format in revocable.c. - Add MAINTAINERS entry. - Add copyright. - Move from lib/ to drivers/base/. - EXPORT_SYMBOL() -> EXPORT_SYMBOL_GPL(). - Add Documentation/. - Rename _get() -> try_access(); _put() -> release(). - Fix a sparse warning by removing the redundant __rcu annotations. - Fix a sparse warning by adding __acquires() and __releases() annotations. v1: https://lore.kernel.org/chrome-platform/20250814091020.1302888-2-tzungb= i@kernel.org .../driver-api/driver-model/index.rst | 1 + .../driver-api/driver-model/revocable.rst | 151 ++++++++++++ MAINTAINERS | 7 + drivers/base/Makefile | 2 +- drivers/base/revocable.c | 229 ++++++++++++++++++ include/linux/revocable.h | 37 +++ 6 files changed, 426 insertions(+), 1 deletion(-) create mode 100644 Documentation/driver-api/driver-model/revocable.rst create mode 100644 drivers/base/revocable.c create mode 100644 include/linux/revocable.h diff --git a/Documentation/driver-api/driver-model/index.rst b/Documentatio= n/driver-api/driver-model/index.rst index 4831bdd92e5c..8e1ee21185df 100644 --- a/Documentation/driver-api/driver-model/index.rst +++ b/Documentation/driver-api/driver-model/index.rst @@ -14,6 +14,7 @@ Driver Model overview platform porting + revocable =20 .. only:: subproject and html =20 diff --git a/Documentation/driver-api/driver-model/revocable.rst b/Document= ation/driver-api/driver-model/revocable.rst new file mode 100644 index 000000000000..b9e2968ba9c1 --- /dev/null +++ b/Documentation/driver-api/driver-model/revocable.rst @@ -0,0 +1,151 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D +Revocable Resource Management +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D + +Overview +=3D=3D=3D=3D=3D=3D=3D=3D + +In a system with hot-pluggable devices, such as USB, resources provided by +these devices can be removed asynchronously. If a consumer holds a refere= nce +to such a resource, the resource might be deallocated while the reference = is +still held, leading to use-after-free errors upon subsequent access. + +The "revocable" mechanism addresses this by establishing a weak reference = to a +resource that might be freed at any time. It allows a resource consumer to +safely attempt to access the resource, guaranteeing that the access is val= id +for the duration of its use, or it fails safely if the resource has already +been revoked. + +The implementation is based on a provider/consumer model that uses Sleepab= le +RCU (SRCU) to ensure safe memory access without traditional locking. + +How It Works +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +1. **Provider**: A resource provider, such as a driver for a hot-pluggable + device, allocates a ``struct revocable_provider``. This structure is + initialized with a pointer to the actual resource it manages. + +2. **Consumer**: A consumer that needs to access the resource is given a + ``struct revocable``, which acts as a handle containing a reference to + the provider. + +3. **Accessing the Resource**: To access the resource, the consumer uses + ``revocable_try_access()``. This function enters an SRCU read-side + critical section and returns a pointer to the resource. If the provid= er + has already revoked the resource, this function returns ``NULL``. The + consumer must check for this ``NULL`` return. + +4. **Releasing the Resource**: After the consumer has finished using the + resource, it must call ``revocable_release()`` to exit the SRCU critic= al + section. This signals that the consumer no longer requires access. T= he + ``REVOCABLE()`` macro is provided as a convenient and safe way to mana= ge + the access-release cycle. + +5. **Revoking the Resource**: When the provider needs to remove the resou= rce + (e.g., the device is unplugged), it calls ``revocable_provider_free()`= `. + This function first sets the internal resource pointer to ``NULL``, + preventing any new consumers from accessing it. It then calls + ``synchronize_srcu()``, which waits for all existing consumers current= ly + in the SRCU critical section to finish their work. Once all consumers + have released their access, the resource can be safely deallocated. + +Revocable vs. Device-Managed (devm) Resources +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +It's important to understand the distinction between a standard +device-managed (devm) resource and a resource managed by a +``revocable_provider``. + +The key difference is their lifetime: + +* A **devm resource** is tied to the lifetime of the device. It is + automatically freed when the device is unbound. +* A **revocable_provider** persists as long as there are active referenc= es + to it from ``revocable`` consumer handles. + +This means that a ``revocable_provider`` can outlive the device that creat= ed +it. This is a deliberate design feature, allowing consumers to hold a +reference to a resource even after the underlying device has been removed, +without causing a fault. When the consumer attempts to access the resourc= e, +it will simply be informed that the resource is no longer available. + +API and Usage +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +For Resource Providers +---------------------- + +``struct revocable_provider *revocable_provider_alloc(void *res);`` + Allocates a provider handle for the given resource ``res``. It return= s a + pointer to the ``revocable_provider`` on success, or ``NULL`` on failu= re. + +``struct revocable_provider *devm_revocable_provider_alloc(struct device *= dev, void *res);`` + A device-managed version of ``revocable_provider_alloc``. It is + convenient to allocate providers via this function if the ``res`` is a= lso + tied to the lifetime of the ``dev``. ``revocable_provider_free`` will= be + called automatically when the device is unbound. + +``void revocable_provider_free(struct revocable_provider *rp);`` + Revokes the resource. This function marks the resource as unavailable= and + waits for all current consumers to finish before the underlying memory + can be freed. + +For Resource Consumers +---------------------- + +``struct revocable *revocable_alloc(struct revocable_provider *rp);`` + Allocates a consumer handle for a given provider ``rp``. + +``void revocable_free(struct revocable *rev);`` + Frees a consumer handle. + +``void *revocable_try_access(struct revocable *rev);`` + Attempts to gain access to the resource. Returns a pointer to the + resource on success or ``NULL`` if it has been revoked. + +``void revocable_release(struct revocable *rev);`` + Releases access to the resource, exiting the SRCU critical section. + +The ``REVOCABLE()`` Macro +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D + +The ``REVOCABLE()`` macro simplifies the access-release cycle for consumer= s, +ensuring that ``revocable_release()`` is always called, even in the case of +an early exit. + +``REVOCABLE(rev, res)`` + * ``rev``: The consumer's ``struct revocable *`` handle. + * ``res``: A pointer variable that will be assigned the resource. + +The macro creates a ``for`` loop that executes exactly once. Inside the l= oop, +``res`` is populated with the result of ``revocable_try_access()``. The +consumer code **must** check if ``res`` is ``NULL`` before using it. The +``revocable_release()`` function is automatically called when the scope of +the loop is exited. + +Example Usage +------------- + +.. code-block:: c + + void consumer_use_resource(struct revocable *rev) + { + struct foo_resource *res; + + REVOCABLE(rev, res) { + // Always check if the resource is valid. + if (!res) { + pr_warn("Resource is not available\n"); + return; + } + + // At this point, 'res' is guaranteed to be valid until + // this block exits. + do_something_with(res); + } + + // revocable_release() is automatically called here. + } diff --git a/MAINTAINERS b/MAINTAINERS index e913c1edd1fd..3bc39685bcf3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21663,6 +21663,13 @@ F: include/uapi/linux/rseq.h F: kernel/rseq.c F: tools/testing/selftests/rseq/ =20 +REVOCABLE RESOURCE MANAGEMENT +M: Tzung-Bi Shih +L: linux-kernel@vger.kernel.org +S: Maintained +F: drivers/base/revocable.c +F: include/linux/revocable.h + RFKILL M: Johannes Berg L: linux-wireless@vger.kernel.org diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 8074a10183dc..bdf854694e39 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -6,7 +6,7 @@ obj-y :=3D component.o core.o bus.o dd.o syscore.o \ cpu.o firmware.o init.o map.o devres.o \ attribute_container.o transport_class.o \ topology.o container.o property.o cacheinfo.o \ - swnode.o faux.o + swnode.o faux.o revocable.o obj-$(CONFIG_AUXILIARY_BUS) +=3D auxiliary.o obj-$(CONFIG_DEVTMPFS) +=3D devtmpfs.o obj-y +=3D power/ diff --git a/drivers/base/revocable.c b/drivers/base/revocable.c new file mode 100644 index 000000000000..80a48896b241 --- /dev/null +++ b/drivers/base/revocable.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 Google LLC + * + * Revocable resource management + */ + +#include +#include +#include +#include +#include + +/** + * DOC: Overview + * + * 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 revocable 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 revocable_provider and + * initializes it with a pointer to the resource. + * + * - A resource consumer that wants to access the resource allocates a + * struct revocable which holds a reference to the provider. + * + * - To access the resource, the consumer uses revocable_try_access(). + * 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 + * revocable_release() to exit the SRCU critical section. The + * REVOCABLE() is a convenient helper for doing that. + * + * - When the provider needs to remove the resource, it calls + * revocable_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. + */ + +/** + * struct revocable_provider - A handle for resource provider. + * @srcu: The SRCU to protect the resource. + * @res: The pointer of resource. It can point to anything. + * @kref: The refcount for this handle. + */ +struct revocable_provider { + struct srcu_struct srcu; + void __rcu *res; + struct kref kref; +}; + +/** + * struct revocable - A handle for resource consumer. + * @rp: The pointer of resource provider. + * @idx: The index for the RCU critical section. + */ +struct revocable { + struct revocable_provider *rp; + int idx; +}; + +/** + * revocable_provider_alloc() - Allocate struct revocable_provider. + * @res: The pointer of resource. + * + * This holds an initial refcount to the struct. + * + * Return: The pointer of struct revocable_provider. NULL on errors. + */ +struct revocable_provider *revocable_provider_alloc(void *res) +{ + struct revocable_provider *rp; + + rp =3D kzalloc(sizeof(*rp), GFP_KERNEL); + if (!rp) + return NULL; + + init_srcu_struct(&rp->srcu); + rcu_assign_pointer(rp->res, res); + synchronize_srcu(&rp->srcu); + kref_init(&rp->kref); + + return rp; +} +EXPORT_SYMBOL_GPL(revocable_provider_alloc); + +static void revocable_provider_release(struct kref *kref) +{ + struct revocable_provider *rp =3D container_of(kref, + struct revocable_provider, kref); + + cleanup_srcu_struct(&rp->srcu); + kfree(rp); +} + +/** + * revocable_provider_free() - Free struct revocable_provider. + * @rp: The pointer of resource provider. + * + * This sets the resource `(struct revocable_provider *)->res` to NULL to + * indicate the resource has gone. + * + * This drops the refcount to the resource provider. If it is the final + * reference, revocable_provider_release() will be called to free the stru= ct. + */ +void revocable_provider_free(struct revocable_provider *rp) +{ + rcu_assign_pointer(rp->res, NULL); + synchronize_srcu(&rp->srcu); + kref_put(&rp->kref, revocable_provider_release); +} +EXPORT_SYMBOL_GPL(revocable_provider_free); + +static void devm_revocable_provider_free(void *data) +{ + struct revocable_provider *rp =3D data; + + revocable_provider_free(rp); +} + +/** + * devm_revocable_provider_alloc() - Dev-managed revocable_provider_alloc(= ). + * @dev: The device. + * @res: The pointer of resource. + * + * It is convenient to allocate providers via this function if the @res is + * also tied to the lifetime of the @dev. revocable_provider_free() will + * be called automatically when the device is unbound. + * + * This holds an initial refcount to the struct. + * + * Return: The pointer of struct revocable_provider. NULL on errors. + */ +struct revocable_provider *devm_revocable_provider_alloc(struct device *de= v, + void *res) +{ + struct revocable_provider *rp; + + rp =3D revocable_provider_alloc(res); + if (!rp) + return NULL; + + if (devm_add_action_or_reset(dev, devm_revocable_provider_free, rp)) + return NULL; + + return rp; +} +EXPORT_SYMBOL_GPL(devm_revocable_provider_alloc); + +/** + * revocable_alloc() - Allocate struct revocable_provider. + * @rp: The pointer of resource provider. + * + * This holds a refcount to the resource provider. + * + * Return: The pointer of struct revocable_provider. NULL on errors. + */ +struct revocable *revocable_alloc(struct revocable_provider *rp) +{ + struct revocable *rev; + + rev =3D kzalloc(sizeof(*rev), GFP_KERNEL); + if (!rev) + return NULL; + + rev->rp =3D rp; + kref_get(&rp->kref); + + return rev; +} +EXPORT_SYMBOL_GPL(revocable_alloc); + +/** + * revocable_free() - Free struct revocable. + * @rev: The pointer of struct revocable. + * + * This drops a refcount to the resource provider. If it is the final + * reference, revocable_provider_release() will be called to free the stru= ct. + */ +void revocable_free(struct revocable *rev) +{ + struct revocable_provider *rp =3D rev->rp; + + kref_put(&rp->kref, revocable_provider_release); + kfree(rev); +} +EXPORT_SYMBOL_GPL(revocable_free); + +/** + * revocable_try_access() - Try to access the resource. + * @rev: The pointer of struct revocable. + * + * 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 *revocable_try_access(struct revocable *rev) __acquires(&rev->rp->src= u) +{ + struct revocable_provider *rp =3D rev->rp; + + rev->idx =3D srcu_read_lock(&rp->srcu); + return rcu_dereference(rp->res); +} +EXPORT_SYMBOL_GPL(revocable_try_access); + +/** + * revocable_release() - Stop accessing to the resource. + * @rev: The pointer of struct revocable. + * + * Call this function to indicate the resource is no longer used. It exits + * the RCU critical section. + */ +void revocable_release(struct revocable *rev) __releases(&rev->rp->srcu) +{ + struct revocable_provider *rp =3D rev->rp; + + srcu_read_unlock(&rp->srcu, rev->idx); +} +EXPORT_SYMBOL_GPL(revocable_release); diff --git a/include/linux/revocable.h b/include/linux/revocable.h new file mode 100644 index 000000000000..17d9b7ce633d --- /dev/null +++ b/include/linux/revocable.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2025 Google LLC + */ + +#ifndef __LINUX_REVOCABLE_H +#define __LINUX_REVOCABLE_H + +#include + +struct device; +struct revocable; +struct revocable_provider; + +struct revocable_provider *revocable_provider_alloc(void *res); +void revocable_provider_free(struct revocable_provider *rp); +struct revocable_provider *devm_revocable_provider_alloc(struct device *de= v, + void *res); + +struct revocable *revocable_alloc(struct revocable_provider *rp); +void revocable_free(struct revocable *rev); +void *revocable_try_access(struct revocable *rev) __acquires(&rev->rp->src= u); +void revocable_release(struct revocable *rev) __releases(&rev->rp->srcu); + +DEFINE_FREE(revocable, struct revocable *, if (_T) revocable_release(_T)) + +#define _REVOCABLE(_rev, _label, _res) \ + for (struct revocable *__UNIQUE_ID(name) __free(revocable) =3D _rev; \ + (_res =3D revocable_try_access(_rev)) || true; ({ goto _label; })) \ + if (0) { \ +_label: \ + break; \ + } else + +#define REVOCABLE(_rev, _res) _REVOCABLE(_rev, __UNIQUE_ID(label), _res) + +#endif /* __LINUX_REVOCABLE_H */ --=20 2.51.0.rc1.167.g924127e9c0-goog From nobody Sat Oct 4 04:57:28 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 2FC9B2D878C; Wed, 20 Aug 2025 08:17:29 +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=1755677849; cv=none; b=fgBIEtUsqsm3N7xKM5HUG5o5jJeSSv2k1hquUzlTB+aY4uRjxKD6i0PASu3bf+YqGfONB9wZgmbwrU/J/lO5dkRl5lBlOXCFk4/yuNRGvZOAhrxkKj9YmfgyfSO8rjNzd5v9gI9KhFJiV2S+UXoSEM7zyEpmS8g0I4LrnlKtNsU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755677849; c=relaxed/simple; bh=INTL2DYMj8vfz45oNiL6DY68pepe+FZq6NccY2sNnwo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=VYvC0FHxO5LNnSZMZNTh5l5TUyVinY1N+mWVRVDzaPLu5x69yOa8v/oKfgHKhlYmrc9SqKen75jnG2R0YuB2GsXJL2qVL4ulMKmeDBgdQwwV2fIUp3jAKcRZFA6cQ7PIVTUToqsTbkJ+YgJ4A0byl1f1vg6fu5KQK0tAGT4uy8A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=EHwb7R/C; 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="EHwb7R/C" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 3D567C113D0; Wed, 20 Aug 2025 08:17:27 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1755677849; bh=INTL2DYMj8vfz45oNiL6DY68pepe+FZq6NccY2sNnwo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=EHwb7R/C78IFf/xGINY1DlAo1OmTFYSSQmeaR9FhCjixHvl2qqSqxNex4SmZtzOm+ n8Ou07vYC3JAnHl+Li1qR3SbTk8yOIWHwKDs3dtPWSV0adDMRRLRXQgaP28JQrcBZ6 Y1S2uwnaHyupq0LznXME0/FFio+4lzDs4nn9t8NG+9H98yKr4CWEa3Hgz3UkLcSmlf tnvFz1y4xTYje0cs7oHVt9+Ml0LS5zDbbmGz4UCCdDsyeo9pmPWkGQuAVFSo7HoAGg No8rITnBF796+B5Ip7U2uLYySByG1N9vd6/ODSwvh8GH97HNpD7fe0SoArQdtJOMFN NC5ck5p7P/36Q== From: Tzung-Bi Shih To: Benson Leung , Greg Kroah-Hartman , "Rafael J . Wysocki" , Danilo Krummrich Cc: Jonathan Corbet , Shuah Khan , Dawid Niedzwiecki , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, chrome-platform@lists.linux.dev, linux-kselftest@vger.kernel.org, tzungbi@kernel.org Subject: [PATCH v2 2/5] revocable: Add Kunit test cases Date: Wed, 20 Aug 2025 08:16:42 +0000 Message-ID: <20250820081645.847919-3-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0.rc1.167.g924127e9c0-goog In-Reply-To: <20250820081645.847919-1-tzungbi@kernel.org> References: <20250820081645.847919-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" Add Kunit test cases for the revocable API. The test cases cover the following scenarios: - Basic: Verifies that a consumer can successfully access the resource provided via the provider. - Revocation: Verifies that after the provider revokes the resource, the consumer correctly receives a NULL pointer on a subsequent access. - Macro: Same as "Revocation" but uses the REVOCABLE(). A way to run the test: $ ./tools/testing/kunit/kunit.py run \ --kconfig_add CONFIG_REVOCABLE_KUNIT_TEST=3Dy \ revocable_test Signed-off-by: Tzung-Bi Shih --- v2: - New in the series. MAINTAINERS | 1 + drivers/base/Kconfig | 8 +++ drivers/base/Makefile | 3 + drivers/base/revocable_test.c | 110 ++++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 drivers/base/revocable_test.c diff --git a/MAINTAINERS b/MAINTAINERS index 3bc39685bcf3..e8a23ba2e2a4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21668,6 +21668,7 @@ M: Tzung-Bi Shih L: linux-kernel@vger.kernel.org S: Maintained F: drivers/base/revocable.c +F: drivers/base/revocable_test.c F: include/linux/revocable.h =20 RFKILL diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 064eb52ff7e2..a6488035f63d 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -244,3 +244,11 @@ config FW_DEVLINK_SYNC_STATE_TIMEOUT work on. =20 endmenu + +# Kunit test cases +config REVOCABLE_KUNIT_TEST + tristate "Kunit tests for revocable" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + Kunit tests for the revocable API. diff --git a/drivers/base/Makefile b/drivers/base/Makefile index bdf854694e39..4185aaa9bbb9 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -35,3 +35,6 @@ ccflags-$(CONFIG_DEBUG_DRIVER) :=3D -DDEBUG # define_trace.h needs to know how to find our header CFLAGS_trace.o :=3D -I$(src) obj-$(CONFIG_TRACING) +=3D trace.o + +# Kunit test cases +obj-$(CONFIG_REVOCABLE_KUNIT_TEST) +=3D revocable_test.o diff --git a/drivers/base/revocable_test.c b/drivers/base/revocable_test.c new file mode 100644 index 000000000000..d1ec7e47cd1d --- /dev/null +++ b/drivers/base/revocable_test.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 Google LLC + * + * Kunit tests for the revocable API. + * + * The test cases cover the following scenarios: + * + * - Basic: Verifies that a consumer can successfully access the resource + * provided via the provider. + * + * - Revocation: Verifies that after the provider revokes the resource, + * the consumer correctly receives a NULL pointer on a subsequent access. + * + * - Macro: Same as "Revocation" but uses the REVOCABLE(). + */ + +#include +#include + +static void revocable_test_basic(struct kunit *test) +{ + struct revocable_provider *rp; + struct revocable *rev; + void *real_res =3D (void *)0x12345678, *res; + + rp =3D revocable_provider_alloc(real_res); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); + + rev =3D revocable_alloc(rp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); + + res =3D revocable_try_access(rev); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + revocable_release(rev); + + revocable_free(rev); + revocable_provider_free(rp); +} + +static void revocable_test_revocation(struct kunit *test) +{ + struct revocable_provider *rp; + struct revocable *rev; + void *real_res =3D (void *)0x12345678, *res; + + rp =3D revocable_provider_alloc(real_res); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); + + rev =3D revocable_alloc(rp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); + + res =3D revocable_try_access(rev); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + revocable_release(rev); + + revocable_provider_free(rp); + + res =3D revocable_try_access(rev); + KUNIT_EXPECT_PTR_EQ(test, res, NULL); + revocable_release(rev); + + revocable_free(rev); +} + +static void revocable_test_macro(struct kunit *test) +{ + struct revocable_provider *rp; + struct revocable *rev; + void *real_res =3D (void *)0x12345678, *res; + bool accessed; + + rp =3D revocable_provider_alloc(real_res); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); + + rev =3D revocable_alloc(rp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); + + accessed =3D false; + REVOCABLE(rev, res) { + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + accessed =3D true; + } + KUNIT_EXPECT_TRUE(test, accessed); + + revocable_provider_free(rp); + + accessed =3D false; + REVOCABLE(rev, res) { + KUNIT_EXPECT_PTR_EQ(test, res, NULL); + accessed =3D true; + } + KUNIT_EXPECT_TRUE(test, accessed); + + revocable_free(rev); +} + +static struct kunit_case revocable_test_cases[] =3D { + KUNIT_CASE(revocable_test_basic), + KUNIT_CASE(revocable_test_revocation), + KUNIT_CASE(revocable_test_macro), + {} +}; + +static struct kunit_suite revocable_test_suite =3D { + .name =3D "revocable_test", + .test_cases =3D revocable_test_cases, +}; + +kunit_test_suite(revocable_test_suite); --=20 2.51.0.rc1.167.g924127e9c0-goog From nobody Sat Oct 4 04:57:28 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 B15A12D9EE0; Wed, 20 Aug 2025 08:17:31 +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=1755677852; cv=none; b=MCkiBxkXw+gBiihflk1Kiq+QmrrvTbMYvoTXuqCUkMaM2xqJb2Iw5IbXVzmLmQ1SNtBKEyIOG5l+u0WPRzzpSNhn/yRlGOP9+nZ0Bmw6KcbRqouHrWKFXe/NZ0G3eNbZ93SdWax70SgklVyMaO0Cw1ZlbMliinzB1BLDnUHr7Fs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755677852; c=relaxed/simple; bh=IB0+/yBqjOCPdI8llQO75+VQVQBNBishqAxFcST0tm0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=X2DQ26jVMFNuktfZbsJjBT0/kt9AR70W82VfKF4GR1NyzFqWJrsNf+DGp59c4UT/8tZSMXyRdmtOwB4G1upkhIJ7Q6n1KdKwqZQYacKZX+P9vxliYNBv+0NQ17jrh5PokufqhS39o+4fReN/WrFJG9oZhyh2QsaUybqHmBtos0k= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=IkYPR8Tg; 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="IkYPR8Tg" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 68FD9C116D0; Wed, 20 Aug 2025 08:17:29 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1755677851; bh=IB0+/yBqjOCPdI8llQO75+VQVQBNBishqAxFcST0tm0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IkYPR8Tg2ht+nPz2NMfjJJHJ5pmlzRl5f0Yge/06IMdM2EozFaW0TsZyluGNZVZ55 Dr4cbA4LE58h41GNRbn7dX+TpChlM/H75T1qnQbbjGWFa50CiMozPWcRTF5SScvg5q h2pOzPnmF5QRq7f2Ss9CJkRF1JoIfHVOu4ojpDvw5MFLpgik5lgHoyO6fKpv6jjNiy k8lMZD0GTgLNc5u2HExy7QJ+bLw3+s2RRz0mYlWlFUseJueXhFg000oLurgk3knuFa gVc2JqD+8xX4+cDz+2SccjCw2it3UnkRiUlooyv1cNNcPUHtdAfMi5abAi1KQGqhRO wMatndEN+mscw== From: Tzung-Bi Shih To: Benson Leung , Greg Kroah-Hartman , "Rafael J . Wysocki" , Danilo Krummrich Cc: Jonathan Corbet , Shuah Khan , Dawid Niedzwiecki , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, chrome-platform@lists.linux.dev, linux-kselftest@vger.kernel.org, tzungbi@kernel.org Subject: [PATCH v2 3/5] selftests: revocable: Add kselftest cases Date: Wed, 20 Aug 2025 08:16:43 +0000 Message-ID: <20250820081645.847919-4-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0.rc1.167.g924127e9c0-goog In-Reply-To: <20250820081645.847919-1-tzungbi@kernel.org> References: <20250820081645.847919-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" Add kselftest cases for the revocable API. The test consists of three parts: - A kernel module (revocable_test.ko) that creates a debugfs interface with `/provider` and `/consumer` files. - A user-space C program (revocable_test) that uses the kselftest harness to interact with the debugfs files. - An orchestrating shell script (test-revocable.sh) that loads the module, runs the C program, and unloads the module. The test cases cover the following scenarios: - Basic: Verifies that a consumer can successfully access the resource provided via the provider. - Revocation: Verifies that after the provider revokes the resource, the consumer correctly receives a NULL pointer on a subsequent access. - Macro: Same as "Revocation" but uses the REVOCABLE(). Signed-off-by: Tzung-Bi Shih --- v2: - New in the series. MAINTAINERS | 1 + tools/testing/selftests/Makefile | 1 + .../selftests/drivers/base/revocable/Makefile | 7 + .../drivers/base/revocable/revocable_test.c | 116 +++++++++++ .../drivers/base/revocable/test-revocable.sh | 39 ++++ .../base/revocable/test_modules/Makefile | 10 + .../revocable/test_modules/revocable_test.c | 188 ++++++++++++++++++ 7 files changed, 362 insertions(+) create mode 100644 tools/testing/selftests/drivers/base/revocable/Makefile create mode 100644 tools/testing/selftests/drivers/base/revocable/revocabl= e_test.c create mode 100755 tools/testing/selftests/drivers/base/revocable/test-rev= ocable.sh create mode 100644 tools/testing/selftests/drivers/base/revocable/test_mod= ules/Makefile create mode 100644 tools/testing/selftests/drivers/base/revocable/test_mod= ules/revocable_test.c diff --git a/MAINTAINERS b/MAINTAINERS index e8a23ba2e2a4..81281b828979 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21670,6 +21670,7 @@ S: Maintained F: drivers/base/revocable.c F: drivers/base/revocable_test.c F: include/linux/revocable.h +F: tools/testing/selftests/drivers/base/revocable/ =20 RFKILL M: Johannes Berg diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Mak= efile index babed7b1c2d1..efdc7bacab9e 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -17,6 +17,7 @@ TARGETS +=3D damon TARGETS +=3D devices/error_logs TARGETS +=3D devices/probe TARGETS +=3D dmabuf-heaps +TARGETS +=3D drivers/base/revocable TARGETS +=3D drivers/dma-buf TARGETS +=3D drivers/ntsync TARGETS +=3D drivers/s390x/uvdevice diff --git a/tools/testing/selftests/drivers/base/revocable/Makefile b/tool= s/testing/selftests/drivers/base/revocable/Makefile new file mode 100644 index 000000000000..afa5ca0fa452 --- /dev/null +++ b/tools/testing/selftests/drivers/base/revocable/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 + +TEST_GEN_MODS_DIR :=3D test_modules +TEST_GEN_PROGS_EXTENDED :=3D revocable_test +TEST_PROGS :=3D test-revocable.sh + +include ../../../lib.mk diff --git a/tools/testing/selftests/drivers/base/revocable/revocable_test.= c b/tools/testing/selftests/drivers/base/revocable/revocable_test.c new file mode 100644 index 000000000000..cdef49dc4095 --- /dev/null +++ b/tools/testing/selftests/drivers/base/revocable/revocable_test.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 Google LLC + * + * A selftest for the revocable API. + * + * The test cases cover the following scenarios: + * + * - Basic: Verifies that a consumer can successfully access the resource + * provided via the provider. + * + * - Revocation: Verifies that after the provider revokes the resource, + * the consumer correctly receives a NULL pointer on a subsequent access. + * + * - Macro: Same as "Revocation" but uses the REVOCABLE(). + */ + +#include +#include + +#include "../../../kselftest_harness.h" + +#define DEBUGFS_PATH "/sys/kernel/debug/revocable_test" +#define TEST_CMD_RESOURCE_GONE "resource_gone" +#define TEST_DATA "12345678" +#define TEST_MAGIC_OFFSET 0x1234 + +FIXTURE(revocable_fixture) { + int pfd; + int cfd; +}; + +FIXTURE_SETUP(revocable_fixture) { + int ret; + + self->pfd =3D open(DEBUGFS_PATH "/provider", O_WRONLY); + ASSERT_NE(-1, self->pfd) + TH_LOG("failed to open provider fd"); + + ret =3D write(self->pfd, TEST_DATA, strlen(TEST_DATA)); + ASSERT_NE(-1, ret) { + close(self->pfd); + TH_LOG("failed to write test data"); + } + + self->cfd =3D open(DEBUGFS_PATH "/consumer", O_RDONLY); + ASSERT_NE(-1, self->cfd) + TH_LOG("failed to open consumer fd"); +} + +FIXTURE_TEARDOWN(revocable_fixture) { + close(self->cfd); + close(self->pfd); +} + +/* + * ASSERT_* is only available in TEST or TEST_F block. Use + * macro for the helper. + */ +#define READ_TEST_DATA(_fd, _offset, _data, _msg) \ + do { \ + int ret; \ + \ + ret =3D lseek(_fd, _offset, SEEK_SET); \ + ASSERT_NE(-1, ret) \ + TH_LOG("failed to lseek"); \ + \ + ret =3D read(_fd, _data, sizeof(_data) - 1); \ + ASSERT_NE(-1, ret) \ + TH_LOG(_msg); \ + data[ret] =3D '\0'; \ + } while (0) + +TEST_F(revocable_fixture, basic) { + char data[16]; + + READ_TEST_DATA(self->cfd, 0, data, "failed to read test data"); + EXPECT_STREQ(TEST_DATA, data); +} + +TEST_F(revocable_fixture, revocation) { + char data[16]; + int ret; + + READ_TEST_DATA(self->cfd, 0, data, "failed to read test data"); + EXPECT_STREQ(TEST_DATA, data); + + ret =3D write(self->pfd, TEST_CMD_RESOURCE_GONE, + strlen(TEST_CMD_RESOURCE_GONE)); + ASSERT_NE(-1, ret) + TH_LOG("failed to write resource gone cmd"); + + READ_TEST_DATA(self->cfd, 0, data, + "failed to read test data after resource gone"); + EXPECT_STREQ("(null)", data); +} + +TEST_F(revocable_fixture, macro) { + char data[16]; + int ret; + + READ_TEST_DATA(self->cfd, TEST_MAGIC_OFFSET, data, + "failed to read test data"); + EXPECT_STREQ(TEST_DATA, data); + + ret =3D write(self->pfd, TEST_CMD_RESOURCE_GONE, + strlen(TEST_CMD_RESOURCE_GONE)); + ASSERT_NE(-1, ret) + TH_LOG("failed to write resource gone cmd"); + + READ_TEST_DATA(self->cfd, TEST_MAGIC_OFFSET, data, + "failed to read test data after resource gone"); + EXPECT_STREQ("(null)", data); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/drivers/base/revocable/test-revocable.= sh b/tools/testing/selftests/drivers/base/revocable/test-revocable.sh new file mode 100755 index 000000000000..3a34be28001a --- /dev/null +++ b/tools/testing/selftests/drivers/base/revocable/test-revocable.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +mod_name=3D"revocable_test" +ksft_fail=3D1 +ksft_skip=3D4 + +if [ "$(id -u)" -ne 0 ]; then + echo "$0: Must be run as root" + exit "$ksft_skip" +fi + +if ! which insmod > /dev/null 2>&1; then + echo "$0: Need insmod" + exit "$ksft_skip" +fi + +if ! which rmmod > /dev/null 2>&1; then + echo "$0: Need rmmod" + exit "$ksft_skip" +fi + +insmod test_modules/"${mod_name}".ko + +if [ ! -d /sys/kernel/debug/revocable_test/ ]; then + mount -t debugfs none /sys/kernel/debug/ + + if [ ! -d /sys/kernel/debug/revocable_test/ ]; then + echo "$0: Error mounting debugfs" + exit "$ksft_fail" + fi +fi + +./revocable_test +ret=3D$? + +rmmod "${mod_name}" + +exit "${ret}" diff --git a/tools/testing/selftests/drivers/base/revocable/test_modules/Ma= kefile b/tools/testing/selftests/drivers/base/revocable/test_modules/Makefi= le new file mode 100644 index 000000000000..f29e4f909402 --- /dev/null +++ b/tools/testing/selftests/drivers/base/revocable/test_modules/Makefile @@ -0,0 +1,10 @@ +TESTMODS_DIR :=3D $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))= )) +KDIR ?=3D /lib/modules/$(shell uname -r)/build + +obj-m +=3D revocable_test.o + +all: + $(Q)$(MAKE) -C $(KDIR) M=3D$(TESTMODS_DIR) + +clean: + $(Q)$(MAKE) -C $(KDIR) M=3D$(TESTMODS_DIR) clean diff --git a/tools/testing/selftests/drivers/base/revocable/test_modules/re= vocable_test.c b/tools/testing/selftests/drivers/base/revocable/test_module= s/revocable_test.c new file mode 100644 index 000000000000..9166b860a55a --- /dev/null +++ b/tools/testing/selftests/drivers/base/revocable/test_modules/revocable= _test.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 Google LLC + * + * A kernel module for testing the revocable API. + */ + +#include +#include +#include +#include + +#define TEST_CMD_RESOURCE_GONE "resource_gone" +#define TEST_MAGIC_OFFSET 0x1234 + +static struct dentry *debugfs_dir; + +struct revocable_test_provider_priv { + struct revocable_provider *rp; + struct dentry *dentry; + char res[16]; +}; + +static int revocable_test_consumer_open(struct inode *inode, struct file *= filp) +{ + struct revocable *rev; + struct revocable_provider *rp =3D inode->i_private; + + rev =3D revocable_alloc(rp); + if (!rev) + return -ENOMEM; + filp->private_data =3D rev; + + return 0; +} + +static int revocable_test_consumer_release(struct inode *inode, + struct file *filp) +{ + struct revocable *rev =3D filp->private_data; + + revocable_free(rev); + return 0; +} + +static ssize_t revocable_test_consumer_read(struct file *filp, + char __user *buf, + size_t count, loff_t *offset) +{ + char *res; + char data[16]; + size_t len; + struct revocable *rev =3D filp->private_data; + + switch (*offset) { + case 0: + res =3D revocable_try_access(rev); + snprintf(data, sizeof(data), "%s", res ?: "(null)"); + revocable_release(rev); + break; + case TEST_MAGIC_OFFSET: + REVOCABLE(rev, res) + snprintf(data, sizeof(data), "%s", res ?: "(null)"); + break; + default: + return 0; + } + + len =3D min_t(size_t, strlen(data), count); + if (copy_to_user(buf, data, len)) + return -EFAULT; + + *offset =3D len; + return len; +} + +static const struct file_operations revocable_test_consumer_fops =3D { + .open =3D revocable_test_consumer_open, + .release =3D revocable_test_consumer_release, + .read =3D revocable_test_consumer_read, + .llseek =3D default_llseek, +}; + +static int revocable_test_provider_open(struct inode *inode, struct file *= filp) +{ + struct revocable_test_provider_priv *priv; + + priv =3D kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + filp->private_data =3D priv; + + return 0; +} + +static int revocable_test_provider_release(struct inode *inode, + struct file *filp) +{ + struct revocable_test_provider_priv *priv =3D filp->private_data; + + debugfs_remove(priv->dentry); + if (priv->rp) + revocable_provider_free(priv->rp); + kfree(priv); + + return 0; +} + +static ssize_t revocable_test_provider_write(struct file *filp, + const char __user *buf, + size_t count, loff_t *offset) +{ + size_t copied; + char data[64]; + struct revocable_test_provider_priv *priv =3D filp->private_data; + + copied =3D strncpy_from_user(data, buf, sizeof(data)); + if (copied < 0) + return copied; + if (copied =3D=3D sizeof(data)) + data[sizeof(data) - 1] =3D '\0'; + + /* + * Note: The test can't just close the FD for signaling the + * resource gone. Subsequent file operations on the opening + * FD of debugfs return -EIO after calling debugfs_remove(). + * See also debugfs_file_get(). + * + * Here is a side command channel for signaling the resource + * gone. + */ + if (!strcmp(data, TEST_CMD_RESOURCE_GONE)) { + revocable_provider_free(priv->rp); + priv->rp =3D NULL; + } else { + if (priv->res[0] !=3D '\0') + return 0; + + strscpy(priv->res, data); + + priv->rp =3D revocable_provider_alloc(&priv->res); + if (!priv->rp) + return -ENOMEM; + + priv->dentry =3D debugfs_create_file("consumer", 0400, + debugfs_dir, priv->rp, + &revocable_test_consumer_fops); + if (!priv->dentry) { + revocable_provider_free(priv->rp); + return -ENOMEM; + } + } + + return copied; +} + +static const struct file_operations revocable_test_provider_fops =3D { + .open =3D revocable_test_provider_open, + .release =3D revocable_test_provider_release, + .write =3D revocable_test_provider_write, +}; + +static int __init revocable_test_init(void) +{ + debugfs_dir =3D debugfs_create_dir("revocable_test", NULL); + if (!debugfs_dir) + return -ENOMEM; + + if (!debugfs_create_file("provider", 0200, debugfs_dir, NULL, + &revocable_test_provider_fops)) { + debugfs_remove_recursive(debugfs_dir); + return -ENOMEM; + } + + return 0; +} + +static void __exit revocable_test_exit(void) +{ + debugfs_remove_recursive(debugfs_dir); +} + +module_init(revocable_test_init); +module_exit(revocable_test_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tzung-Bi Shih "); +MODULE_DESCRIPTION("Revocable Kselftest"); --=20 2.51.0.rc1.167.g924127e9c0-goog From nobody Sat Oct 4 04:57:28 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 8AE922D9EF8; Wed, 20 Aug 2025 08:17:33 +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=1755677853; cv=none; b=cfLW3hq307VGw2whNG8Q3c38I5A40FGDPJzPfUJx4toic2Tj/za/bQ2DYWpLQYD90DB4GKO2+d6xr5oqs26tyYyojNv6LTZ959cQXqXIAU1r+8e7SHiDBTLe0uP0jk4ZLx4WOncKMHPk7aDf8iHlIh9Y12dEwIpj0tyde1E/zX0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755677853; c=relaxed/simple; bh=7fsMezvoDQEYp2ae8oSLemUvrmjXu6gJVUF5jYa8vMs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JPmMrVc9tGrigMVt0OHmxZMtz12zHdI+2+srw9jSXvNbr39CoWUsM5iBtn6mrjWNeNG8FYnXI9dgBKvnPtukb9ZiwrglAXg0Ec2UtltaIOTYqTVjU/ebyyE9t5VIL8Lxd7sDBIAI8NiJrAGFqe3x7ItgPU13LRB8mTLaaNEVGs8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=R0e2KWhX; 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="R0e2KWhX" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 95987C113D0; Wed, 20 Aug 2025 08:17:31 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1755677853; bh=7fsMezvoDQEYp2ae8oSLemUvrmjXu6gJVUF5jYa8vMs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=R0e2KWhXHtI1VnTHl1Fsv57y9Ww9+SKlB3nGZM6pSL3YwAMPT8McXEO95tHkLq2Op /iPfv6auBcQxG49322bKL+ngXCmjs0yXnh32/eUkQ0X6FKF9yJDAZR49il2UnE/1jD UAqUu0MPttMzasmpozO66zsy60OuZOgaKVcUT8Dg8PBfHWHb/bBVJ5gov+C5ktDLDB A72KVVim2068GkQAznQ3vsxA99zdNN9LyOyIMZNJwbb4KTD/sYu50fDWSQRBJAJubj RvdtSGsjfg8aTHFA4Au1TJa1WLDFkZB1c3Rqqn5JRZzXJuQYZrWhZKBvFQW8VRXAWY apgMxufqMcEWA== From: Tzung-Bi Shih To: Benson Leung , Greg Kroah-Hartman , "Rafael J . Wysocki" , Danilo Krummrich Cc: Jonathan Corbet , Shuah Khan , Dawid Niedzwiecki , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, chrome-platform@lists.linux.dev, linux-kselftest@vger.kernel.org, tzungbi@kernel.org Subject: [PATCH v2 4/5] platform/chrome: Protect cros_ec_device lifecycle with revocable Date: Wed, 20 Aug 2025 08:16:44 +0000 Message-ID: <20250820081645.847919-5-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0.rc1.167.g924127e9c0-goog In-Reply-To: <20250820081645.847919-1-tzungbi@kernel.org> References: <20250820081645.847919-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 revocable and convert the underlying device drivers to resource providers of cros_ec_device. Signed-off-by: Tzung-Bi Shih --- v2: - Rename "ref_proxy" -> "revocable". v1: https://lore.kernel.org/chrome-platform/20250814091020.1302888-3-tzungb= i@kernel.org 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 | 4 ++++ 7 files changed, 33 insertions(+) diff --git a/drivers/platform/chrome/cros_ec_i2c.c b/drivers/platform/chrom= e/cros_ec_i2c.c index 38af97cdaab2..68a8d2c65ca3 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->revocable_provider =3D devm_revocable_provider_alloc(dev, ec_dev); + if (!ec_dev->revocable_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..3467b4e9ceb5 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->revocable_provider =3D devm_revocable_provider_alloc(dev, ec_dev); + if (!ec_dev->revocable_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..77bdaf430979 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->revocable_provider =3D devm_revocable_provider_alloc(dev, ec_dev); + if (!ec_dev->revocable_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..04d886829aa7 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->revocable_provider =3D devm_revocable_provider_alloc(dev, ec_dev); + if (!ec_dev->revocable_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..1b611ec5c9c1 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->revocable_provider =3D devm_revocable_provider_alloc(dev, ec_dev); + if (!ec_dev->revocable_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..b234f13a92a9 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->revocable_provider =3D devm_revocable_provider_alloc(dev, ec_dev); + if (!ec_dev->revocable_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..0c3df0bb0aa4 100644 --- a/include/linux/platform_data/cros_ec_proto.h +++ b/include/linux/platform_data/cros_ec_proto.h @@ -12,6 +12,7 @@ #include #include #include +#include =20 #include =20 @@ -158,6 +159,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. + * @revocable_provider: The revocable_provider to this device. */ struct cros_ec_device { /* These are used by other drivers that want to talk to the EC */ @@ -203,6 +205,8 @@ struct cros_ec_device { struct platform_device *pd; =20 struct blocking_notifier_head panic_notifier; + + struct revocable_provider *revocable_provider; }; =20 /** --=20 2.51.0.rc1.167.g924127e9c0-goog From nobody Sat Oct 4 04:57:28 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 B6A762DA776; Wed, 20 Aug 2025 08:17:35 +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=1755677855; cv=none; b=DmM2n5wKSRUkwabEgdzAkaeB47wa6uPzK9+mrL7Yml0cqdWQo6f1lgdC8vW129xOvH1xJiW40cIoL34EBAD1VYgcbHsGfm15JzdFLeRF5GuAydgBYTd5CN0h6baitCMEXp8abVG8dkU+vPo1uEfOG3/M7yCzTinZzIeoxJAwEJw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755677855; c=relaxed/simple; bh=JMpIgp8YIbrOSw336Fx7Pf1imqt/c5Pc3wWDqKBle34=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=OtjREno3QO1WeA6yBZa4/H407KS+H91hApde1scI97FtWYTT6kdAIORqrYE6k0ZgrjXZCbTVXLsK6U1Y/RpEEj6jPFgxBKBnnlyPBuwCzXopWjha4/zPBn0gySARFAy9CWejHlI/eBmnY13wwzcn5igiJkcRJCUpYANqsQcZWgQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=UfJcplxp; 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="UfJcplxp" Received: by smtp.kernel.org (Postfix) with ESMTPSA id C1CC9C4CEEB; Wed, 20 Aug 2025 08:17:33 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1755677855; bh=JMpIgp8YIbrOSw336Fx7Pf1imqt/c5Pc3wWDqKBle34=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UfJcplxpUPDflKOZsWezZeWsG7SqcXhO2qVLnFE1MpovVnbQbdfJIGhqjlB50Zz2V QBPHPE+OooMGlt/sXhN96M57wAN9SlYuJ1S5hFfdq1Y3zbOr+C7yaLlXaL2tZU6U6l M+XMzFcZLGGcger62L/+Fg59ZZRIR2fJiIkxcMfnTXbi5nZi8KaO++AbfNQIXEkE2s hM58INCxce4hJ3xqEOIkpshELZ99An5WP/ux6h+5lIujvnWXDcHM49bEDw4yEY8aFn EdT5rPnT5mVtdP6zcPB3sb2MmwjznUvZqdM+EN+RLmHZqzuum3cHmJSGvAtdRlAW2I chtYEt1F6zOzQ== From: Tzung-Bi Shih To: Benson Leung , Greg Kroah-Hartman , "Rafael J . Wysocki" , Danilo Krummrich Cc: Jonathan Corbet , Shuah Khan , Dawid Niedzwiecki , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, chrome-platform@lists.linux.dev, linux-kselftest@vger.kernel.org, tzungbi@kernel.org Subject: [PATCH v2 5/5] platform/chrome: cros_ec_chardev: Consume cros_ec_device via revocable Date: Wed, 20 Aug 2025 08:16:45 +0000 Message-ID: <20250820081645.847919-6-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0.rc1.167.g924127e9c0-goog In-Reply-To: <20250820081645.847919-1-tzungbi@kernel.org> References: <20250820081645.847919-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 revocable and convert cros_ec_chardev to a resource consumer of cros_ec_device. Signed-off-by: Tzung-Bi Shih --- v2: - Rename "ref_proxy" -> "revocable". - Fix a sparse warning by removing the redundant __rcu annotation. v1: https://lore.kernel.org/chrome-platform/20250814091020.1302888-4-tzungb= i@kernel.org drivers/platform/chrome/cros_ec_chardev.c | 124 +++++++++++++++------- 1 file changed, 84 insertions(+), 40 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_chardev.c b/drivers/platform/c= hrome/cros_ec_chardev.c index c9d80ad5b57e..d41c7f574cf1 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 revocable *ec_dev_rev; 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 *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; + REVOCABLE(priv->ec_dev_rev, 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 *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; + + REVOCABLE(priv->ec_dev_rev, ec_dev) { + if (!ec_dev) + return NOTIFY_DONE; + + event_bit =3D 1 << ec_dev->event_data.event_type; + total_size =3D sizeof(*event) + ec_dev->event_size; =20 - if (!(event_bit & priv->event_mask) || - (priv->event_len + total_size) > CROS_MAX_EVENT_LEN) - return NOTIFY_DONE; + if (!(event_bit & priv->event_mask) || + (priv->event_len + total_size) > CROS_MAX_EVENT_LEN) + return NOTIFY_DONE; =20 - event =3D kzalloc(total_size, GFP_KERNEL); - if (!event) - return NOTIFY_DONE; + event =3D kzalloc(total_size, GFP_KERNEL); + if (!event) + 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->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_rev =3D revocable_alloc(ec_dev->revocable_provider); + if (!priv->ec_dev_rev) { + 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_rev) + revocable_free(priv->ec_dev_rev); + kfree(priv); return ret; } =20 @@ -251,11 +278,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 =3D filp->private_data; - struct cros_ec_device *ec_dev =3D priv->ec_dev; + struct cros_ec_device *ec_dev; struct ec_event *event, *e; =20 - blocking_notifier_chain_unregister(&ec_dev->event_notifier, - &priv->notifier); + REVOCABLE(priv->ec_dev_rev, ec_dev) { + if (ec_dev) + blocking_notifier_chain_unregister(&ec_dev->event_notifier, + &priv->notifier); + } + revocable_free(priv->ec_dev_rev); =20 list_for_each_entry_safe(event, e, &priv->events, node) { list_del(&event->node); @@ -273,6 +304,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 *ec_dev; long ret; =20 if (copy_from_user(&u_cmd, arg, sizeof(u_cmd))) @@ -299,10 +331,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; + REVOCABLE(priv->ec_dev_rev, 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 +352,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 *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; + REVOCABLE(priv->ec_dev_rev, 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.167.g924127e9c0-goog