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 */