diff options
| -rw-r--r-- | Documentation/driver-api/driver-model/index.rst | 1 | ||||
| -rw-r--r-- | Documentation/driver-api/driver-model/revocable.rst | 149 | ||||
| -rw-r--r-- | MAINTAINERS | 7 | ||||
| -rw-r--r-- | drivers/base/revocable.c | 225 | ||||
| -rw-r--r-- | include/linux/revocable.h | 89 |
5 files changed, 0 insertions, 471 deletions
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 <tzungbi@kernel.org> -L: linux-kernel@vger.kernel.org -S: Maintained -F: drivers/base/revocable.c -F: include/linux/revocable.h - RFKILL M: Johannes Berg <johannes@sipsolutions.net> 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 <linux/device.h> -#include <linux/kref.h> -#include <linux/revocable.h> -#include <linux/slab.h> -#include <linux/srcu.h> - -/** - * 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 <linux/compiler.h> -#include <linux/cleanup.h> - -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 */ |
