From e254b758976f651c47ec902d92306bd49f452ab0 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 29 Dec 2025 15:43:25 +0100 Subject: driver core: make bus_find_device_by_acpi_dev() stub prototype aligned Currently the bus_find_device_by_acpi_dev() stub for !CONFIG_ACPI case takes a const void * parameter instead of const struct acpi_device *. As long as it's a pointer, we may named it as we want to with the help of a forward declaration. Hence move the declaration out of the ifdeffery and use the same prototype in both cases. This adds a bit of an additional type checking at a compilation time. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20251229144325.1252197-1-andriy.shevchenko@linux.intel.com [ Fix minor typo in the commit message. - Danilo ] Signed-off-by: Danilo Krummrich --- include/linux/device/bus.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/include/linux/device/bus.h b/include/linux/device/bus.h index 99b1002b3e31..99c3c83ea520 100644 --- a/include/linux/device/bus.h +++ b/include/linux/device/bus.h @@ -215,9 +215,9 @@ bus_find_next_device(const struct bus_type *bus,struct device *cur) return bus_find_device(bus, cur, NULL, device_match_any); } -#ifdef CONFIG_ACPI struct acpi_device; +#ifdef CONFIG_ACPI /** * bus_find_device_by_acpi_dev : device iterator for locating a particular device * matching the ACPI COMPANION device. @@ -231,7 +231,7 @@ bus_find_device_by_acpi_dev(const struct bus_type *bus, const struct acpi_device } #else static inline struct device * -bus_find_device_by_acpi_dev(const struct bus_type *bus, const void *adev) +bus_find_device_by_acpi_dev(const struct bus_type *bus, const struct acpi_device *adev) { return NULL; } -- cgit v1.2.3 From 62eb557580eb2177cf16c3fd2b6efadff297b29a Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Fri, 16 Jan 2026 08:02:33 +0000 Subject: revocable: Revocable resource management 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. 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 valid for the duration of its use, or it fails safely if the resource has already been revoked. The implementation uses a provider/consumer model built on Sleepable RCU (SRCU) to guarantee safe memory access: - A resource provider, such as a driver for a hot-pluggable device, 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 acts as a handle containing 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_withdraw_access() to exit the SRCU critical section. The REVOCABLE_TRY_ACCESS_WITH() and REVOCABLE_TRY_ACCESS_SCOPED() are convenient helpers for doing that. - When the provider needs to remove the resource, it calls revocable_provider_revoke(). 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. Acked-by: Danilo Krummrich Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260116080235.350305-2-tzungbi@kernel.org Signed-off-by: Greg Kroah-Hartman --- Documentation/driver-api/driver-model/index.rst | 1 + .../driver-api/driver-model/revocable.rst | 152 +++++++++++++ MAINTAINERS | 7 + drivers/base/Makefile | 2 +- drivers/base/revocable.c | 242 +++++++++++++++++++++ include/linux/revocable.h | 69 ++++++ 6 files changed, 472 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 (limited to 'include/linux') diff --git a/Documentation/driver-api/driver-model/index.rst b/Documentation/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 .. only:: subproject and html diff --git a/Documentation/driver-api/driver-model/revocable.rst b/Documentation/driver-api/driver-model/revocable.rst new file mode 100644 index 000000000000..22a442cc8d7f --- /dev/null +++ b/Documentation/driver-api/driver-model/revocable.rst @@ -0,0 +1,152 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============================== +Revocable Resource Management +============================== + +Overview +======== + +.. kernel-doc:: drivers/base/revocable.c + :doc: Overview + +Revocable vs. Devres (devm) +=========================== + +It's important to understand the distinct roles of the Revocable and Devres, +and how they can complement each other. They address different problems in +resource management: + +* **Devres:** Primarily address **resource leaks**. The lifetime of the + resources is tied to the lifetime of the device. The resource is + automatically freed when the device is unbound. This cleanup happens + irrespective of any potential active users. + +* **Revocable:** Primarily addresses **invalid memory access**, + such as Use-After-Free (UAF). It's an independent synchronization + primitive that decouples consumer access from the resource's actual + presence. Consumers interact with a "revocable object" (an intermediary), + not the underlying resource directly. This revocable object persists as + long as there are active references to it from consumer handles. + +**Key Distinctions & How They Complement Each Other:** + +1. **Reference Target:** Consumers of a resource managed by the Revocable + mechanism hold a reference to the *revocable object*, not the + encapsulated resource itself. + +2. **Resource Lifetime vs. Access:** The underlying resource's lifetime is + independent of the number of references to the revocable object. The + resource can be freed at any point. A common scenario is the resource + being freed by `devres` when the providing device is unbound. + +3. **Safe Access:** Revocable provides a safe way to attempt access. Before + using the resource, a consumer uses the Revocable API (e.g., + revocable_try_access()). This function checks if the resource is still + valid. It returns a pointer to the resource only if it hasn't been + revoked; otherwise, it returns NULL. This prevents UAF by providing a + clear signal that the resource is gone. + +4. **Complementary Usage:** `devres` and Revocable work well together. + `devres` can handle the automatic allocation and deallocation of a + resource tied to a device. The Revocable mechanism can be layered on top + to provide safe access for consumers whose lifetimes might extend beyond + the provider device's lifetime. For instance, a userspace program might + keep a character device file open even after the physical device has been + removed. In this case: + + * `devres` frees the device-specific resource upon unbinding. + * The Revocable mechanism ensures that any subsequent operations on the + open file handle, which attempt to access the now-freed resource, + will fail gracefully (e.g., revocable_try_access() returns NULL) + instead of causing a UAF. + +In summary, `devres` ensures resources are *released* to prevent leaks, while +the Revocable mechanism ensures that attempts to *access* these resources are +done safely, even if the resource has been released. + +API and Usage +============= + +For Resource Providers +---------------------- +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_provider + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_provider_alloc + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: devm_revocable_provider_alloc + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_provider_revoke + +For Resource Consumers +---------------------- +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_alloc + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_free + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_try_access + +.. kernel-doc:: drivers/base/revocable.c + :identifiers: revocable_withdraw_access + +.. kernel-doc:: include/linux/revocable.h + :identifiers: REVOCABLE_TRY_ACCESS_WITH + +Example Usage +~~~~~~~~~~~~~ + +.. code-block:: c + + void consumer_use_resource(struct revocable *rev) + { + struct foo_resource *res; + + REVOCABLE_TRY_ACCESS_WITH(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_withdraw_access() is automatically called here. + +.. kernel-doc:: include/linux/revocable.h + :identifiers: REVOCABLE_TRY_ACCESS_SCOPED + +Example Usage +~~~~~~~~~~~~~ + +.. code-block:: c + + void consumer_use_resource(struct revocable *rev) + { + struct foo_resource *res; + + REVOCABLE_TRY_ACCESS_SCOPED(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_withdraw_access() is automatically called here. + } diff --git a/MAINTAINERS b/MAINTAINERS index 54ea68a0883d..f873553d4543 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22368,6 +22368,13 @@ F: include/uapi/linux/rseq.h F: kernel/rseq.c F: tools/testing/selftests/rseq/ +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 := 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) += auxiliary.o obj-$(CONFIG_DEVTMPFS) += devtmpfs.o obj-y += power/ diff --git a/drivers/base/revocable.c b/drivers/base/revocable.c new file mode 100644 index 000000000000..f6cece275aac --- /dev/null +++ b/drivers/base/revocable.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2026 Google LLC + * + * Revocable resource management + */ + +#include +#include +#include +#include +#include + +/** + * DOC: Overview + * + * The "revocable" mechanism is a synchronization primitive designed to manage + * safe access to resources that can be asynchronously removed or invalidated. + * Its primary purpose is to prevent Use-After-Free (UAF) errors when + * interacting with resources whose lifetimes are not guaranteed to outlast + * their consumers. + * + * This is particularly useful in systems where resources can disappear + * unexpectedly, such as those provided by hot-pluggable devices like USB. + * When a consumer holds a reference to such a resource, the underlying device + * might be removed, causing the resource's memory to be freed. Subsequent + * access attempts by the consumer would then lead to UAF errors. + * + * Revocable addresses this by providing a form of "weak reference" and a + * controlled access method. It allows a resource consumer to safely attempt to + * access the resource. The mechanism guarantees that any access granted is + * valid for the duration of its use. If the resource has already been + * revoked (i.e., freed), the access attempt will fail safely, typically by + * returning NULL, instead of causing a crash. + * + * The implementation uses a provider/consumer model built on Sleepable + * RCU (SRCU) to guarantee safe memory access: + * + * - A resource provider, such as a driver for a hot-pluggable device, + * 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 acts as a handle containing 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_withdraw_access() to exit the SRCU critical section. The + * REVOCABLE_TRY_ACCESS_WITH() and REVOCABLE_TRY_ACCESS_SCOPED() are + * convenient helpers for doing that. + * + * - When the provider needs to remove the resource, it calls + * revocable_provider_revoke(). 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 = 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 = container_of(kref, + struct revocable_provider, kref); + + cleanup_srcu_struct(&rp->srcu); + kfree(rp); +} + +/** + * revocable_provider_revoke() - Revoke the managed resource. + * @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 struct. + */ +void revocable_provider_revoke(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_revoke); + +static void devm_revocable_provider_revoke(void *data) +{ + struct revocable_provider *rp = data; + + revocable_provider_revoke(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_revoke() 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 *dev, + void *res) +{ + struct revocable_provider *rp; + + rp = revocable_provider_alloc(res); + if (!rp) + return NULL; + + if (devm_add_action_or_reset(dev, devm_revocable_provider_revoke, rp)) + return NULL; + + return rp; +} +EXPORT_SYMBOL_GPL(devm_revocable_provider_alloc); + +/** + * revocable_alloc() - Allocate struct revocable. + * @rp: The pointer of resource provider. + * + * This holds a refcount to the resource provider. + * + * Return: The pointer of struct revocable. NULL on errors. + */ +struct revocable *revocable_alloc(struct revocable_provider *rp) +{ + struct revocable *rev; + + rev = kzalloc(sizeof(*rev), GFP_KERNEL); + if (!rev) + return NULL; + + rev->rp = 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 struct. + */ +void revocable_free(struct revocable *rev) +{ + struct revocable_provider *rp = 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->srcu) +{ + struct revocable_provider *rp = rev->rp; + + rev->idx = srcu_read_lock(&rp->srcu); + return srcu_dereference(rp->res, &rp->srcu); +} +EXPORT_SYMBOL_GPL(revocable_try_access); + +/** + * revocable_withdraw_access() - 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_withdraw_access(struct revocable *rev) __releases(&rev->rp->srcu) +{ + struct revocable_provider *rp = rev->rp; + + srcu_read_unlock(&rp->srcu, rev->idx); +} +EXPORT_SYMBOL_GPL(revocable_withdraw_access); diff --git a/include/linux/revocable.h b/include/linux/revocable.h new file mode 100644 index 000000000000..659ba01c58db --- /dev/null +++ b/include/linux/revocable.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2026 Google LLC + */ + +#ifndef __LINUX_REVOCABLE_H +#define __LINUX_REVOCABLE_H + +#include +#include + +struct device; +struct revocable; +struct revocable_provider; + +struct revocable_provider *revocable_provider_alloc(void *res); +void revocable_provider_revoke(struct revocable_provider *rp); +struct revocable_provider *devm_revocable_provider_alloc(struct device *dev, + 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->srcu); +void revocable_withdraw_access(struct revocable *rev) __releases(&rev->rp->srcu); + +DEFINE_FREE(access_rev, struct revocable *, if (_T) revocable_withdraw_access(_T)) + +/** + * REVOCABLE_TRY_ACCESS_WITH() - A helper for accessing revocable resource + * @_rev: The consumer's ``struct revocable *`` handle. + * @_res: A pointer variable that will be assigned the resource. + * + * The macro simplifies the access-release cycle for consumers, ensuring that + * revocable_withdraw_access() is always called, even in the case of an early + * exit. + * + * It creates a local variable in the current scope. @_res is populated with + * the result of revocable_try_access(). The consumer code **must** check if + * @_res is ``NULL`` before using it. The revocable_withdraw_access() function + * is automatically called when the scope is exited. + * + * Note: It shares the same issue with guard() in cleanup.h. No goto statements + * are allowed before the helper. Otherwise, the compiler fails with + * "jump bypasses initialization of variable with __attribute__((cleanup))". + */ +#define REVOCABLE_TRY_ACCESS_WITH(_rev, _res) \ + struct revocable *__UNIQUE_ID(name) __free(access_rev) = _rev; \ + _res = revocable_try_access(_rev) + +#define _REVOCABLE_TRY_ACCESS_SCOPED(_rev, _label, _res) \ + for (struct revocable *__UNIQUE_ID(name) __free(access_rev) = _rev; \ + (_res = revocable_try_access(_rev)) || true; ({ goto _label; })) \ + if (0) { \ +_label: \ + break; \ + } else + +/** + * REVOCABLE_TRY_ACCESS_SCOPED() - A helper for accessing revocable resource + * @_rev: The consumer's ``struct revocable *`` handle. + * @_res: A pointer variable that will be assigned the resource. + * + * Similar to REVOCABLE_TRY_ACCESS_WITH() but with an explicit scope from a + * temporary ``for`` loop. + */ +#define REVOCABLE_TRY_ACCESS_SCOPED(_rev, _res) \ + _REVOCABLE_TRY_ACCESS_SCOPED(_rev, __UNIQUE_ID(label), _res) + +#endif /* __LINUX_REVOCABLE_H */ -- cgit v1.2.3 From 7a96ccc82c106b763dd561cb87f9c7261dff4f0d Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Sat, 20 Dec 2025 04:45:34 +0100 Subject: driver core: attribute_container: change return type to void attribute_container_register() has always returned 0 since its introduction in commit 06ff5a987e ("Add attribute container to generic device model") in the historical Linux tree [1]. Convert the return type to void and update all callers. This removes dead code where callers checked for errors that could never occur. Link: https://git.kernel.org/pub/scm/linux/kernel/git/tglx/history.git [1] Signed-off-by: Daniel Gomez Link: https://patch.msgid.link/20251220-dev-attribute-container-linux-scsi-v1-1-d58fcd03bf21@samsung.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/attribute_container.c | 4 +--- drivers/base/transport_class.c | 8 ++------ drivers/scsi/scsi_transport_spi.c | 2 +- include/linux/attribute_container.h | 2 +- include/linux/transport_class.h | 6 +++--- 5 files changed, 8 insertions(+), 14 deletions(-) (limited to 'include/linux') diff --git a/drivers/base/attribute_container.c b/drivers/base/attribute_container.c index b6f941a6ab69..72adbacc6554 100644 --- a/drivers/base/attribute_container.c +++ b/drivers/base/attribute_container.c @@ -69,7 +69,7 @@ static DEFINE_MUTEX(attribute_container_mutex); * @cont: The container to register. This must be allocated by the * callee and should also be zeroed by it. */ -int +void attribute_container_register(struct attribute_container *cont) { INIT_LIST_HEAD(&cont->node); @@ -79,8 +79,6 @@ attribute_container_register(struct attribute_container *cont) mutex_lock(&attribute_container_mutex); list_add_tail(&cont->node, &attribute_container_list); mutex_unlock(&attribute_container_mutex); - - return 0; } EXPORT_SYMBOL_GPL(attribute_container_register); diff --git a/drivers/base/transport_class.c b/drivers/base/transport_class.c index 09ee2a1e35bb..4b1e8820e764 100644 --- a/drivers/base/transport_class.c +++ b/drivers/base/transport_class.c @@ -88,17 +88,13 @@ static int anon_transport_dummy_function(struct transport_container *tc, * events. Use prezero and then use DECLARE_ANON_TRANSPORT_CLASS() to * initialise the anon transport class storage. */ -int anon_transport_class_register(struct anon_transport_class *atc) +void anon_transport_class_register(struct anon_transport_class *atc) { - int error; atc->container.class = &atc->tclass.class; attribute_container_set_no_classdevs(&atc->container); - error = attribute_container_register(&atc->container); - if (error) - return error; + attribute_container_register(&atc->container); atc->tclass.setup = anon_transport_dummy_function; atc->tclass.remove = anon_transport_dummy_function; - return 0; } EXPORT_SYMBOL_GPL(anon_transport_class_register); diff --git a/drivers/scsi/scsi_transport_spi.c b/drivers/scsi/scsi_transport_spi.c index fe47850a8258..17a4a0918fc4 100644 --- a/drivers/scsi/scsi_transport_spi.c +++ b/drivers/scsi/scsi_transport_spi.c @@ -1622,7 +1622,7 @@ static __init int spi_transport_init(void) error = transport_class_register(&spi_transport_class); if (error) return error; - error = anon_transport_class_register(&spi_device_class); + anon_transport_class_register(&spi_device_class); return transport_class_register(&spi_host_class); } diff --git a/include/linux/attribute_container.h b/include/linux/attribute_container.h index b3643de9931d..fa6520e192be 100644 --- a/include/linux/attribute_container.h +++ b/include/linux/attribute_container.h @@ -36,7 +36,7 @@ attribute_container_set_no_classdevs(struct attribute_container *atc) atc->flags |= ATTRIBUTE_CONTAINER_NO_CLASSDEVS; } -int attribute_container_register(struct attribute_container *cont); +void attribute_container_register(struct attribute_container *cont); int __must_check attribute_container_unregister(struct attribute_container *cont); void attribute_container_create_device(struct device *dev, int (*fn)(struct attribute_container *, diff --git a/include/linux/transport_class.h b/include/linux/transport_class.h index 2efc271a96fa..9c2e03104461 100644 --- a/include/linux/transport_class.h +++ b/include/linux/transport_class.h @@ -87,9 +87,9 @@ transport_unregister_device(struct device *dev) transport_destroy_device(dev); } -static inline int transport_container_register(struct transport_container *tc) +static inline void transport_container_register(struct transport_container *tc) { - return attribute_container_register(&tc->ac); + attribute_container_register(&tc->ac); } static inline void transport_container_unregister(struct transport_container *tc) @@ -99,7 +99,7 @@ static inline void transport_container_unregister(struct transport_container *tc } int transport_class_register(struct transport_class *); -int anon_transport_class_register(struct anon_transport_class *); +void anon_transport_class_register(struct anon_transport_class *); void transport_class_unregister(struct transport_class *); void anon_transport_class_unregister(struct anon_transport_class *); -- cgit v1.2.3 From 08a55792245a7bd395c947ff88b08b6abdd56f93 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Mon, 19 Jan 2026 17:27:57 +0100 Subject: driver-core: move devres_for_each_res() to base.h devres_for_each_res() is only used by .../firmware_loader/main.c, which already includes base.h. The usage of devres_for_each_res() by code outside of driver-core is questionable, hence move it to base.h. Acked-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260119162920.77189-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- drivers/base/base.h | 4 ++++ include/linux/device/devres.h | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'include/linux') diff --git a/drivers/base/base.h b/drivers/base/base.h index 430cbefbc97f..60ee4d466b29 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -213,6 +213,10 @@ static inline void device_set_driver(struct device *dev, const struct device_dri WRITE_ONCE(dev->driver, (struct device_driver *)drv); } +void devres_for_each_res(struct device *dev, dr_release_t release, + dr_match_t match, void *match_data, + void (*fn)(struct device *, void *, void *), + void *data); int devres_release_all(struct device *dev); void device_block_probing(void); void device_unblock_probing(void); diff --git a/include/linux/device/devres.h b/include/linux/device/devres.h index 9c1e3d643d69..14ab9159bdda 100644 --- a/include/linux/device/devres.h +++ b/include/linux/device/devres.h @@ -26,10 +26,6 @@ __devres_alloc_node(dr_release_t release, size_t size, gfp_t gfp, int nid, const #define devres_alloc_node(release, size, gfp, nid) \ __devres_alloc_node(release, size, gfp, nid, #release) -void devres_for_each_res(struct device *dev, dr_release_t release, - dr_match_t match, void *match_data, - void (*fn)(struct device *, void *, void *), - void *data); void devres_free(void *res); void devres_add(struct device *dev, void *res); void *devres_find(struct device *dev, dr_release_t release, dr_match_t match, void *match_data); -- cgit v1.2.3 From 4d7dc4d1a62dbb22b1178dddeeb7a22d0272df77 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Thu, 29 Jan 2026 14:37:30 +0000 Subject: revocable: Fix races in revocable_alloc() using RCU There are two race conditions when allocating a revocable instance: 1. After a struct revocable_provider is revoked, the caller might still hold a dangling pointer to it. A subsequent call to revocable_alloc() can trigger a use-after-free. 2. If revocable_provider_release() runs concurrently with revocable_alloc(), the memory of struct revocable_provider can be accessed during or after kfree(). To fix these: - Manage the lifetime of struct revocable_provider using RCU. Annotate pointers to it with __rcu and use kfree_rcu() for deallocation. - Update revocable_alloc() to safely acquire a reference using RCU primitives. - Update revocable_provider_revoke() to take a double pointer (`**rp`). It atomically NULLs out the caller's pointer before starting revocation. This prevents the caller from holding a dangling pointer. - Drop devm_revocable_provider_alloc(). The devm-managed model cannot support the required double-pointer semantic for safe pointer nulling. Reported-by: Johan Hovold Closes: https://lore.kernel.org/all/aXdy-b3GOJkzGqYo@hovoldconsulting.com/ Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260129143733.45618-2-tzungbi@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../driver-api/driver-model/revocable.rst | 3 - drivers/base/revocable.c | 93 +++++++++++----------- drivers/base/revocable_test.c | 20 +++-- include/linux/revocable.h | 8 +- .../base/revocable/test_modules/revocable_test.c | 19 ++--- 5 files changed, 72 insertions(+), 71 deletions(-) (limited to 'include/linux') diff --git a/Documentation/driver-api/driver-model/revocable.rst b/Documentation/driver-api/driver-model/revocable.rst index 22a442cc8d7f..ab7c0af5fbb6 100644 --- a/Documentation/driver-api/driver-model/revocable.rst +++ b/Documentation/driver-api/driver-model/revocable.rst @@ -76,9 +76,6 @@ For Resource Providers .. kernel-doc:: drivers/base/revocable.c :identifiers: revocable_provider_alloc -.. kernel-doc:: drivers/base/revocable.c - :identifiers: devm_revocable_provider_alloc - .. kernel-doc:: drivers/base/revocable.c :identifiers: revocable_provider_revoke diff --git a/drivers/base/revocable.c b/drivers/base/revocable.c index b068e18a847d..1bcd1cf54764 100644 --- a/drivers/base/revocable.c +++ b/drivers/base/revocable.c @@ -64,11 +64,13 @@ * @srcu: The SRCU to protect the resource. * @res: The pointer of resource. It can point to anything. * @kref: The refcount for this handle. + * @rcu: The RCU to protect pointer to itself. */ struct revocable_provider { struct srcu_struct srcu; void __rcu *res; struct kref kref; + struct rcu_head rcu; }; /** @@ -88,8 +90,9 @@ struct revocable { * This holds an initial refcount to the struct. * * Return: The pointer of struct revocable_provider. NULL on errors. + * It enforces the caller handles the returned pointer in RCU ways. */ -struct revocable_provider *revocable_provider_alloc(void *res) +struct revocable_provider __rcu *revocable_provider_alloc(void *res) { struct revocable_provider *rp; @@ -98,10 +101,10 @@ struct revocable_provider *revocable_provider_alloc(void *res) return NULL; init_srcu_struct(&rp->srcu); - rcu_assign_pointer(rp->res, res); + RCU_INIT_POINTER(rp->res, res); kref_init(&rp->kref); - return rp; + return (struct revocable_provider __rcu *)rp; } EXPORT_SYMBOL_GPL(revocable_provider_alloc); @@ -111,82 +114,80 @@ static void revocable_provider_release(struct kref *kref) struct revocable_provider, kref); cleanup_srcu_struct(&rp->srcu); - kfree(rp); + kfree_rcu(rp, rcu); } /** * revocable_provider_revoke() - Revoke the managed resource. - * @rp: The pointer of resource provider. + * @rp_ptr: The pointer of 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 struct. - */ -void revocable_provider_revoke(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_revoke); - -static void devm_revocable_provider_revoke(void *data) -{ - struct revocable_provider *rp = data; - - revocable_provider_revoke(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_revoke() 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. + * It enforces the caller to pass a pointer of pointer of resource provider so + * that it sets \*rp_ptr to NULL to prevent from keeping a dangling pointer. */ -struct revocable_provider *devm_revocable_provider_alloc(struct device *dev, - void *res) +void revocable_provider_revoke(struct revocable_provider __rcu **rp_ptr) { struct revocable_provider *rp; - rp = revocable_provider_alloc(res); + rp = rcu_replace_pointer(*rp_ptr, NULL, 1); if (!rp) - return NULL; + return; - if (devm_add_action_or_reset(dev, devm_revocable_provider_revoke, rp)) - return NULL; - - return rp; + rcu_assign_pointer(rp->res, NULL); + synchronize_srcu(&rp->srcu); + kref_put(&rp->kref, revocable_provider_release); } -EXPORT_SYMBOL_GPL(devm_revocable_provider_alloc); +EXPORT_SYMBOL_GPL(revocable_provider_revoke); /** * revocable_alloc() - Allocate struct revocable. - * @rp: The pointer of resource provider. + * @_rp: The pointer of resource provider. * * This holds a refcount to the resource provider. * * Return: The pointer of struct revocable. NULL on errors. */ -struct revocable *revocable_alloc(struct revocable_provider *rp) +struct revocable *revocable_alloc(struct revocable_provider __rcu *_rp) { + struct revocable_provider *rp; struct revocable *rev; + if (!_rp) + return NULL; + + /* + * Enter a read-side critical section. + * + * This prevents kfree_rcu() from freeing the struct revocable_provider + * memory, for the duration of this scope. + */ + scoped_guard(rcu) { + rp = rcu_dereference(_rp); + if (!rp) + /* The revocable provider has been revoked. */ + return NULL; + + if (!kref_get_unless_zero(&rp->kref)) + /* + * The revocable provider is releasing (i.e., + * revocable_provider_release() has been called). + */ + return NULL; + } + /* At this point, `rp` is safe to access as holding a kref of it */ + rev = kzalloc(sizeof(*rev), GFP_KERNEL); - if (!rev) + if (!rev) { + kref_put(&rp->kref, revocable_provider_release); return NULL; + } rev->rp = rp; - kref_get(&rp->kref); - return rev; } EXPORT_SYMBOL_GPL(revocable_alloc); diff --git a/drivers/base/revocable_test.c b/drivers/base/revocable_test.c index 873a44082b6c..1622aae92fd3 100644 --- a/drivers/base/revocable_test.c +++ b/drivers/base/revocable_test.c @@ -21,7 +21,7 @@ static void revocable_test_basic(struct kunit *test) { - struct revocable_provider *rp; + struct revocable_provider __rcu *rp; struct revocable *rev; void *real_res = (void *)0x12345678, *res; @@ -36,12 +36,13 @@ static void revocable_test_basic(struct kunit *test) revocable_withdraw_access(rev); revocable_free(rev); - revocable_provider_revoke(rp); + revocable_provider_revoke(&rp); + KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); } static void revocable_test_revocation(struct kunit *test) { - struct revocable_provider *rp; + struct revocable_provider __rcu *rp; struct revocable *rev; void *real_res = (void *)0x12345678, *res; @@ -55,7 +56,8 @@ static void revocable_test_revocation(struct kunit *test) KUNIT_EXPECT_PTR_EQ(test, res, real_res); revocable_withdraw_access(rev); - revocable_provider_revoke(rp); + revocable_provider_revoke(&rp); + KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); res = revocable_try_access(rev); KUNIT_EXPECT_PTR_EQ(test, res, NULL); @@ -66,7 +68,7 @@ static void revocable_test_revocation(struct kunit *test) static void revocable_test_try_access_macro(struct kunit *test) { - struct revocable_provider *rp; + struct revocable_provider __rcu *rp; struct revocable *rev; void *real_res = (void *)0x12345678, *res; @@ -81,7 +83,8 @@ static void revocable_test_try_access_macro(struct kunit *test) KUNIT_EXPECT_PTR_EQ(test, res, real_res); } - revocable_provider_revoke(rp); + revocable_provider_revoke(&rp); + KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); { REVOCABLE_TRY_ACCESS_WITH(rev, res); @@ -93,7 +96,7 @@ static void revocable_test_try_access_macro(struct kunit *test) static void revocable_test_try_access_macro2(struct kunit *test) { - struct revocable_provider *rp; + struct revocable_provider __rcu *rp; struct revocable *rev; void *real_res = (void *)0x12345678, *res; bool accessed; @@ -111,7 +114,8 @@ static void revocable_test_try_access_macro2(struct kunit *test) } KUNIT_EXPECT_TRUE(test, accessed); - revocable_provider_revoke(rp); + revocable_provider_revoke(&rp); + KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); accessed = false; REVOCABLE_TRY_ACCESS_SCOPED(rev, res) { diff --git a/include/linux/revocable.h b/include/linux/revocable.h index 659ba01c58db..d5da3584adbe 100644 --- a/include/linux/revocable.h +++ b/include/linux/revocable.h @@ -13,12 +13,10 @@ struct device; struct revocable; struct revocable_provider; -struct revocable_provider *revocable_provider_alloc(void *res); -void revocable_provider_revoke(struct revocable_provider *rp); -struct revocable_provider *devm_revocable_provider_alloc(struct device *dev, - void *res); +struct revocable_provider __rcu *revocable_provider_alloc(void *res); +void revocable_provider_revoke(struct revocable_provider __rcu **rp); -struct revocable *revocable_alloc(struct revocable_provider *rp); +struct revocable *revocable_alloc(struct revocable_provider __rcu *rp); void revocable_free(struct revocable *rev); void *revocable_try_access(struct revocable *rev) __acquires(&rev->rp->srcu); void revocable_withdraw_access(struct revocable *rev) __releases(&rev->rp->srcu); diff --git a/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c b/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c index 1b0692eb75f3..ae6c67e65f3d 100644 --- a/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c +++ b/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c @@ -17,7 +17,7 @@ static struct dentry *debugfs_dir; struct revocable_test_provider_priv { - struct revocable_provider *rp; + struct revocable_provider __rcu *rp; struct dentry *dentry; char res[16]; }; @@ -25,7 +25,7 @@ struct revocable_test_provider_priv { static int revocable_test_consumer_open(struct inode *inode, struct file *filp) { struct revocable *rev; - struct revocable_provider *rp = inode->i_private; + struct revocable_provider __rcu *rp = inode->i_private; rev = revocable_alloc(rp); if (!rev) @@ -106,8 +106,8 @@ static int revocable_test_provider_release(struct inode *inode, struct revocable_test_provider_priv *priv = filp->private_data; debugfs_remove(priv->dentry); - if (priv->rp) - revocable_provider_revoke(priv->rp); + if (unrcu_pointer(priv->rp)) + revocable_provider_revoke(&priv->rp); kfree(priv); return 0; @@ -137,8 +137,8 @@ static ssize_t revocable_test_provider_write(struct file *filp, * gone. */ if (!strcmp(data, TEST_CMD_RESOURCE_GONE)) { - revocable_provider_revoke(priv->rp); - priv->rp = NULL; + revocable_provider_revoke(&priv->rp); + rcu_assign_pointer(priv->rp, NULL); } else { if (priv->res[0] != '\0') return 0; @@ -146,14 +146,15 @@ static ssize_t revocable_test_provider_write(struct file *filp, strscpy(priv->res, data); priv->rp = revocable_provider_alloc(&priv->res); - if (!priv->rp) + if (!unrcu_pointer(priv->rp)) return -ENOMEM; priv->dentry = debugfs_create_file("consumer", 0400, - debugfs_dir, priv->rp, + debugfs_dir, + unrcu_pointer(priv->rp), &revocable_test_consumer_fops); if (!priv->dentry) { - revocable_provider_revoke(priv->rp); + revocable_provider_revoke(&priv->rp); return -ENOMEM; } } -- cgit v1.2.3 From 377563ce0653031de8d530e8b2f590d13349e29c Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Thu, 29 Jan 2026 14:37:32 +0000 Subject: revocable: fix SRCU index corruption by requiring caller-provided storage The struct revocable handle stores the SRCU read-side index (idx) for the duration of a resource access. If multiple threads share the same struct revocable instance, they race on writing to the idx field, corrupting the SRCU state and potentially causing unsafe unlocks. Refactor the API to replace revocable_alloc()/revocable_free() with revocable_init()/revocable_deinit(). This change requires the caller to provide the storage for struct revocable. By moving storage ownership to the caller, the API ensures that concurrent users maintain their own private idx storage, eliminating the race condition. Reported-by: Johan Hovold Closes: https://lore.kernel.org/all/20260124170535.11756-4-johan@kernel.org/ Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260129143733.45618-4-tzungbi@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../driver-api/driver-model/revocable.rst | 14 ++--- drivers/base/revocable.c | 41 ++++--------- drivers/base/revocable_test.c | 69 ++++++++++------------ include/linux/revocable.h | 54 ++++++++++++----- .../base/revocable/test_modules/revocable_test.c | 37 +++++------- 5 files changed, 102 insertions(+), 113 deletions(-) (limited to 'include/linux') diff --git a/Documentation/driver-api/driver-model/revocable.rst b/Documentation/driver-api/driver-model/revocable.rst index ab7c0af5fbb6..350e7faeccdc 100644 --- a/Documentation/driver-api/driver-model/revocable.rst +++ b/Documentation/driver-api/driver-model/revocable.rst @@ -81,14 +81,14 @@ For Resource Providers For Resource Consumers ---------------------- -.. kernel-doc:: drivers/base/revocable.c +.. kernel-doc:: include/linux/revocable.h :identifiers: revocable .. kernel-doc:: drivers/base/revocable.c - :identifiers: revocable_alloc + :identifiers: revocable_init .. kernel-doc:: drivers/base/revocable.c - :identifiers: revocable_free + :identifiers: revocable_deinit .. kernel-doc:: drivers/base/revocable.c :identifiers: revocable_try_access @@ -104,11 +104,11 @@ Example Usage .. code-block:: c - void consumer_use_resource(struct revocable *rev) + void consumer_use_resource(struct revocable_provider *rp) { struct foo_resource *res; - REVOCABLE_TRY_ACCESS_WITH(rev, res); + REVOCABLE_TRY_ACCESS_WITH(rp, res); // Always check if the resource is valid. if (!res) { pr_warn("Resource is not available\n"); @@ -129,11 +129,11 @@ Example Usage .. code-block:: c - void consumer_use_resource(struct revocable *rev) + void consumer_use_resource(struct revocable_provider *rp) { struct foo_resource *res; - REVOCABLE_TRY_ACCESS_SCOPED(rev, res) { + REVOCABLE_TRY_ACCESS_SCOPED(rp, res) { // Always check if the resource is valid. if (!res) { pr_warn("Resource is not available\n"); diff --git a/drivers/base/revocable.c b/drivers/base/revocable.c index 1bcd1cf54764..8532ca6a371c 100644 --- a/drivers/base/revocable.c +++ b/drivers/base/revocable.c @@ -73,16 +73,6 @@ struct revocable_provider { struct rcu_head rcu; }; -/** - * 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. @@ -145,20 +135,20 @@ void revocable_provider_revoke(struct revocable_provider __rcu **rp_ptr) EXPORT_SYMBOL_GPL(revocable_provider_revoke); /** - * revocable_alloc() - Allocate struct revocable. + * revocable_init() - Initialize struct revocable. * @_rp: The pointer of resource provider. + * @rev: The pointer of resource consumer. * * This holds a refcount to the resource provider. * - * Return: The pointer of struct revocable. NULL on errors. + * Return: 0 on success, -errno otherwise. */ -struct revocable *revocable_alloc(struct revocable_provider __rcu *_rp) +int revocable_init(struct revocable_provider __rcu *_rp, struct revocable *rev) { struct revocable_provider *rp; - struct revocable *rev; if (!_rp) - return NULL; + return -ENODEV; /* * Enter a read-side critical section. @@ -170,43 +160,36 @@ struct revocable *revocable_alloc(struct revocable_provider __rcu *_rp) rp = rcu_dereference(_rp); if (!rp) /* The revocable provider has been revoked. */ - return NULL; + return -ENODEV; if (!kref_get_unless_zero(&rp->kref)) /* * The revocable provider is releasing (i.e., * revocable_provider_release() has been called). */ - return NULL; + return -ENODEV; } /* At this point, `rp` is safe to access as holding a kref of it */ - rev = kzalloc(sizeof(*rev), GFP_KERNEL); - if (!rev) { - kref_put(&rp->kref, revocable_provider_release); - return NULL; - } - rev->rp = rp; - return rev; + return 0; } -EXPORT_SYMBOL_GPL(revocable_alloc); +EXPORT_SYMBOL_GPL(revocable_init); /** - * revocable_free() - Free struct revocable. + * revocable_deinit() - Deinitialize 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 struct. */ -void revocable_free(struct revocable *rev) +void revocable_deinit(struct revocable *rev) { struct revocable_provider *rp = rev->rp; kref_put(&rp->kref, revocable_provider_release); - kfree(rev); } -EXPORT_SYMBOL_GPL(revocable_free); +EXPORT_SYMBOL_GPL(revocable_deinit); /** * revocable_try_access() - Try to access the resource. diff --git a/drivers/base/revocable_test.c b/drivers/base/revocable_test.c index 7fc4d6a3dff6..a2818ec01298 100644 --- a/drivers/base/revocable_test.c +++ b/drivers/base/revocable_test.c @@ -15,7 +15,7 @@ * - Try Access Macro: Same as "Revocation" but uses the * REVOCABLE_TRY_ACCESS_WITH() and REVOCABLE_TRY_ACCESS_SCOPED(). * - * - Provider Use-after-free: Verifies revocable_alloc() correctly handles + * - Provider Use-after-free: Verifies revocable_init() correctly handles * race conditions where the provider is being released. */ @@ -26,20 +26,21 @@ static void revocable_test_basic(struct kunit *test) { struct revocable_provider __rcu *rp; - struct revocable *rev; + struct revocable rev; void *real_res = (void *)0x12345678, *res; + int ret; rp = revocable_provider_alloc(real_res); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); - rev = revocable_alloc(rp); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); + ret = revocable_init(rp, &rev); + KUNIT_ASSERT_EQ(test, ret, 0); - res = revocable_try_access(rev); + res = revocable_try_access(&rev); KUNIT_EXPECT_PTR_EQ(test, res, real_res); - revocable_withdraw_access(rev); + revocable_withdraw_access(&rev); - revocable_free(rev); + revocable_deinit(&rev); revocable_provider_revoke(&rp); KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); } @@ -47,43 +48,40 @@ static void revocable_test_basic(struct kunit *test) static void revocable_test_revocation(struct kunit *test) { struct revocable_provider __rcu *rp; - struct revocable *rev; + struct revocable rev; void *real_res = (void *)0x12345678, *res; + int ret; rp = revocable_provider_alloc(real_res); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); - rev = revocable_alloc(rp); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); + ret = revocable_init(rp, &rev); + KUNIT_ASSERT_EQ(test, ret, 0); - res = revocable_try_access(rev); + res = revocable_try_access(&rev); KUNIT_EXPECT_PTR_EQ(test, res, real_res); - revocable_withdraw_access(rev); + revocable_withdraw_access(&rev); revocable_provider_revoke(&rp); KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); - res = revocable_try_access(rev); + res = revocable_try_access(&rev); KUNIT_EXPECT_PTR_EQ(test, res, NULL); - revocable_withdraw_access(rev); + revocable_withdraw_access(&rev); - revocable_free(rev); + revocable_deinit(&rev); } static void revocable_test_try_access_macro(struct kunit *test) { struct revocable_provider __rcu *rp; - struct revocable *rev; void *real_res = (void *)0x12345678, *res; rp = revocable_provider_alloc(real_res); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); - rev = revocable_alloc(rp); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); - { - REVOCABLE_TRY_ACCESS_WITH(rev, res); + REVOCABLE_TRY_ACCESS_WITH(rp, res); KUNIT_EXPECT_PTR_EQ(test, res, real_res); } @@ -91,28 +89,22 @@ static void revocable_test_try_access_macro(struct kunit *test) KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); { - REVOCABLE_TRY_ACCESS_WITH(rev, res); + REVOCABLE_TRY_ACCESS_WITH(rp, res); KUNIT_EXPECT_PTR_EQ(test, res, NULL); } - - revocable_free(rev); } static void revocable_test_try_access_macro2(struct kunit *test) { struct revocable_provider __rcu *rp; - struct revocable *rev; void *real_res = (void *)0x12345678, *res; bool accessed; rp = revocable_provider_alloc(real_res); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); - rev = revocable_alloc(rp); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); - accessed = false; - REVOCABLE_TRY_ACCESS_SCOPED(rev, res) { + REVOCABLE_TRY_ACCESS_SCOPED(rp, res) { KUNIT_EXPECT_PTR_EQ(test, res, real_res); accessed = true; } @@ -122,32 +114,31 @@ static void revocable_test_try_access_macro2(struct kunit *test) KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); accessed = false; - REVOCABLE_TRY_ACCESS_SCOPED(rev, res) { + REVOCABLE_TRY_ACCESS_SCOPED(rp, res) { KUNIT_EXPECT_PTR_EQ(test, res, NULL); accessed = true; } KUNIT_EXPECT_TRUE(test, accessed); - - revocable_free(rev); } static void revocable_test_provider_use_after_free(struct kunit *test) { struct revocable_provider __rcu *rp; struct revocable_provider *old_rp; + struct revocable rev; void *real_res = (void *)0x12345678; - struct revocable *rev; + int ret; rp = revocable_provider_alloc(real_res); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); - rev = revocable_alloc(NULL); - KUNIT_EXPECT_PTR_EQ(test, rev, NULL); + ret = revocable_init(NULL, &rev); + KUNIT_EXPECT_NE(test, ret, 0); /* Simulate the provider has been freed. */ old_rp = rcu_replace_pointer(rp, NULL, 1); - rev = revocable_alloc(rp); - KUNIT_EXPECT_PTR_EQ(test, rev, NULL); + ret = revocable_init(rp, &rev); + KUNIT_EXPECT_NE(test, ret, 0); rcu_replace_pointer(rp, old_rp, 1); struct { @@ -159,12 +150,14 @@ static void revocable_test_provider_use_after_free(struct kunit *test) /* Simulate the provider is releasing. */ refcount_set(&rp_internal->kref.refcount, 0); - rev = revocable_alloc(rp); - KUNIT_EXPECT_PTR_EQ(test, rev, NULL); + ret = revocable_init(rp, &rev); + KUNIT_EXPECT_NE(test, ret, 0); refcount_set(&rp_internal->kref.refcount, 1); revocable_provider_revoke(&rp); KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); + ret = revocable_init(rp, &rev); + KUNIT_EXPECT_NE(test, ret, 0); } static struct kunit_case revocable_test_cases[] = { diff --git a/include/linux/revocable.h b/include/linux/revocable.h index d5da3584adbe..e3d6d2c953a3 100644 --- a/include/linux/revocable.h +++ b/include/linux/revocable.h @@ -10,27 +10,46 @@ #include struct device; -struct revocable; struct revocable_provider; +/** + * 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; +}; + struct revocable_provider __rcu *revocable_provider_alloc(void *res); void revocable_provider_revoke(struct revocable_provider __rcu **rp); -struct revocable *revocable_alloc(struct revocable_provider __rcu *rp); -void revocable_free(struct revocable *rev); +int revocable_init(struct revocable_provider __rcu *rp, struct revocable *rev); +void revocable_deinit(struct revocable *rev); void *revocable_try_access(struct revocable *rev) __acquires(&rev->rp->srcu); void revocable_withdraw_access(struct revocable *rev) __releases(&rev->rp->srcu); -DEFINE_FREE(access_rev, struct revocable *, if (_T) revocable_withdraw_access(_T)) +DEFINE_FREE(access_rev, struct revocable *, { + if ((_T)->idx != -1) + revocable_withdraw_access(_T); + if ((_T)->rp) + revocable_deinit(_T); +}) + +#define _REVOCABLE_TRY_ACCESS_WITH(_rp, _rev, _res) \ + struct revocable _rev = {.rp = NULL, .idx = -1}; \ + struct revocable *__UNIQUE_ID(name) __free(access_rev) = &_rev; \ + _res = revocable_init(_rp, &_rev) ? NULL : revocable_try_access(&_rev) /** * REVOCABLE_TRY_ACCESS_WITH() - A helper for accessing revocable resource - * @_rev: The consumer's ``struct revocable *`` handle. + * @_rp: The provider's ``struct revocable_provider *`` handle. * @_res: A pointer variable that will be assigned the resource. * * The macro simplifies the access-release cycle for consumers, ensuring that - * revocable_withdraw_access() is always called, even in the case of an early - * exit. + * corresponding revocable_withdraw_access() and revocable_deinit() are called, + * even in the case of an early exit. * * It creates a local variable in the current scope. @_res is populated with * the result of revocable_try_access(). The consumer code **must** check if @@ -41,13 +60,15 @@ DEFINE_FREE(access_rev, struct revocable *, if (_T) revocable_withdraw_access(_T * are allowed before the helper. Otherwise, the compiler fails with * "jump bypasses initialization of variable with __attribute__((cleanup))". */ -#define REVOCABLE_TRY_ACCESS_WITH(_rev, _res) \ - struct revocable *__UNIQUE_ID(name) __free(access_rev) = _rev; \ - _res = revocable_try_access(_rev) +#define REVOCABLE_TRY_ACCESS_WITH(_rp, _res) \ + _REVOCABLE_TRY_ACCESS_WITH(_rp, __UNIQUE_ID(name), _res) -#define _REVOCABLE_TRY_ACCESS_SCOPED(_rev, _label, _res) \ - for (struct revocable *__UNIQUE_ID(name) __free(access_rev) = _rev; \ - (_res = revocable_try_access(_rev)) || true; ({ goto _label; })) \ +#define _REVOCABLE_TRY_ACCESS_SCOPED(_rp, _rev, _label, _res) \ + for (struct revocable _rev = {.rp = NULL, .idx = -1}, \ + *__UNIQUE_ID(name) __free(access_rev) = &_rev; \ + (_res = revocable_init(_rp, &_rev) ? NULL : \ + revocable_try_access(&_rev)) || true; \ + ({ goto _label; })) \ if (0) { \ _label: \ break; \ @@ -55,13 +76,14 @@ _label: \ /** * REVOCABLE_TRY_ACCESS_SCOPED() - A helper for accessing revocable resource - * @_rev: The consumer's ``struct revocable *`` handle. + * @_rp: The provider's ``struct revocable_provider *`` handle. * @_res: A pointer variable that will be assigned the resource. * * Similar to REVOCABLE_TRY_ACCESS_WITH() but with an explicit scope from a * temporary ``for`` loop. */ -#define REVOCABLE_TRY_ACCESS_SCOPED(_rev, _res) \ - _REVOCABLE_TRY_ACCESS_SCOPED(_rev, __UNIQUE_ID(label), _res) +#define REVOCABLE_TRY_ACCESS_SCOPED(_rp, _res) \ + _REVOCABLE_TRY_ACCESS_SCOPED(_rp, __UNIQUE_ID(name), \ + __UNIQUE_ID(label), _res) #endif /* __LINUX_REVOCABLE_H */ diff --git a/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c b/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c index ae6c67e65f3d..a560ceda7318 100644 --- a/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c +++ b/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c @@ -24,23 +24,7 @@ struct revocable_test_provider_priv { static int revocable_test_consumer_open(struct inode *inode, struct file *filp) { - struct revocable *rev; - struct revocable_provider __rcu *rp = inode->i_private; - - rev = revocable_alloc(rp); - if (!rev) - return -ENOMEM; - filp->private_data = rev; - - return 0; -} - -static int revocable_test_consumer_release(struct inode *inode, - struct file *filp) -{ - struct revocable *rev = filp->private_data; - - revocable_free(rev); + filp->private_data = inode->i_private; return 0; } @@ -48,25 +32,33 @@ static ssize_t revocable_test_consumer_read(struct file *filp, char __user *buf, size_t count, loff_t *offset) { + int ret; char *res; char data[16]; size_t len; - struct revocable *rev = filp->private_data; + struct revocable rev; + struct revocable_provider __rcu *rp = filp->private_data; switch (*offset) { case 0: - res = revocable_try_access(rev); + ret = revocable_init(rp, &rev); + if (ret) { + snprintf(data, sizeof(data), "%s", "(null)"); + break; + } + res = revocable_try_access(&rev); snprintf(data, sizeof(data), "%s", res ?: "(null)"); - revocable_withdraw_access(rev); + revocable_withdraw_access(&rev); + revocable_deinit(&rev); break; case TEST_MAGIC_OFFSET: { - REVOCABLE_TRY_ACCESS_WITH(rev, res); + REVOCABLE_TRY_ACCESS_WITH(rp, res); snprintf(data, sizeof(data), "%s", res ?: "(null)"); } break; case TEST_MAGIC_OFFSET2: - REVOCABLE_TRY_ACCESS_SCOPED(rev, res) + REVOCABLE_TRY_ACCESS_SCOPED(rp, res) snprintf(data, sizeof(data), "%s", res ?: "(null)"); break; default: @@ -83,7 +75,6 @@ static ssize_t revocable_test_consumer_read(struct file *filp, static const struct file_operations revocable_test_consumer_fops = { .open = revocable_test_consumer_open, - .release = revocable_test_consumer_release, .read = revocable_test_consumer_read, .llseek = default_llseek, }; -- cgit v1.2.3 From 21bab791346e5b7902a04709231c0642ff6d69bc Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 4 Feb 2026 15:28:49 +0100 Subject: Revert "revocable: Revocable resource management" This reverts commit 62eb557580eb2177cf16c3fd2b6efadff297b29a. The revocable implementation uses two separate abstractions, struct revocable_provider and struct revocable, in order to store the SRCU read lock index which must be passed unaltered to srcu_read_unlock() in the same context when a resource is no longer needed. With the merged revocable API, multiple threads could however share the same struct revocable and therefore potentially overwrite the SRCU index of another thread which can cause the SRCU synchronisation in revocable_provider_revoke() to never complete. [1] An example revocable conversion of the gpiolib code also turned out to be fundamentally flawed and could lead to use-after-free. [2] An attempt to address both issues was quickly put together and merged, but revocable is still fundamentally broken. [3] Specifically, the latest design relies on RCU for storing a pointer to the revocable provider, but since the resource can be shared by value (e.g. as in the now reverted selftests) this does not work at all and can also lead to use-after-free: static void revocable_provider_release(struct kref *kref) { struct revocable_provider *rp = container_of(kref, struct revocable_provider, kref); cleanup_srcu_struct(&rp->srcu); kfree_rcu(rp, rcu); } void revocable_provider_revoke(struct revocable_provider __rcu **rp_ptr) { struct revocable_provider *rp; rp = rcu_replace_pointer(*rp_ptr, NULL, 1); ... kref_put(&rp->kref, revocable_provider_release); } int revocable_init(struct revocable_provider __rcu *_rp, struct revocable *rev) { struct revocable_provider *rp; ... scoped_guard(rcu) { rp = rcu_dereference(_rp); if (!rp) return -ENODEV; if (!kref_get_unless_zero(&rp->kref)) return -ENODEV; } ... } producer: priv->rp = revocable_provider_alloc(&priv->res); // pass priv->rp by value to consumer revocable_provider_revoke(&priv->rp); consumer: struct revocable_provider __rcu *rp = filp->private_data; struct revocable *rev; revocable_init(rp, &rev); as _rp would still be non-NULL in revocable_init() regardless of whether the producer has revoked the resource and set its pointer to NULL. Essentially revocable still relies on having a pointer to reference counted driver data which holds the revocable provider, which makes all the RCU protection unnecessary along with most of the current revocable design and implementation. As the above shows, and as has been pointed out repeatedly elsewhere, these kind of issues are not something that should be addressed incrementally. [4] Revert the revocable implementation until a redesign has been proposed and evaluated properly. Link: https://lore.kernel.org/all/20260124170535.11756-4-johan@kernel.org/ [1] Link: https://lore.kernel.org/all/aXT45B6vLf9R3Pbf@hovoldconsulting.com/ [2] Link: https://lore.kernel.org/all/20260129143733.45618-1-tzungbi@kernel.org/ [3] Link: https://lore.kernel.org/all/aXobzoeooJqxMkEj@hovoldconsulting.com/ [4] Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260204142849.22055-4-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- Documentation/driver-api/driver-model/index.rst | 1 - .../driver-api/driver-model/revocable.rst | 149 -------------- MAINTAINERS | 7 - drivers/base/revocable.c | 225 --------------------- include/linux/revocable.h | 89 -------- 5 files changed, 471 deletions(-) delete mode 100644 Documentation/driver-api/driver-model/revocable.rst delete mode 100644 drivers/base/revocable.c delete mode 100644 include/linux/revocable.h (limited to 'include/linux') diff --git a/Documentation/driver-api/driver-model/index.rst b/Documentation/driver-api/driver-model/index.rst index 8e1ee21185df..4831bdd92e5c 100644 --- a/Documentation/driver-api/driver-model/index.rst +++ b/Documentation/driver-api/driver-model/index.rst @@ -14,7 +14,6 @@ Driver Model overview platform porting - revocable .. only:: subproject and html diff --git a/Documentation/driver-api/driver-model/revocable.rst b/Documentation/driver-api/driver-model/revocable.rst deleted file mode 100644 index 350e7faeccdc..000000000000 --- a/Documentation/driver-api/driver-model/revocable.rst +++ /dev/null @@ -1,149 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0 - -============================== -Revocable Resource Management -============================== - -Overview -======== - -.. kernel-doc:: drivers/base/revocable.c - :doc: Overview - -Revocable vs. Devres (devm) -=========================== - -It's important to understand the distinct roles of the Revocable and Devres, -and how they can complement each other. They address different problems in -resource management: - -* **Devres:** Primarily address **resource leaks**. The lifetime of the - resources is tied to the lifetime of the device. The resource is - automatically freed when the device is unbound. This cleanup happens - irrespective of any potential active users. - -* **Revocable:** Primarily addresses **invalid memory access**, - such as Use-After-Free (UAF). It's an independent synchronization - primitive that decouples consumer access from the resource's actual - presence. Consumers interact with a "revocable object" (an intermediary), - not the underlying resource directly. This revocable object persists as - long as there are active references to it from consumer handles. - -**Key Distinctions & How They Complement Each Other:** - -1. **Reference Target:** Consumers of a resource managed by the Revocable - mechanism hold a reference to the *revocable object*, not the - encapsulated resource itself. - -2. **Resource Lifetime vs. Access:** The underlying resource's lifetime is - independent of the number of references to the revocable object. The - resource can be freed at any point. A common scenario is the resource - being freed by `devres` when the providing device is unbound. - -3. **Safe Access:** Revocable provides a safe way to attempt access. Before - using the resource, a consumer uses the Revocable API (e.g., - revocable_try_access()). This function checks if the resource is still - valid. It returns a pointer to the resource only if it hasn't been - revoked; otherwise, it returns NULL. This prevents UAF by providing a - clear signal that the resource is gone. - -4. **Complementary Usage:** `devres` and Revocable work well together. - `devres` can handle the automatic allocation and deallocation of a - resource tied to a device. The Revocable mechanism can be layered on top - to provide safe access for consumers whose lifetimes might extend beyond - the provider device's lifetime. For instance, a userspace program might - keep a character device file open even after the physical device has been - removed. In this case: - - * `devres` frees the device-specific resource upon unbinding. - * The Revocable mechanism ensures that any subsequent operations on the - open file handle, which attempt to access the now-freed resource, - will fail gracefully (e.g., revocable_try_access() returns NULL) - instead of causing a UAF. - -In summary, `devres` ensures resources are *released* to prevent leaks, while -the Revocable mechanism ensures that attempts to *access* these resources are -done safely, even if the resource has been released. - -API and Usage -============= - -For Resource Providers ----------------------- -.. kernel-doc:: drivers/base/revocable.c - :identifiers: revocable_provider - -.. kernel-doc:: drivers/base/revocable.c - :identifiers: revocable_provider_alloc - -.. kernel-doc:: drivers/base/revocable.c - :identifiers: revocable_provider_revoke - -For Resource Consumers ----------------------- -.. kernel-doc:: include/linux/revocable.h - :identifiers: revocable - -.. kernel-doc:: drivers/base/revocable.c - :identifiers: revocable_init - -.. kernel-doc:: drivers/base/revocable.c - :identifiers: revocable_deinit - -.. kernel-doc:: drivers/base/revocable.c - :identifiers: revocable_try_access - -.. kernel-doc:: drivers/base/revocable.c - :identifiers: revocable_withdraw_access - -.. kernel-doc:: include/linux/revocable.h - :identifiers: REVOCABLE_TRY_ACCESS_WITH - -Example Usage -~~~~~~~~~~~~~ - -.. code-block:: c - - void consumer_use_resource(struct revocable_provider *rp) - { - struct foo_resource *res; - - REVOCABLE_TRY_ACCESS_WITH(rp, 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_withdraw_access() is automatically called here. - -.. kernel-doc:: include/linux/revocable.h - :identifiers: REVOCABLE_TRY_ACCESS_SCOPED - -Example Usage -~~~~~~~~~~~~~ - -.. code-block:: c - - void consumer_use_resource(struct revocable_provider *rp) - { - struct foo_resource *res; - - REVOCABLE_TRY_ACCESS_SCOPED(rp, 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_withdraw_access() is automatically called here. - } diff --git a/MAINTAINERS b/MAINTAINERS index 93c07c645c68..1c5ceccc9928 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22385,13 +22385,6 @@ F: include/uapi/linux/rseq.h F: kernel/rseq.c F: tools/testing/selftests/rseq/ -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/revocable.c b/drivers/base/revocable.c deleted file mode 100644 index 8532ca6a371c..000000000000 --- a/drivers/base/revocable.c +++ /dev/null @@ -1,225 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright 2026 Google LLC - * - * Revocable resource management - */ - -#include -#include -#include -#include -#include - -/** - * DOC: Overview - * - * The "revocable" mechanism is a synchronization primitive designed to manage - * safe access to resources that can be asynchronously removed or invalidated. - * Its primary purpose is to prevent Use-After-Free (UAF) errors when - * interacting with resources whose lifetimes are not guaranteed to outlast - * their consumers. - * - * This is particularly useful in systems where resources can disappear - * unexpectedly, such as those provided by hot-pluggable devices like USB. - * When a consumer holds a reference to such a resource, the underlying device - * might be removed, causing the resource's memory to be freed. Subsequent - * access attempts by the consumer would then lead to UAF errors. - * - * Revocable addresses this by providing a form of "weak reference" and a - * controlled access method. It allows a resource consumer to safely attempt to - * access the resource. The mechanism guarantees that any access granted is - * valid for the duration of its use. If the resource has already been - * revoked (i.e., freed), the access attempt will fail safely, typically by - * returning NULL, instead of causing a crash. - * - * The implementation uses a provider/consumer model built on Sleepable - * RCU (SRCU) to guarantee safe memory access: - * - * - A resource provider, such as a driver for a hot-pluggable device, - * 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 acts as a handle containing 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_withdraw_access() to exit the SRCU critical section. The - * REVOCABLE_TRY_ACCESS_WITH() and REVOCABLE_TRY_ACCESS_SCOPED() are - * convenient helpers for doing that. - * - * - When the provider needs to remove the resource, it calls - * revocable_provider_revoke(). 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. - * @rcu: The RCU to protect pointer to itself. - */ -struct revocable_provider { - struct srcu_struct srcu; - void __rcu *res; - struct kref kref; - struct rcu_head rcu; -}; - -/** - * 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. - * It enforces the caller handles the returned pointer in RCU ways. - */ -struct revocable_provider __rcu *revocable_provider_alloc(void *res) -{ - struct revocable_provider *rp; - - rp = kzalloc(sizeof(*rp), GFP_KERNEL); - if (!rp) - return NULL; - - init_srcu_struct(&rp->srcu); - RCU_INIT_POINTER(rp->res, res); - kref_init(&rp->kref); - - return (struct revocable_provider __rcu *)rp; -} -EXPORT_SYMBOL_GPL(revocable_provider_alloc); - -static void revocable_provider_release(struct kref *kref) -{ - struct revocable_provider *rp = container_of(kref, - struct revocable_provider, kref); - - cleanup_srcu_struct(&rp->srcu); - kfree_rcu(rp, rcu); -} - -/** - * revocable_provider_revoke() - Revoke the managed resource. - * @rp_ptr: The pointer of 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 struct. - * - * It enforces the caller to pass a pointer of pointer of resource provider so - * that it sets \*rp_ptr to NULL to prevent from keeping a dangling pointer. - */ -void revocable_provider_revoke(struct revocable_provider __rcu **rp_ptr) -{ - struct revocable_provider *rp; - - rp = rcu_replace_pointer(*rp_ptr, NULL, 1); - if (!rp) - return; - - rcu_assign_pointer(rp->res, NULL); - synchronize_srcu(&rp->srcu); - kref_put(&rp->kref, revocable_provider_release); -} -EXPORT_SYMBOL_GPL(revocable_provider_revoke); - -/** - * revocable_init() - Initialize struct revocable. - * @_rp: The pointer of resource provider. - * @rev: The pointer of resource consumer. - * - * This holds a refcount to the resource provider. - * - * Return: 0 on success, -errno otherwise. - */ -int revocable_init(struct revocable_provider __rcu *_rp, struct revocable *rev) -{ - struct revocable_provider *rp; - - if (!_rp) - return -ENODEV; - - /* - * Enter a read-side critical section. - * - * This prevents kfree_rcu() from freeing the struct revocable_provider - * memory, for the duration of this scope. - */ - scoped_guard(rcu) { - rp = rcu_dereference(_rp); - if (!rp) - /* The revocable provider has been revoked. */ - return -ENODEV; - - if (!kref_get_unless_zero(&rp->kref)) - /* - * The revocable provider is releasing (i.e., - * revocable_provider_release() has been called). - */ - return -ENODEV; - } - /* At this point, `rp` is safe to access as holding a kref of it */ - - rev->rp = rp; - return 0; -} -EXPORT_SYMBOL_GPL(revocable_init); - -/** - * revocable_deinit() - Deinitialize 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 struct. - */ -void revocable_deinit(struct revocable *rev) -{ - struct revocable_provider *rp = rev->rp; - - kref_put(&rp->kref, revocable_provider_release); -} -EXPORT_SYMBOL_GPL(revocable_deinit); - -/** - * 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->srcu) -{ - struct revocable_provider *rp = rev->rp; - - rev->idx = srcu_read_lock(&rp->srcu); - return srcu_dereference(rp->res, &rp->srcu); -} -EXPORT_SYMBOL_GPL(revocable_try_access); - -/** - * revocable_withdraw_access() - 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_withdraw_access(struct revocable *rev) __releases(&rev->rp->srcu) -{ - struct revocable_provider *rp = rev->rp; - - srcu_read_unlock(&rp->srcu, rev->idx); -} -EXPORT_SYMBOL_GPL(revocable_withdraw_access); diff --git a/include/linux/revocable.h b/include/linux/revocable.h deleted file mode 100644 index e3d6d2c953a3..000000000000 --- a/include/linux/revocable.h +++ /dev/null @@ -1,89 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Copyright 2026 Google LLC - */ - -#ifndef __LINUX_REVOCABLE_H -#define __LINUX_REVOCABLE_H - -#include -#include - -struct device; -struct revocable_provider; - -/** - * 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; -}; - -struct revocable_provider __rcu *revocable_provider_alloc(void *res); -void revocable_provider_revoke(struct revocable_provider __rcu **rp); - -int revocable_init(struct revocable_provider __rcu *rp, struct revocable *rev); -void revocable_deinit(struct revocable *rev); -void *revocable_try_access(struct revocable *rev) __acquires(&rev->rp->srcu); -void revocable_withdraw_access(struct revocable *rev) __releases(&rev->rp->srcu); - -DEFINE_FREE(access_rev, struct revocable *, { - if ((_T)->idx != -1) - revocable_withdraw_access(_T); - if ((_T)->rp) - revocable_deinit(_T); -}) - -#define _REVOCABLE_TRY_ACCESS_WITH(_rp, _rev, _res) \ - struct revocable _rev = {.rp = NULL, .idx = -1}; \ - struct revocable *__UNIQUE_ID(name) __free(access_rev) = &_rev; \ - _res = revocable_init(_rp, &_rev) ? NULL : revocable_try_access(&_rev) - -/** - * REVOCABLE_TRY_ACCESS_WITH() - A helper for accessing revocable resource - * @_rp: The provider's ``struct revocable_provider *`` handle. - * @_res: A pointer variable that will be assigned the resource. - * - * The macro simplifies the access-release cycle for consumers, ensuring that - * corresponding revocable_withdraw_access() and revocable_deinit() are called, - * even in the case of an early exit. - * - * It creates a local variable in the current scope. @_res is populated with - * the result of revocable_try_access(). The consumer code **must** check if - * @_res is ``NULL`` before using it. The revocable_withdraw_access() function - * is automatically called when the scope is exited. - * - * Note: It shares the same issue with guard() in cleanup.h. No goto statements - * are allowed before the helper. Otherwise, the compiler fails with - * "jump bypasses initialization of variable with __attribute__((cleanup))". - */ -#define REVOCABLE_TRY_ACCESS_WITH(_rp, _res) \ - _REVOCABLE_TRY_ACCESS_WITH(_rp, __UNIQUE_ID(name), _res) - -#define _REVOCABLE_TRY_ACCESS_SCOPED(_rp, _rev, _label, _res) \ - for (struct revocable _rev = {.rp = NULL, .idx = -1}, \ - *__UNIQUE_ID(name) __free(access_rev) = &_rev; \ - (_res = revocable_init(_rp, &_rev) ? NULL : \ - revocable_try_access(&_rev)) || true; \ - ({ goto _label; })) \ - if (0) { \ -_label: \ - break; \ - } else - -/** - * REVOCABLE_TRY_ACCESS_SCOPED() - A helper for accessing revocable resource - * @_rp: The provider's ``struct revocable_provider *`` handle. - * @_res: A pointer variable that will be assigned the resource. - * - * Similar to REVOCABLE_TRY_ACCESS_WITH() but with an explicit scope from a - * temporary ``for`` loop. - */ -#define REVOCABLE_TRY_ACCESS_SCOPED(_rp, _res) \ - _REVOCABLE_TRY_ACCESS_SCOPED(_rp, __UNIQUE_ID(name), \ - __UNIQUE_ID(label), _res) - -#endif /* __LINUX_REVOCABLE_H */ -- cgit v1.2.3