linux-doc.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 0/7] platform/chrome: Fix a possible UAF via revocable
@ 2025-09-23  7:52 Tzung-Bi Shih
  2025-09-23  7:52 ` [PATCH v4 1/7] revocable: Revocable resource management Tzung-Bi Shih
                   ` (6 more replies)
  0 siblings, 7 replies; 11+ messages in thread
From: Tzung-Bi Shih @ 2025-09-23  7:52 UTC (permalink / raw)
  To: Benson Leung, Greg Kroah-Hartman, Rafael J . Wysocki,
	Danilo Krummrich
  Cc: Jonathan Corbet, Shuah Khan, Dawid Niedzwiecki, linux-doc,
	linux-kernel, chrome-platform, linux-kselftest, tzungbi,
	Laurent Pinchart, Bartosz Golaszewski, Wolfram Sang

This is a follow-up series of [1].  It tries to fix a possible UAF in the
fops of cros_ec_chardev after the underlying protocol device has gone by
using revocable.

The 1st patch introduces the revocable which is an implementation of ideas
from the talk [2].

The 2nd and 3rd patches add test cases for revocable in Kunit and selftest.

The 4th patch converts existing protocol devices to resource providers
of cros_ec_device.

The 5th - 7th are PoC patches for moving most revocable code to subsystem
level.  Miscdevice is used as it would be simpler for PoC.  Note that the
device driver (e.g., cros_ec_chardev) still needs to be revocable-aware.
The driver needs to specify where to save the pointer and thus the resource
is available in fops.
- The 5th patch adds a helper for using revocable API with fops.
- The 6th patch leverages the helper in miscdevice.
- The 7th patch converts cros_ec_chardev to a resource consumer of
  cros_ec_device to fix the UAF.

[1] https://lore.kernel.org/chrome-platform/20250721044456.2736300-6-tzungbi@kernel.org
[2] https://lpc.events/event/17/contributions/1627/

v4:
- Rebase onto next-20250922.
- Remove the 5th patch from v3.
- Add fops replacement PoC in 5th - 7th patches.

v3: https://lore.kernel.org/chrome-platform/20250912081718.3827390-1-tzungbi@kernel.org
- Rebase onto https://lore.kernel.org/chrome-platform/20250828083601.856083-1-tzungbi@kernel.org
  and next-20250912.
- The 4th patch changed accordingly.

v2: https://lore.kernel.org/chrome-platform/20250820081645.847919-1-tzungbi@kernel.org
- Rename "ref_proxy" -> "revocable".
- Add test cases in Kunit and selftest.

v1: https://lore.kernel.org/chrome-platform/20250814091020.1302888-1-tzungbi@kernel.org

Tzung-Bi Shih (7):
  revocable: Revocable resource management
  revocable: Add Kunit test cases
  selftests: revocable: Add kselftest cases
  platform/chrome: Protect cros_ec_device lifecycle with revocable
  revocable: Add fops replacement
  char: misc: Leverage revocable fops replacement
  platform/chrome: cros_ec_chardev: Secure cros_ec_device via revocable

 .../driver-api/driver-model/index.rst         |   1 +
 .../driver-api/driver-model/revocable.rst     |  87 ++++
 MAINTAINERS                                   |   9 +
 drivers/base/Kconfig                          |   8 +
 drivers/base/Makefile                         |   5 +-
 drivers/base/revocable.c                      | 374 ++++++++++++++++++
 drivers/base/revocable_test.c                 | 110 ++++++
 drivers/char/misc.c                           |   7 +
 drivers/platform/chrome/cros_ec.c             |   5 +
 drivers/platform/chrome/cros_ec_chardev.c     |  15 +-
 include/linux/miscdevice.h                    |   3 +
 include/linux/platform_data/cros_ec_proto.h   |   4 +
 include/linux/revocable.h                     |  60 +++
 tools/testing/selftests/Makefile              |   1 +
 .../selftests/drivers/base/revocable/Makefile |   7 +
 .../drivers/base/revocable/revocable_test.c   | 116 ++++++
 .../drivers/base/revocable/test-revocable.sh  |  39 ++
 .../base/revocable/test_modules/Makefile      |  10 +
 .../revocable/test_modules/revocable_test.c   | 188 +++++++++
 19 files changed, 1047 insertions(+), 2 deletions(-)
 create mode 100644 Documentation/driver-api/driver-model/revocable.rst
 create mode 100644 drivers/base/revocable.c
 create mode 100644 drivers/base/revocable_test.c
 create mode 100644 include/linux/revocable.h
 create mode 100644 tools/testing/selftests/drivers/base/revocable/Makefile
 create mode 100644 tools/testing/selftests/drivers/base/revocable/revocable_test.c
 create mode 100755 tools/testing/selftests/drivers/base/revocable/test-revocable.sh
 create mode 100644 tools/testing/selftests/drivers/base/revocable/test_modules/Makefile
 create mode 100644 tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c

-- 
2.51.0.534.gc79095c0ca-goog


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v4 1/7] revocable: Revocable resource management
  2025-09-23  7:52 [PATCH v4 0/7] platform/chrome: Fix a possible UAF via revocable Tzung-Bi Shih
@ 2025-09-23  7:52 ` Tzung-Bi Shih
  2025-09-23  7:52 ` [PATCH v4 2/7] revocable: Add Kunit test cases Tzung-Bi Shih
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 11+ messages in thread
From: Tzung-Bi Shih @ 2025-09-23  7:52 UTC (permalink / raw)
  To: Benson Leung, Greg Kroah-Hartman, Rafael J . Wysocki,
	Danilo Krummrich
  Cc: Jonathan Corbet, Shuah Khan, Dawid Niedzwiecki, linux-doc,
	linux-kernel, chrome-platform, linux-kselftest, tzungbi,
	Laurent Pinchart, Bartosz Golaszewski, Wolfram Sang,
	Simona Vetter

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() is a convenient helper 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 <dakr@kernel.org>
Acked-by: Simona Vetter <simona.vetter@ffwll.ch>
Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v4:
- Rename:
  - revocable_provider_free() -> revocable_provider_revoke().
  - REVOCABLE() -> REVOCABLE_TRY_ACCESS_WITH().
  - revocable_release() -> revocable_withdraw_access().
- rcu_dereference() -> srcu_dereference() to fix a warning from lock debugging.
- Move most docs to kernel-doc, include them in Documentation/, and modify the
  commit message accordingly.
- Fix some doc errors.
- Add Acked-by tags.

v3: https://lore.kernel.org/chrome-platform/20250912081718.3827390-2-tzungbi@kernel.org
- No changes.

v2: https://lore.kernel.org/chrome-platform/20250820081645.847919-2-tzungbi@kernel.org
- Rename "ref_proxy" -> "revocable".
- Add introduction in kernel-doc format in revocable.c.
- Add MAINTAINERS entry.
- Add copyright.
- Move from lib/ to drivers/base/.
- EXPORT_SYMBOL() -> EXPORT_SYMBOL_GPL().
- Add Documentation/.
- Rename _get() -> try_access(); _put() -> release().
- Fix a sparse warning by removing the redundant __rcu annotations.
- Fix a sparse warning by adding __acquires() and __releases() annotations.

v1: https://lore.kernel.org/chrome-platform/20250814091020.1302888-2-tzungbi@kernel.org

Note (for my reference):
- An optional .release() callback for revocable provider-managed resource
  hasn't added.
- `make O=build SPHINXDIRS=driver-api/driver-model/ htmldocs` a way to
  verify the Documentation/.

 .../driver-api/driver-model/index.rst         |   1 +
 .../driver-api/driver-model/revocable.rst     |  87 +++++++
 MAINTAINERS                                   |   7 +
 drivers/base/Makefile                         |   2 +-
 drivers/base/revocable.c                      | 233 ++++++++++++++++++
 include/linux/revocable.h                     |  53 ++++
 6 files changed, 382 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/driver-api/driver-model/revocable.rst
 create mode 100644 drivers/base/revocable.c
 create mode 100644 include/linux/revocable.h

diff --git a/Documentation/driver-api/driver-model/index.rst b/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..ce2d0eb1c8cb
--- /dev/null
+++ b/Documentation/driver-api/driver-model/revocable.rst
@@ -0,0 +1,87 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+==============================
+Revocable Resource Management
+==============================
+
+Overview
+========
+
+.. kernel-doc:: drivers/base/revocable.c
+   :doc: Overview
+
+Revocable vs. Device-Managed (devm) Resources
+=============================================
+
+It's important to understand the distinction between a standard
+device-managed (devm) resource and a resource managed by a revocable provider.
+
+The key difference is their lifetime:
+
+*   A **devm resource** is tied to the lifetime of the device.  It is
+    automatically freed when the device is unbound.
+*   A **revocable provider** persists as long as there are active references
+    to it from consumer handles.
+
+This means that a revocable provider can outlive the device that created
+it.  This is a deliberate design feature, allowing consumers to hold a
+reference to a resource even after the underlying device has been removed,
+without causing a fault.  When the consumer attempts to access the resource,
+it will simply be informed that the resource is no longer available.
+
+API and Usage
+=============
+
+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
+
+For Resource Consumers
+----------------------
+
+.. 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.
+    }
diff --git a/MAINTAINERS b/MAINTAINERS
index 0c8281ea4cc6..15d45289fda6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21952,6 +21952,13 @@ 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/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..f8dd4363a87b
--- /dev/null
+++ b/drivers/base/revocable.c
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025 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
+ *
+ * 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() is a convenient helper 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..7177bf045d9c
--- /dev/null
+++ b/include/linux/revocable.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2025 Google LLC
+ */
+
+#ifndef __LINUX_REVOCABLE_H
+#define __LINUX_REVOCABLE_H
+
+#include <linux/cleanup.h>
+
+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(revocable, struct revocable *, if (_T) revocable_withdraw_access(_T))
+
+#define _REVOCABLE_TRY_ACCESS_WITH(_rev, _label, _res)				\
+	for (struct revocable *__UNIQUE_ID(name) __free(revocable) = _rev;	\
+	     (_res = revocable_try_access(_rev)) || true; ({ goto _label; }))	\
+		if (0) {							\
+_label:										\
+			break;							\
+		} else
+
+/**
+ * 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 ``for`` loop that executes exactly once.  Inside the loop,
+ * @_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
+ * of the loop is exited.
+ */
+#define REVOCABLE_TRY_ACCESS_WITH(_rev, _res)					\
+		_REVOCABLE_TRY_ACCESS_WITH(_rev, __UNIQUE_ID(label), _res)
+
+#endif /* __LINUX_REVOCABLE_H */
-- 
2.51.0.534.gc79095c0ca-goog


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 2/7] revocable: Add Kunit test cases
  2025-09-23  7:52 [PATCH v4 0/7] platform/chrome: Fix a possible UAF via revocable Tzung-Bi Shih
  2025-09-23  7:52 ` [PATCH v4 1/7] revocable: Revocable resource management Tzung-Bi Shih
@ 2025-09-23  7:52 ` Tzung-Bi Shih
  2025-09-23  7:52 ` [PATCH v4 3/7] selftests: revocable: Add kselftest cases Tzung-Bi Shih
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 11+ messages in thread
From: Tzung-Bi Shih @ 2025-09-23  7:52 UTC (permalink / raw)
  To: Benson Leung, Greg Kroah-Hartman, Rafael J . Wysocki,
	Danilo Krummrich
  Cc: Jonathan Corbet, Shuah Khan, Dawid Niedzwiecki, linux-doc,
	linux-kernel, chrome-platform, linux-kselftest, tzungbi,
	Laurent Pinchart, Bartosz Golaszewski, Wolfram Sang

Add Kunit test cases for the revocable API.

The test cases cover the following scenarios:
- Basic: Verifies that a consumer can successfully access the resource
  provided via the provider.
- Revocation: Verifies that after the provider revokes the resource,
  the consumer correctly receives a NULL pointer on a subsequent access.
- Macro: Same as "Revocation" but uses the REVOCABLE().

A way to run the test:
$ ./tools/testing/kunit/kunit.py run \
        --kconfig_add CONFIG_REVOCABLE_KUNIT_TEST=y \
        revocable_test

Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v4:
- REVOCABLE() -> REVOCABLE_TRY_ACCESS_WITH().
- revocable_release() -> revocable_withdraw_access().

v3: https://lore.kernel.org/chrome-platform/20250912081718.3827390-3-tzungbi@kernel.org
- No changes.

v2: https://lore.kernel.org/chrome-platform/20250820081645.847919-3-tzungbi@kernel.org
- New in the series.

 MAINTAINERS                   |   1 +
 drivers/base/Kconfig          |   8 +++
 drivers/base/Makefile         |   3 +
 drivers/base/revocable_test.c | 110 ++++++++++++++++++++++++++++++++++
 4 files changed, 122 insertions(+)
 create mode 100644 drivers/base/revocable_test.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 15d45289fda6..1f12100d1ab7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21957,6 +21957,7 @@ M:	Tzung-Bi Shih <tzungbi@kernel.org>
 L:	linux-kernel@vger.kernel.org
 S:	Maintained
 F:	drivers/base/revocable.c
+F:	drivers/base/revocable_test.c
 F:	include/linux/revocable.h
 
 RFKILL
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index 064eb52ff7e2..a6488035f63d 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -244,3 +244,11 @@ config FW_DEVLINK_SYNC_STATE_TIMEOUT
 	  work on.
 
 endmenu
+
+# Kunit test cases
+config REVOCABLE_KUNIT_TEST
+	tristate "Kunit tests for revocable" if !KUNIT_ALL_TESTS
+	depends on KUNIT
+	default KUNIT_ALL_TESTS
+	help
+	  Kunit tests for the revocable API.
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index bdf854694e39..4185aaa9bbb9 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -35,3 +35,6 @@ ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG
 # define_trace.h needs to know how to find our header
 CFLAGS_trace.o		:= -I$(src)
 obj-$(CONFIG_TRACING)	+= trace.o
+
+# Kunit test cases
+obj-$(CONFIG_REVOCABLE_KUNIT_TEST)	+= revocable_test.o
diff --git a/drivers/base/revocable_test.c b/drivers/base/revocable_test.c
new file mode 100644
index 000000000000..1c3e08ba0505
--- /dev/null
+++ b/drivers/base/revocable_test.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Kunit tests for the revocable API.
+ *
+ * The test cases cover the following scenarios:
+ *
+ * - Basic: Verifies that a consumer can successfully access the resource
+ *   provided via the provider.
+ *
+ * - Revocation: Verifies that after the provider revokes the resource,
+ *   the consumer correctly receives a NULL pointer on a subsequent access.
+ *
+ * - Macro: Same as "Revocation" but uses the REVOCABLE_TRY_ACCESS_WITH().
+ */
+
+#include <kunit/test.h>
+#include <linux/revocable.h>
+
+static void revocable_test_basic(struct kunit *test)
+{
+	struct revocable_provider *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);
+
+	res = revocable_try_access(rev);
+	KUNIT_EXPECT_PTR_EQ(test, res, real_res);
+	revocable_withdraw_access(rev);
+
+	revocable_free(rev);
+	revocable_provider_revoke(rp);
+}
+
+static void revocable_test_revocation(struct kunit *test)
+{
+	struct revocable_provider *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);
+
+	res = revocable_try_access(rev);
+	KUNIT_EXPECT_PTR_EQ(test, res, real_res);
+	revocable_withdraw_access(rev);
+
+	revocable_provider_revoke(rp);
+
+	res = revocable_try_access(rev);
+	KUNIT_EXPECT_PTR_EQ(test, res, NULL);
+	revocable_withdraw_access(rev);
+
+	revocable_free(rev);
+}
+
+static void revocable_test_macro(struct kunit *test)
+{
+	struct revocable_provider *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_WITH(rev, res) {
+		KUNIT_EXPECT_PTR_EQ(test, res, real_res);
+		accessed = true;
+	}
+	KUNIT_EXPECT_TRUE(test, accessed);
+
+	revocable_provider_revoke(rp);
+
+	accessed = false;
+	REVOCABLE_TRY_ACCESS_WITH(rev, res) {
+		KUNIT_EXPECT_PTR_EQ(test, res, NULL);
+		accessed = true;
+	}
+	KUNIT_EXPECT_TRUE(test, accessed);
+
+	revocable_free(rev);
+}
+
+static struct kunit_case revocable_test_cases[] = {
+	KUNIT_CASE(revocable_test_basic),
+	KUNIT_CASE(revocable_test_revocation),
+	KUNIT_CASE(revocable_test_macro),
+	{}
+};
+
+static struct kunit_suite revocable_test_suite = {
+	.name = "revocable_test",
+	.test_cases = revocable_test_cases,
+};
+
+kunit_test_suite(revocable_test_suite);
-- 
2.51.0.534.gc79095c0ca-goog


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 3/7] selftests: revocable: Add kselftest cases
  2025-09-23  7:52 [PATCH v4 0/7] platform/chrome: Fix a possible UAF via revocable Tzung-Bi Shih
  2025-09-23  7:52 ` [PATCH v4 1/7] revocable: Revocable resource management Tzung-Bi Shih
  2025-09-23  7:52 ` [PATCH v4 2/7] revocable: Add Kunit test cases Tzung-Bi Shih
@ 2025-09-23  7:52 ` Tzung-Bi Shih
  2025-09-23  7:52 ` [PATCH v4 4/7] platform/chrome: Protect cros_ec_device lifecycle with revocable Tzung-Bi Shih
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 11+ messages in thread
From: Tzung-Bi Shih @ 2025-09-23  7:52 UTC (permalink / raw)
  To: Benson Leung, Greg Kroah-Hartman, Rafael J . Wysocki,
	Danilo Krummrich
  Cc: Jonathan Corbet, Shuah Khan, Dawid Niedzwiecki, linux-doc,
	linux-kernel, chrome-platform, linux-kselftest, tzungbi,
	Laurent Pinchart, Bartosz Golaszewski, Wolfram Sang

Add kselftest cases for the revocable API.

The test consists of three parts:
- A kernel module (revocable_test.ko) that creates a debugfs interface
  with `/provider` and `/consumer` files.
- A user-space C program (revocable_test) that uses the kselftest
  harness to interact with the debugfs files.
- An orchestrating shell script (test-revocable.sh) that loads the
  module, runs the C program, and unloads the module.

The test cases cover the following scenarios:
- Basic: Verifies that a consumer can successfully access the resource
  provided via the provider.
- Revocation: Verifies that after the provider revokes the resource,
  the consumer correctly receives a NULL pointer on a subsequent access.
- Macro: Same as "Revocation" but uses the REVOCABLE().

Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v4:
- REVOCABLE() -> REVOCABLE_TRY_ACCESS_WITH().
- revocable_release() -> revocable_withdraw_access().

v3: https://lore.kernel.org/chrome-platform/20250912081718.3827390-4-tzungbi@kernel.org
- No changes.

v2: https://lore.kernel.org/chrome-platform/20250820081645.847919-4-tzungbi@kernel.org
- New in the series.

A way to run the kselftest (for my reference):
- Update kernel to the DUT.
- `mkdir build` and copy the kernel config to build/.
- `make O=build LLVM=1 -j32` (for generated headers and built-in symbols).
- `make O=build LLVM=1 KDIR=$(pwd) -C tools/testing/selftests/
     TARGETS=drivers/base/revocable gen_tar`.
- Copy build/kselftest/kselftest_install/kselftest-packages/kselftest.tar.gz
  to the DUT, extract, and execute the run_kselftest.sh.

 MAINTAINERS                                   |   1 +
 tools/testing/selftests/Makefile              |   1 +
 .../selftests/drivers/base/revocable/Makefile |   7 +
 .../drivers/base/revocable/revocable_test.c   | 116 +++++++++++
 .../drivers/base/revocable/test-revocable.sh  |  39 ++++
 .../base/revocable/test_modules/Makefile      |  10 +
 .../revocable/test_modules/revocable_test.c   | 188 ++++++++++++++++++
 7 files changed, 362 insertions(+)
 create mode 100644 tools/testing/selftests/drivers/base/revocable/Makefile
 create mode 100644 tools/testing/selftests/drivers/base/revocable/revocable_test.c
 create mode 100755 tools/testing/selftests/drivers/base/revocable/test-revocable.sh
 create mode 100644 tools/testing/selftests/drivers/base/revocable/test_modules/Makefile
 create mode 100644 tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 1f12100d1ab7..fff052ba040d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21959,6 +21959,7 @@ S:	Maintained
 F:	drivers/base/revocable.c
 F:	drivers/base/revocable_test.c
 F:	include/linux/revocable.h
+F:	tools/testing/selftests/drivers/base/revocable/
 
 RFKILL
 M:	Johannes Berg <johannes@sipsolutions.net>
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index c46ebdb9b8ef..1136a8f12ef5 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -17,6 +17,7 @@ TARGETS += damon
 TARGETS += devices/error_logs
 TARGETS += devices/probe
 TARGETS += dmabuf-heaps
+TARGETS += drivers/base/revocable
 TARGETS += drivers/dma-buf
 TARGETS += drivers/ntsync
 TARGETS += drivers/s390x/uvdevice
diff --git a/tools/testing/selftests/drivers/base/revocable/Makefile b/tools/testing/selftests/drivers/base/revocable/Makefile
new file mode 100644
index 000000000000..afa5ca0fa452
--- /dev/null
+++ b/tools/testing/selftests/drivers/base/revocable/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+
+TEST_GEN_MODS_DIR := test_modules
+TEST_GEN_PROGS_EXTENDED := revocable_test
+TEST_PROGS := test-revocable.sh
+
+include ../../../lib.mk
diff --git a/tools/testing/selftests/drivers/base/revocable/revocable_test.c b/tools/testing/selftests/drivers/base/revocable/revocable_test.c
new file mode 100644
index 000000000000..3c0feb7a8944
--- /dev/null
+++ b/tools/testing/selftests/drivers/base/revocable/revocable_test.c
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025 Google LLC
+ *
+ * A selftest for the revocable API.
+ *
+ * The test cases cover the following scenarios:
+ *
+ * - Basic: Verifies that a consumer can successfully access the resource
+ *   provided via the provider.
+ *
+ * - Revocation: Verifies that after the provider revokes the resource,
+ *   the consumer correctly receives a NULL pointer on a subsequent access.
+ *
+ * - Macro: Same as "Revocation" but uses the REVOCABLE_TRY_ACCESS_WITH().
+ */
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "../../../kselftest_harness.h"
+
+#define DEBUGFS_PATH "/sys/kernel/debug/revocable_test"
+#define TEST_CMD_RESOURCE_GONE "resource_gone"
+#define TEST_DATA "12345678"
+#define TEST_MAGIC_OFFSET 0x1234
+
+FIXTURE(revocable_fixture) {
+	int pfd;
+	int cfd;
+};
+
+FIXTURE_SETUP(revocable_fixture) {
+	int ret;
+
+	self->pfd = open(DEBUGFS_PATH "/provider", O_WRONLY);
+	ASSERT_NE(-1, self->pfd)
+		TH_LOG("failed to open provider fd");
+
+	ret = write(self->pfd, TEST_DATA, strlen(TEST_DATA));
+	ASSERT_NE(-1, ret) {
+		close(self->pfd);
+		TH_LOG("failed to write test data");
+	}
+
+	self->cfd = open(DEBUGFS_PATH "/consumer", O_RDONLY);
+	ASSERT_NE(-1, self->cfd)
+		TH_LOG("failed to open consumer fd");
+}
+
+FIXTURE_TEARDOWN(revocable_fixture) {
+	close(self->cfd);
+	close(self->pfd);
+}
+
+/*
+ * ASSERT_* is only available in TEST or TEST_F block.  Use
+ * macro for the helper.
+ */
+#define READ_TEST_DATA(_fd, _offset, _data, _msg)			\
+	do {								\
+		int ret;						\
+									\
+		ret = lseek(_fd, _offset, SEEK_SET);			\
+		ASSERT_NE(-1, ret)					\
+			TH_LOG("failed to lseek");			\
+									\
+		ret = read(_fd, _data, sizeof(_data) - 1);		\
+		ASSERT_NE(-1, ret)					\
+			TH_LOG(_msg);					\
+		data[ret] = '\0';					\
+	} while (0)
+
+TEST_F(revocable_fixture, basic) {
+	char data[16];
+
+	READ_TEST_DATA(self->cfd, 0, data, "failed to read test data");
+	EXPECT_STREQ(TEST_DATA, data);
+}
+
+TEST_F(revocable_fixture, revocation) {
+	char data[16];
+	int ret;
+
+	READ_TEST_DATA(self->cfd, 0, data, "failed to read test data");
+	EXPECT_STREQ(TEST_DATA, data);
+
+	ret = write(self->pfd, TEST_CMD_RESOURCE_GONE,
+		    strlen(TEST_CMD_RESOURCE_GONE));
+	ASSERT_NE(-1, ret)
+		TH_LOG("failed to write resource gone cmd");
+
+	READ_TEST_DATA(self->cfd, 0, data,
+		       "failed to read test data after resource gone");
+	EXPECT_STREQ("(null)", data);
+}
+
+TEST_F(revocable_fixture, macro) {
+	char data[16];
+	int ret;
+
+	READ_TEST_DATA(self->cfd, TEST_MAGIC_OFFSET, data,
+		       "failed to read test data");
+	EXPECT_STREQ(TEST_DATA, data);
+
+	ret = write(self->pfd, TEST_CMD_RESOURCE_GONE,
+		    strlen(TEST_CMD_RESOURCE_GONE));
+	ASSERT_NE(-1, ret)
+		TH_LOG("failed to write resource gone cmd");
+
+	READ_TEST_DATA(self->cfd, TEST_MAGIC_OFFSET, data,
+		       "failed to read test data after resource gone");
+	EXPECT_STREQ("(null)", data);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/drivers/base/revocable/test-revocable.sh b/tools/testing/selftests/drivers/base/revocable/test-revocable.sh
new file mode 100755
index 000000000000..3a34be28001a
--- /dev/null
+++ b/tools/testing/selftests/drivers/base/revocable/test-revocable.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+mod_name="revocable_test"
+ksft_fail=1
+ksft_skip=4
+
+if [ "$(id -u)" -ne 0 ]; then
+	echo "$0: Must be run as root"
+	exit "$ksft_skip"
+fi
+
+if ! which insmod > /dev/null 2>&1; then
+	echo "$0: Need insmod"
+	exit "$ksft_skip"
+fi
+
+if ! which rmmod > /dev/null 2>&1; then
+	echo "$0: Need rmmod"
+	exit "$ksft_skip"
+fi
+
+insmod test_modules/"${mod_name}".ko
+
+if [ ! -d /sys/kernel/debug/revocable_test/ ]; then
+	mount -t debugfs none /sys/kernel/debug/
+
+	if [ ! -d /sys/kernel/debug/revocable_test/ ]; then
+		echo "$0: Error mounting debugfs"
+		exit "$ksft_fail"
+	fi
+fi
+
+./revocable_test
+ret=$?
+
+rmmod "${mod_name}"
+
+exit "${ret}"
diff --git a/tools/testing/selftests/drivers/base/revocable/test_modules/Makefile b/tools/testing/selftests/drivers/base/revocable/test_modules/Makefile
new file mode 100644
index 000000000000..f29e4f909402
--- /dev/null
+++ b/tools/testing/selftests/drivers/base/revocable/test_modules/Makefile
@@ -0,0 +1,10 @@
+TESTMODS_DIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
+KDIR ?= /lib/modules/$(shell uname -r)/build
+
+obj-m += revocable_test.o
+
+all:
+	$(Q)$(MAKE) -C $(KDIR) M=$(TESTMODS_DIR)
+
+clean:
+	$(Q)$(MAKE) -C $(KDIR) M=$(TESTMODS_DIR) clean
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
new file mode 100644
index 000000000000..c90ec150a6c8
--- /dev/null
+++ b/tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025 Google LLC
+ *
+ * A kernel module for testing the revocable API.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/revocable.h>
+#include <linux/slab.h>
+
+#define TEST_CMD_RESOURCE_GONE "resource_gone"
+#define TEST_MAGIC_OFFSET 0x1234
+
+static struct dentry *debugfs_dir;
+
+struct revocable_test_provider_priv {
+	struct revocable_provider *rp;
+	struct dentry *dentry;
+	char res[16];
+};
+
+static int revocable_test_consumer_open(struct inode *inode, struct file *filp)
+{
+	struct revocable *rev;
+	struct revocable_provider *rp = 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);
+	return 0;
+}
+
+static ssize_t revocable_test_consumer_read(struct file *filp,
+					    char __user *buf,
+					    size_t count, loff_t *offset)
+{
+	char *res;
+	char data[16];
+	size_t len;
+	struct revocable *rev = filp->private_data;
+
+	switch (*offset) {
+	case 0:
+		res = revocable_try_access(rev);
+		snprintf(data, sizeof(data), "%s", res ?: "(null)");
+		revocable_withdraw_access(rev);
+		break;
+	case TEST_MAGIC_OFFSET:
+		REVOCABLE_TRY_ACCESS_WITH(rev, res)
+			snprintf(data, sizeof(data), "%s", res ?: "(null)");
+		break;
+	default:
+		return 0;
+	}
+
+	len = min_t(size_t, strlen(data), count);
+	if (copy_to_user(buf, data, len))
+		return -EFAULT;
+
+	*offset = len;
+	return len;
+}
+
+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,
+};
+
+static int revocable_test_provider_open(struct inode *inode, struct file *filp)
+{
+	struct revocable_test_provider_priv *priv;
+
+	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	filp->private_data = priv;
+
+	return 0;
+}
+
+static int revocable_test_provider_release(struct inode *inode,
+					   struct file *filp)
+{
+	struct revocable_test_provider_priv *priv = filp->private_data;
+
+	debugfs_remove(priv->dentry);
+	if (priv->rp)
+		revocable_provider_revoke(priv->rp);
+	kfree(priv);
+
+	return 0;
+}
+
+static ssize_t revocable_test_provider_write(struct file *filp,
+					     const char __user *buf,
+					     size_t count, loff_t *offset)
+{
+	size_t copied;
+	char data[64];
+	struct revocable_test_provider_priv *priv = filp->private_data;
+
+	copied = strncpy_from_user(data, buf, sizeof(data));
+	if (copied < 0)
+		return copied;
+	if (copied == sizeof(data))
+		data[sizeof(data) - 1] = '\0';
+
+	/*
+	 * Note: The test can't just close the FD for signaling the
+	 * resource gone.  Subsequent file operations on the opening
+	 * FD of debugfs return -EIO after calling debugfs_remove().
+	 * See also debugfs_file_get().
+	 *
+	 * Here is a side command channel for signaling the resource
+	 * gone.
+	 */
+	if (!strcmp(data, TEST_CMD_RESOURCE_GONE)) {
+		revocable_provider_revoke(priv->rp);
+		priv->rp = NULL;
+	} else {
+		if (priv->res[0] != '\0')
+			return 0;
+
+		strscpy(priv->res, data);
+
+		priv->rp = revocable_provider_alloc(&priv->res);
+		if (!priv->rp)
+			return -ENOMEM;
+
+		priv->dentry = debugfs_create_file("consumer", 0400,
+						   debugfs_dir, priv->rp,
+						   &revocable_test_consumer_fops);
+		if (!priv->dentry) {
+			revocable_provider_revoke(priv->rp);
+			return -ENOMEM;
+		}
+	}
+
+	return copied;
+}
+
+static const struct file_operations revocable_test_provider_fops = {
+	.open = revocable_test_provider_open,
+	.release = revocable_test_provider_release,
+	.write = revocable_test_provider_write,
+};
+
+static int __init revocable_test_init(void)
+{
+	debugfs_dir = debugfs_create_dir("revocable_test", NULL);
+	if (!debugfs_dir)
+		return -ENOMEM;
+
+	if (!debugfs_create_file("provider", 0200, debugfs_dir, NULL,
+				 &revocable_test_provider_fops)) {
+		debugfs_remove_recursive(debugfs_dir);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static void __exit revocable_test_exit(void)
+{
+	debugfs_remove_recursive(debugfs_dir);
+}
+
+module_init(revocable_test_init);
+module_exit(revocable_test_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Tzung-Bi Shih <tzungbi@kernel.org>");
+MODULE_DESCRIPTION("Revocable Kselftest");
-- 
2.51.0.534.gc79095c0ca-goog


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 4/7] platform/chrome: Protect cros_ec_device lifecycle with revocable
  2025-09-23  7:52 [PATCH v4 0/7] platform/chrome: Fix a possible UAF via revocable Tzung-Bi Shih
                   ` (2 preceding siblings ...)
  2025-09-23  7:52 ` [PATCH v4 3/7] selftests: revocable: Add kselftest cases Tzung-Bi Shih
@ 2025-09-23  7:52 ` Tzung-Bi Shih
  2025-09-23  7:53 ` [PATCH v4 5/7] revocable: Add fops replacement Tzung-Bi Shih
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 11+ messages in thread
From: Tzung-Bi Shih @ 2025-09-23  7:52 UTC (permalink / raw)
  To: Benson Leung, Greg Kroah-Hartman, Rafael J . Wysocki,
	Danilo Krummrich
  Cc: Jonathan Corbet, Shuah Khan, Dawid Niedzwiecki, linux-doc,
	linux-kernel, chrome-platform, linux-kselftest, tzungbi,
	Laurent Pinchart, Bartosz Golaszewski, Wolfram Sang

The cros_ec_device can be unregistered when the underlying device is
removed.  Other kernel drivers that interact with the EC may hold a
pointer to the cros_ec_device, creating a risk of a use-after-free
error if the EC device is removed while still being referenced.

To prevent this, leverage the revocable and convert the underlying
device drivers to resource providers of cros_ec_device.

Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v4:
- No changes.

v3: https://lore.kernel.org/chrome-platform/20250912081718.3827390-5-tzungbi@kernel.org
- Initialize the revocable provider in cros_ec_device_alloc() instead of
  spreading in protocol device drivers.

v2: https://lore.kernel.org/chrome-platform/20250820081645.847919-5-tzungbi@kernel.org
- Rename "ref_proxy" -> "revocable".

v1: https://lore.kernel.org/chrome-platform/20250814091020.1302888-3-tzungbi@kernel.org

 drivers/platform/chrome/cros_ec.c           | 5 +++++
 include/linux/platform_data/cros_ec_proto.h | 4 ++++
 2 files changed, 9 insertions(+)

diff --git a/drivers/platform/chrome/cros_ec.c b/drivers/platform/chrome/cros_ec.c
index 1da79e3d215b..95e3e898e3da 100644
--- a/drivers/platform/chrome/cros_ec.c
+++ b/drivers/platform/chrome/cros_ec.c
@@ -16,6 +16,7 @@
 #include <linux/platform_device.h>
 #include <linux/platform_data/cros_ec_commands.h>
 #include <linux/platform_data/cros_ec_proto.h>
+#include <linux/revocable.h>
 #include <linux/slab.h>
 #include <linux/suspend.h>
 
@@ -47,6 +48,10 @@ struct cros_ec_device *cros_ec_device_alloc(struct device *dev)
 	if (!ec_dev)
 		return NULL;
 
+	ec_dev->revocable_provider = devm_revocable_provider_alloc(dev, ec_dev);
+	if (!ec_dev->revocable_provider)
+		return NULL;
+
 	ec_dev->din_size = sizeof(struct ec_host_response) +
 			   sizeof(struct ec_response_get_protocol_info) +
 			   EC_MAX_RESPONSE_OVERHEAD;
diff --git a/include/linux/platform_data/cros_ec_proto.h b/include/linux/platform_data/cros_ec_proto.h
index de14923720a5..fbb6ca34a40f 100644
--- a/include/linux/platform_data/cros_ec_proto.h
+++ b/include/linux/platform_data/cros_ec_proto.h
@@ -12,6 +12,7 @@
 #include <linux/lockdep_types.h>
 #include <linux/mutex.h>
 #include <linux/notifier.h>
+#include <linux/revocable.h>
 
 #include <linux/platform_data/cros_ec_commands.h>
 
@@ -165,6 +166,7 @@ struct cros_ec_command {
  * @pd: The platform_device used by the mfd driver to interface with the
  *      PD behind an EC.
  * @panic_notifier: EC panic notifier.
+ * @revocable_provider: The revocable_provider to this device.
  */
 struct cros_ec_device {
 	/* These are used by other drivers that want to talk to the EC */
@@ -211,6 +213,8 @@ struct cros_ec_device {
 	struct platform_device *pd;
 
 	struct blocking_notifier_head panic_notifier;
+
+	struct revocable_provider *revocable_provider;
 };
 
 /**
-- 
2.51.0.534.gc79095c0ca-goog


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 5/7] revocable: Add fops replacement
  2025-09-23  7:52 [PATCH v4 0/7] platform/chrome: Fix a possible UAF via revocable Tzung-Bi Shih
                   ` (3 preceding siblings ...)
  2025-09-23  7:52 ` [PATCH v4 4/7] platform/chrome: Protect cros_ec_device lifecycle with revocable Tzung-Bi Shih
@ 2025-09-23  7:53 ` Tzung-Bi Shih
  2025-09-24 20:54   ` dan.j.williams
  2025-09-23  7:53 ` [PATCH v4 6/7] char: misc: Leverage revocable " Tzung-Bi Shih
  2025-09-23  7:53 ` [PATCH v4 7/7] platform/chrome: cros_ec_chardev: Secure cros_ec_device via revocable Tzung-Bi Shih
  6 siblings, 1 reply; 11+ messages in thread
From: Tzung-Bi Shih @ 2025-09-23  7:53 UTC (permalink / raw)
  To: Benson Leung, Greg Kroah-Hartman, Rafael J . Wysocki,
	Danilo Krummrich
  Cc: Jonathan Corbet, Shuah Khan, Dawid Niedzwiecki, linux-doc,
	linux-kernel, chrome-platform, linux-kselftest, tzungbi,
	Laurent Pinchart, Bartosz Golaszewski, Wolfram Sang

Introduce revocable_replace_fops() to simplify the use of the revocable
API with file_operations.

The function, should be called from a driver's ->open(), replaces the
fops with a wrapper that automatically handles the `try_access` and
`withdraw_access`.

When the file is closed, the wrapper's ->release() restores the original
fops and cleanups.  This centralizes the revocable logic, making drivers
cleaner and easier to maintain.

Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
PoC patch.

Known issues:
- All file operations are serialized in a mutex unless we want to embed
  the required context to struct file.
- All file operations (for current version) call revocable_try_access()
  for guaranteeing the resource even if it may be unused in the fops.
- To support multiple revocable resources, extend to arrays.

v4:
- New in the series.

 drivers/base/revocable.c  | 141 ++++++++++++++++++++++++++++++++++++++
 include/linux/revocable.h |   7 ++
 2 files changed, 148 insertions(+)

diff --git a/drivers/base/revocable.c b/drivers/base/revocable.c
index f8dd4363a87b..ebb91231adf5 100644
--- a/drivers/base/revocable.c
+++ b/drivers/base/revocable.c
@@ -5,8 +5,10 @@
  * Revocable resource management
  */
 
+#include <linux/cleanup.h>
 #include <linux/device.h>
 #include <linux/kref.h>
+#include <linux/poll.h>
 #include <linux/revocable.h>
 #include <linux/slab.h>
 #include <linux/srcu.h>
@@ -231,3 +233,142 @@ void revocable_withdraw_access(struct revocable *rev) __releases(&rev->rp->srcu)
 	srcu_read_unlock(&rp->srcu, rev->idx);
 }
 EXPORT_SYMBOL_GPL(revocable_withdraw_access);
+
+struct fops_replacement {
+	struct file *filp;
+	const struct revocable_operations *rops;
+	const struct file_operations *orig_fops;
+	struct revocable *rev;
+	struct file_operations fops;
+	struct list_head list;
+};
+
+/*
+ * Use a list and linear search for PoC for now.  A hash table should be more
+ * practical.
+ */
+static LIST_HEAD(fops_replacement_list);
+static DEFINE_MUTEX(fops_replacement_mutex);
+
+/*
+ * Use `filp` as the search key.
+ */
+static struct fops_replacement *find_fops_replacement(struct file *filp)
+{
+	struct fops_replacement *fr;
+
+	guard(mutex)(&fops_replacement_mutex);
+	list_for_each_entry(fr, &fops_replacement_list, list) {
+		if (fr->filp == filp)
+			return fr;
+	}
+
+	return NULL;
+}
+
+#define GET_FOPS_REPLACEMENT(filp) ({					\
+	struct fops_replacement *_fr;					\
+	int _ret;							\
+									\
+	_fr = find_fops_replacement(filp);				\
+	if (!_fr)							\
+		return -ENODEV;						\
+									\
+	_ret = _fr->rops->try_access(_fr->rev, filp->private_data);	\
+	if (_ret)							\
+		return _ret;						\
+									\
+	_fr;								\
+})
+
+static void put_fops_replacement(struct fops_replacement *fr)
+{
+	revocable_withdraw_access(fr->rev);
+}
+
+DEFINE_FREE(fops_replacement, struct fops_replacement *, if (_T) put_fops_replacement(_T))
+
+static ssize_t revocable_fr_read(struct file *filp, char __user *buffer,
+				 size_t length, loff_t *offset)
+{
+	struct fops_replacement *fr __free(fops_replacement) =
+			GET_FOPS_REPLACEMENT(filp);
+	return fr->orig_fops->read(filp, buffer, length, offset);
+}
+
+static __poll_t revocable_fr_poll(struct file *filp, poll_table *wait)
+{
+	struct fops_replacement *fr __free(fops_replacement) =
+			GET_FOPS_REPLACEMENT(filp);
+	return fr->orig_fops->poll(filp, wait);
+}
+
+static long revocable_fr_unlocked_ioctl(struct file *filp, unsigned int cmd,
+					unsigned long arg)
+{
+	struct fops_replacement *fr __free(fops_replacement) =
+			GET_FOPS_REPLACEMENT(filp);
+	return fr->orig_fops->unlocked_ioctl(filp, cmd, arg);
+}
+
+static void revocable_recover_fops(struct file *filp,
+				   struct fops_replacement *fr)
+{
+	filp->f_op = fr->orig_fops;
+
+	revocable_free(fr->rev);
+	scoped_guard(mutex, &fops_replacement_mutex)
+		list_del(&fr->list);
+	kfree(fr);
+}
+
+static int revocable_fr_release(struct inode *inode, struct file *filp)
+{
+	int ret = 0;
+	struct fops_replacement *fr = GET_FOPS_REPLACEMENT(filp);
+
+	if (fr->orig_fops->release)
+		ret = fr->orig_fops->release(inode, filp);
+	put_fops_replacement(fr);
+
+	revocable_recover_fops(filp, fr);
+	return ret;
+}
+
+/**
+ * revocable_replace_fops() - Replace the file operations to be revocable-aware.
+ *
+ * Should be used only from ->open() instances.
+ */
+int revocable_replace_fops(struct file *filp, struct revocable_provider *rp,
+			   const struct revocable_operations *rops)
+{
+	struct fops_replacement *fr;
+
+	fr = kzalloc(sizeof(*fr), GFP_KERNEL);
+	if (!fr)
+		return -ENOMEM;
+
+	fr->filp = filp;
+	fr->rops = rops;
+	fr->orig_fops = filp->f_op;
+	fr->rev = revocable_alloc(rp);
+	if (!fr->rev)
+		return -ENOMEM;
+	memcpy(&fr->fops, filp->f_op, sizeof(struct file_operations));
+	scoped_guard(mutex, &fops_replacement_mutex)
+		list_add(&fr->list, &fops_replacement_list);
+
+	fr->fops.release = revocable_fr_release;
+
+	if (filp->f_op->read)
+		fr->fops.read = revocable_fr_read;
+	if (filp->f_op->poll)
+		fr->fops.poll = revocable_fr_poll;
+	if (filp->f_op->unlocked_ioctl)
+		fr->fops.unlocked_ioctl = revocable_fr_unlocked_ioctl;
+
+	filp->f_op = &fr->fops;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(revocable_replace_fops);
diff --git a/include/linux/revocable.h b/include/linux/revocable.h
index 7177bf045d9c..9ad1d0198a1e 100644
--- a/include/linux/revocable.h
+++ b/include/linux/revocable.h
@@ -50,4 +50,11 @@ _label:										\
 #define REVOCABLE_TRY_ACCESS_WITH(_rev, _res)					\
 		_REVOCABLE_TRY_ACCESS_WITH(_rev, __UNIQUE_ID(label), _res)
 
+struct revocable_operations {
+	int (*try_access)(struct revocable *rev, void *data);
+};
+
+int revocable_replace_fops(struct file *filp, struct revocable_provider *rp,
+			   const struct revocable_operations *rops);
+
 #endif /* __LINUX_REVOCABLE_H */
-- 
2.51.0.534.gc79095c0ca-goog


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 6/7] char: misc: Leverage revocable fops replacement
  2025-09-23  7:52 [PATCH v4 0/7] platform/chrome: Fix a possible UAF via revocable Tzung-Bi Shih
                   ` (4 preceding siblings ...)
  2025-09-23  7:53 ` [PATCH v4 5/7] revocable: Add fops replacement Tzung-Bi Shih
@ 2025-09-23  7:53 ` Tzung-Bi Shih
  2025-09-23  7:53 ` [PATCH v4 7/7] platform/chrome: cros_ec_chardev: Secure cros_ec_device via revocable Tzung-Bi Shih
  6 siblings, 0 replies; 11+ messages in thread
From: Tzung-Bi Shih @ 2025-09-23  7:53 UTC (permalink / raw)
  To: Benson Leung, Greg Kroah-Hartman, Rafael J . Wysocki,
	Danilo Krummrich
  Cc: Jonathan Corbet, Shuah Khan, Dawid Niedzwiecki, linux-doc,
	linux-kernel, chrome-platform, linux-kselftest, tzungbi,
	Laurent Pinchart, Bartosz Golaszewski, Wolfram Sang

Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
PoC patch.

v4:
- New in the series.

 drivers/char/misc.c        | 7 +++++++
 include/linux/miscdevice.h | 3 +++
 2 files changed, 10 insertions(+)

diff --git a/drivers/char/misc.c b/drivers/char/misc.c
index 726516fb0a3b..fcbbb0b1af35 100644
--- a/drivers/char/misc.c
+++ b/drivers/char/misc.c
@@ -159,6 +159,13 @@ static int misc_open(struct inode *inode, struct file *file)
 
 	err = 0;
 	replace_fops(file, new_fops);
+
+	if (c->rp && c->rops) {
+		err = revocable_replace_fops(file, c->rp, c->rops);
+		if (err)
+			goto fail;
+	}
+
 	if (file->f_op->open)
 		err = file->f_op->open(inode, file);
 fail:
diff --git a/include/linux/miscdevice.h b/include/linux/miscdevice.h
index 7d0aa718499c..00f5c878266a 100644
--- a/include/linux/miscdevice.h
+++ b/include/linux/miscdevice.h
@@ -5,6 +5,7 @@
 #include <linux/list.h>
 #include <linux/types.h>
 #include <linux/device.h>
+#include <linux/revocable.h>
 
 /*
  *	These allocations are managed by device@lanana.org. If you need
@@ -92,6 +93,8 @@ struct miscdevice {
 	const struct attribute_group **groups;
 	const char *nodename;
 	umode_t mode;
+	struct revocable_provider *rp;
+	const struct revocable_operations *rops;
 };
 
 extern int misc_register(struct miscdevice *misc);
-- 
2.51.0.534.gc79095c0ca-goog


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 7/7] platform/chrome: cros_ec_chardev: Secure cros_ec_device via revocable
  2025-09-23  7:52 [PATCH v4 0/7] platform/chrome: Fix a possible UAF via revocable Tzung-Bi Shih
                   ` (5 preceding siblings ...)
  2025-09-23  7:53 ` [PATCH v4 6/7] char: misc: Leverage revocable " Tzung-Bi Shih
@ 2025-09-23  7:53 ` Tzung-Bi Shih
  2025-09-23  8:29   ` Tzung-Bi Shih
  6 siblings, 1 reply; 11+ messages in thread
From: Tzung-Bi Shih @ 2025-09-23  7:53 UTC (permalink / raw)
  To: Benson Leung, Greg Kroah-Hartman, Rafael J . Wysocki,
	Danilo Krummrich
  Cc: Jonathan Corbet, Shuah Khan, Dawid Niedzwiecki, linux-doc,
	linux-kernel, chrome-platform, linux-kselftest, tzungbi,
	Laurent Pinchart, Bartosz Golaszewski, Wolfram Sang

Miscdevice now supports revocable fops replacement.  Use it to secure
the cros_ec_device.

Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
PoC patch.

v4:
- New in the series.

 drivers/platform/chrome/cros_ec_chardev.c | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/drivers/platform/chrome/cros_ec_chardev.c b/drivers/platform/chrome/cros_ec_chardev.c
index c9d80ad5b57e..cacc73635d6b 100644
--- a/drivers/platform/chrome/cros_ec_chardev.c
+++ b/drivers/platform/chrome/cros_ec_chardev.c
@@ -166,7 +166,6 @@ static int cros_ec_chardev_open(struct inode *inode, struct file *filp)
 	if (!priv)
 		return -ENOMEM;
 
-	priv->ec_dev = ec_dev;
 	priv->cmd_offset = ec->cmd_offset;
 	filp->private_data = priv;
 	INIT_LIST_HEAD(&priv->events);
@@ -370,6 +369,18 @@ static const struct file_operations chardev_fops = {
 #endif
 };
 
+static int cros_ec_chardev_rev_try_access(struct revocable *rev, void *data)
+{
+	struct chardev_priv *priv = data;
+
+	priv->ec_dev = revocable_try_access(rev);
+	return priv->ec_dev ? 0 : -ENODEV;
+}
+
+static const struct revocable_operations cros_ec_chardev_rops = {
+	.try_access = cros_ec_chardev_rev_try_access,
+};
+
 static int cros_ec_chardev_probe(struct platform_device *pdev)
 {
 	struct cros_ec_dev *ec = dev_get_drvdata(pdev->dev.parent);
@@ -385,6 +396,8 @@ static int cros_ec_chardev_probe(struct platform_device *pdev)
 	misc->fops = &chardev_fops;
 	misc->name = ec_platform->ec_name;
 	misc->parent = pdev->dev.parent;
+	misc->rp = ec->ec_dev->revocable_provider;
+	misc->rops = &cros_ec_chardev_rops;
 
 	dev_set_drvdata(&pdev->dev, misc);
 
-- 
2.51.0.534.gc79095c0ca-goog


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH v4 7/7] platform/chrome: cros_ec_chardev: Secure cros_ec_device via revocable
  2025-09-23  7:53 ` [PATCH v4 7/7] platform/chrome: cros_ec_chardev: Secure cros_ec_device via revocable Tzung-Bi Shih
@ 2025-09-23  8:29   ` Tzung-Bi Shih
  0 siblings, 0 replies; 11+ messages in thread
From: Tzung-Bi Shih @ 2025-09-23  8:29 UTC (permalink / raw)
  To: Benson Leung, Greg Kroah-Hartman, Rafael J . Wysocki,
	Danilo Krummrich
  Cc: Jonathan Corbet, Shuah Khan, Dawid Niedzwiecki, linux-doc,
	linux-kernel, chrome-platform, linux-kselftest, Laurent Pinchart,
	Bartosz Golaszewski, Wolfram Sang

On Tue, Sep 23, 2025 at 07:53:02AM +0000, Tzung-Bi Shih wrote:
> @@ -166,7 +166,6 @@ static int cros_ec_chardev_open(struct inode *inode, struct file *filp)
>  	if (!priv)
>  		return -ENOMEM;
>  
> -	priv->ec_dev = ec_dev;
>  	priv->cmd_offset = ec->cmd_offset;
>  	filp->private_data = priv;
>  	INIT_LIST_HEAD(&priv->events);
> @@ -370,6 +369,18 @@ static const struct file_operations chardev_fops = {
>  #endif
>  };
>  
> +static int cros_ec_chardev_rev_try_access(struct revocable *rev, void *data)
> +{
> +	struct chardev_priv *priv = data;
> +
> +	priv->ec_dev = revocable_try_access(rev);
> +	return priv->ec_dev ? 0 : -ENODEV;
> +}
> +
> +static const struct revocable_operations cros_ec_chardev_rops = {
> +	.try_access = cros_ec_chardev_rev_try_access,
> +};
> +
>  static int cros_ec_chardev_probe(struct platform_device *pdev)
>  {
>  	struct cros_ec_dev *ec = dev_get_drvdata(pdev->dev.parent);
> @@ -385,6 +396,8 @@ static int cros_ec_chardev_probe(struct platform_device *pdev)
>  	misc->fops = &chardev_fops;
>  	misc->name = ec_platform->ec_name;
>  	misc->parent = pdev->dev.parent;
> +	misc->rp = ec->ec_dev->revocable_provider;
> +	misc->rops = &cros_ec_chardev_rops;
>  
>  	dev_set_drvdata(&pdev->dev, misc);

If we prefer to avoid modifying the device driver, the driver must at least
register its resource providers with the subsystem.

This would allow the subsystem to guarantee resource validity by calling
revocable_try_access() before dispatching any file operations.

The device driver could then access its resources directly
(e.g., priv->ec_dev) within its fops, as their validity would have already
been checked by the subsystem.

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v4 5/7] revocable: Add fops replacement
  2025-09-23  7:53 ` [PATCH v4 5/7] revocable: Add fops replacement Tzung-Bi Shih
@ 2025-09-24 20:54   ` dan.j.williams
  2025-10-01 14:23     ` Tzung-Bi Shih
  0 siblings, 1 reply; 11+ messages in thread
From: dan.j.williams @ 2025-09-24 20:54 UTC (permalink / raw)
  To: Tzung-Bi Shih, Benson Leung, Greg Kroah-Hartman,
	Rafael J . Wysocki, Danilo Krummrich
  Cc: Jonathan Corbet, Shuah Khan, Dawid Niedzwiecki, linux-doc,
	linux-kernel, chrome-platform, linux-kselftest, tzungbi,
	Laurent Pinchart, Bartosz Golaszewski, Wolfram Sang

Tzung-Bi Shih wrote:
> Introduce revocable_replace_fops() to simplify the use of the revocable
> API with file_operations.
> 
> The function, should be called from a driver's ->open(), replaces the
> fops with a wrapper that automatically handles the `try_access` and
> `withdraw_access`.
> 
> When the file is closed, the wrapper's ->release() restores the original
> fops and cleanups.  This centralizes the revocable logic, making drivers
> cleaner and easier to maintain.
> 
> Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
[..]
> +/**
> + * revocable_replace_fops() - Replace the file operations to be revocable-aware.
> + *
> + * Should be used only from ->open() instances.
> + */
> +int revocable_replace_fops(struct file *filp, struct revocable_provider *rp,
> +			   const struct revocable_operations *rops)
> +{
> +	struct fops_replacement *fr;
> +
> +	fr = kzalloc(sizeof(*fr), GFP_KERNEL);
> +	if (!fr)
> +		return -ENOMEM;
> +
> +	fr->filp = filp;
> +	fr->rops = rops;
> +	fr->orig_fops = filp->f_op;
> +	fr->rev = revocable_alloc(rp);
> +	if (!fr->rev)
> +		return -ENOMEM;
> +	memcpy(&fr->fops, filp->f_op, sizeof(struct file_operations));
> +	scoped_guard(mutex, &fops_replacement_mutex)
> +		list_add(&fr->list, &fops_replacement_list);

This list grows for every active instance? Unless I am misreading, that
looks like a scaling burden that the simple approach below does not
have.

> +	fr->fops.release = revocable_fr_release;
> +
> +	if (filp->f_op->read)
> +		fr->fops.read = revocable_fr_read;
> +	if (filp->f_op->poll)
> +		fr->fops.poll = revocable_fr_poll;
> +	if (filp->f_op->unlocked_ioctl)
> +		fr->fops.unlocked_ioctl = revocable_fr_unlocked_ioctl;
> +
> +	filp->f_op = &fr->fops;
> +	return 0;
> +}

This facility is protecting the wrong resource, and I argue hides bugs
in drivers that think they need this. That matches the conclusion I came
to with my "managed_fops" attempt.

The resource that is being revoked is the device's attachment to its
driver. Whether that is dev_get_drvdata() or some other device-to-data
lookup, that is the resource that gets removed, not the fops themselves.
The only resource race with fops is whether the code text section
remains available while the fops are registered, but that lifetime scope
is not at a per-device instance scope.

revocable() could be useful for more complicated scenarios, but for
making sure that ->unlocked_ioctl(), ->poll(), and ->read() start
reliably failing, just do:

    guard(rwsem_read)(&subsystem_device_registration_lock);
    data = subsystem_lookup_device_relative_data(...)
    if (data)
        return subsystem_{ioctl,poll,read}(data, ...);
    return -ENXIO;

...and revoke future subsystem_{ioctl,poll,read}() invocations by making
subsystem_lookup_device_relative_data() start failing under
guard(rwsem_write)(&subsystem_device_registration_lock).

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v4 5/7] revocable: Add fops replacement
  2025-09-24 20:54   ` dan.j.williams
@ 2025-10-01 14:23     ` Tzung-Bi Shih
  0 siblings, 0 replies; 11+ messages in thread
From: Tzung-Bi Shih @ 2025-10-01 14:23 UTC (permalink / raw)
  To: dan.j.williams
  Cc: Benson Leung, Greg Kroah-Hartman, Rafael J . Wysocki,
	Danilo Krummrich, Jonathan Corbet, Shuah Khan, Dawid Niedzwiecki,
	linux-doc, linux-kernel, chrome-platform, linux-kselftest,
	Laurent Pinchart, Bartosz Golaszewski, Wolfram Sang

On Wed, Sep 24, 2025 at 01:54:14PM -0700, dan.j.williams@intel.com wrote:
> Tzung-Bi Shih wrote:
> > +int revocable_replace_fops(struct file *filp, struct revocable_provider *rp,
> > +			   const struct revocable_operations *rops)
> > +{
> > +	struct fops_replacement *fr;
> > +
> > +	fr = kzalloc(sizeof(*fr), GFP_KERNEL);
> > +	if (!fr)
> > +		return -ENOMEM;
> > +
> > +	fr->filp = filp;
> > +	fr->rops = rops;
> > +	fr->orig_fops = filp->f_op;
> > +	fr->rev = revocable_alloc(rp);
> > +	if (!fr->rev)
> > +		return -ENOMEM;
> > +	memcpy(&fr->fops, filp->f_op, sizeof(struct file_operations));
> > +	scoped_guard(mutex, &fops_replacement_mutex)
> > +		list_add(&fr->list, &fops_replacement_list);
> 
> This list grows for every active instance? Unless I am misreading, that
> looks like a scaling burden that the simple approach below does not
> have.

Correct, unless we want to embed the context (e.g. struct fops_replacement)
into struct file.  FWIW: the issue also listed as a known issue after "---".

> > +	fr->fops.release = revocable_fr_release;
> > +
> > +	if (filp->f_op->read)
> > +		fr->fops.read = revocable_fr_read;
> > +	if (filp->f_op->poll)
> > +		fr->fops.poll = revocable_fr_poll;
> > +	if (filp->f_op->unlocked_ioctl)
> > +		fr->fops.unlocked_ioctl = revocable_fr_unlocked_ioctl;
> > +
> > +	filp->f_op = &fr->fops;
> > +	return 0;
> > +}
> 
> This facility is protecting the wrong resource, and I argue hides bugs
> in drivers that think they need this. That matches the conclusion I came
> to with my "managed_fops" attempt.
> 
> The resource that is being revoked is the device's attachment to its
> driver. Whether that is dev_get_drvdata() or some other device-to-data
> lookup, that is the resource that gets removed, not the fops themselves.
> The only resource race with fops is whether the code text section
> remains available while the fops are registered, but that lifetime scope
> is not at a per-device instance scope.

revocable_replace_fops() doesn't protect any resources.  It replaces the
fops to revocable wrappers and recovers the fops when the file is releasing.

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2025-10-01 14:23 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-23  7:52 [PATCH v4 0/7] platform/chrome: Fix a possible UAF via revocable Tzung-Bi Shih
2025-09-23  7:52 ` [PATCH v4 1/7] revocable: Revocable resource management Tzung-Bi Shih
2025-09-23  7:52 ` [PATCH v4 2/7] revocable: Add Kunit test cases Tzung-Bi Shih
2025-09-23  7:52 ` [PATCH v4 3/7] selftests: revocable: Add kselftest cases Tzung-Bi Shih
2025-09-23  7:52 ` [PATCH v4 4/7] platform/chrome: Protect cros_ec_device lifecycle with revocable Tzung-Bi Shih
2025-09-23  7:53 ` [PATCH v4 5/7] revocable: Add fops replacement Tzung-Bi Shih
2025-09-24 20:54   ` dan.j.williams
2025-10-01 14:23     ` Tzung-Bi Shih
2025-09-23  7:53 ` [PATCH v4 6/7] char: misc: Leverage revocable " Tzung-Bi Shih
2025-09-23  7:53 ` [PATCH v4 7/7] platform/chrome: cros_ec_chardev: Secure cros_ec_device via revocable Tzung-Bi Shih
2025-09-23  8:29   ` Tzung-Bi Shih

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).