From nobody Thu Oct 2 19:03:40 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D85872E401; Fri, 12 Sep 2025 08:18:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757665100; cv=none; b=K0aiqxmXSwKjPlwP3HUh+J5L8Ozwg9WoilhiEv/4EcjwGIlzDG2faFhsXKAHBIp83n/mfbPtROTixp4MiAoxJ86LvbOB1i3jMpAIwOZ72nvOtfLvlsMFfhc058Ad0JI+/Qs8SGIWu3ckSR8thgmhhMc52TxZNDZRTvDEExte/9Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757665100; c=relaxed/simple; bh=hCs2MuquISe3xBHyQof8vQBuzIKmejcLXWXY9Xaxn+s=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=c9v/EhPD5uC5QS3Y5FsnkwkVO/WXbe+48OCrkecSSnxbogSnGmcq41BjFYgcMAz3n2oFga6MK6bsEbAqQ9Q7YNytY2yh5b/ikg7H1GE0wHulTrR5i+6NFqeQMgZ+PMs6ZZiGkey7XuNfAFudx++V7BWfJVchcC5DbLaVR5bDUc8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=HT8m2S7V; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="HT8m2S7V" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 908A2C4CEF8; Fri, 12 Sep 2025 08:18:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1757665098; bh=hCs2MuquISe3xBHyQof8vQBuzIKmejcLXWXY9Xaxn+s=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=HT8m2S7VChKnOfVnqmup6VWXs0vwCwARSCrDWVBpLKh9VxykRDkLbu6AD6KkHFqNi ZrI/bHWeQVisOE8nzE5aH7huW0s5t5M6efQwwtdEkNeqICC7cCTAllV1+PGA4gAxYu qUbGw6nckc+e1UHNEuUEElYJ4nI3tussV8xpgsx3dQqRgAB3jK+481cbAYYPVBrbLX t5whhycdUd6OaMVycNMosSml6avzfhmOWIFBzY3cK63lBQuac0zQsO/3UuFunZeq6Z cK7x5EjLiGfpNEEd1PlA5NAOgxOb3xJqQ/hHn73OlVOLSUEz0NGqp2yKNUffjq1VCQ Vro/VTfhWq0Aw== From: Tzung-Bi Shih To: Benson Leung , Greg Kroah-Hartman , "Rafael J . Wysocki" , Danilo Krummrich Cc: Jonathan Corbet , Shuah Khan , Dawid Niedzwiecki , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, chrome-platform@lists.linux.dev, linux-kselftest@vger.kernel.org, tzungbi@kernel.org Subject: [PATCH v3 1/5] revocable: Revocable resource management Date: Fri, 12 Sep 2025 08:17:13 +0000 Message-ID: <20250912081718.3827390-2-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0.384.g4c02a37b29-goog In-Reply-To: <20250912081718.3827390-1-tzungbi@kernel.org> References: <20250912081718.3827390-1-tzungbi@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Some resources can be removed asynchronously, for example, resources provided by a hot-pluggable device like USB. When holding a reference to such a resource, it's possible for the resource to be removed and its memory freed, leading to use-after-free errors on subsequent access. Introduce the revocable to establish weak references to such resources. It allows a resource consumer to safely attempt to access a resource that might be freed at any time by the resource provider. The implementation uses a provider/consumer model built on Sleepable RCU (SRCU) to guarantee safe memory access: - A resource provider allocates a struct revocable_provider and initializes it with a pointer to the resource. - A resource consumer that wants to access the resource allocates a struct revocable which holds a reference to the provider. - To access the resource, the consumer uses revocable_try_access(). This function enters an SRCU read-side critical section and returns the pointer to the resource. If the provider has already freed the resource, it returns NULL. After use, the consumer calls revocable_release() to exit the SRCU critical section. The REVOCABLE() is a convenient helper for doing that. - When the provider needs to remove the resource, it calls revocable_provider_free(). This function sets the internal resource pointer to NULL and then calls synchronize_srcu() to wait for all current readers to finish before the resource can be completely torn down. Signed-off-by: Tzung-Bi Shih Acked-by: Simona Vetter --- v3: - 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-tzungb= i@kernel.org .../driver-api/driver-model/index.rst | 1 + .../driver-api/driver-model/revocable.rst | 151 ++++++++++++ MAINTAINERS | 7 + drivers/base/Makefile | 2 +- drivers/base/revocable.c | 229 ++++++++++++++++++ include/linux/revocable.h | 37 +++ 6 files changed, 426 insertions(+), 1 deletion(-) create mode 100644 Documentation/driver-api/driver-model/revocable.rst create mode 100644 drivers/base/revocable.c create mode 100644 include/linux/revocable.h diff --git a/Documentation/driver-api/driver-model/index.rst b/Documentatio= n/driver-api/driver-model/index.rst index 4831bdd92e5c..8e1ee21185df 100644 --- a/Documentation/driver-api/driver-model/index.rst +++ b/Documentation/driver-api/driver-model/index.rst @@ -14,6 +14,7 @@ Driver Model overview platform porting + revocable =20 .. only:: subproject and html =20 diff --git a/Documentation/driver-api/driver-model/revocable.rst b/Document= ation/driver-api/driver-model/revocable.rst new file mode 100644 index 000000000000..b9e2968ba9c1 --- /dev/null +++ b/Documentation/driver-api/driver-model/revocable.rst @@ -0,0 +1,151 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D +Revocable Resource Management +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D + +Overview +=3D=3D=3D=3D=3D=3D=3D=3D + +In a system with hot-pluggable devices, such as USB, resources provided by +these devices can be removed asynchronously. If a consumer holds a refere= nce +to such a resource, the resource might be deallocated while the reference = is +still held, leading to use-after-free errors upon subsequent access. + +The "revocable" mechanism addresses this by establishing a weak reference = to a +resource that might be freed at any time. It allows a resource consumer to +safely attempt to access the resource, guaranteeing that the access is val= id +for the duration of its use, or it fails safely if the resource has already +been revoked. + +The implementation is based on a provider/consumer model that uses Sleepab= le +RCU (SRCU) to ensure safe memory access without traditional locking. + +How It Works +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +1. **Provider**: A resource provider, such as a driver for a hot-pluggable + device, allocates a ``struct revocable_provider``. This structure is + initialized with a pointer to the actual resource it manages. + +2. **Consumer**: A consumer that needs to access the resource is given a + ``struct revocable``, which acts as a handle containing a reference to + the provider. + +3. **Accessing the Resource**: To access the resource, the consumer uses + ``revocable_try_access()``. This function enters an SRCU read-side + critical section and returns a pointer to the resource. If the provid= er + has already revoked the resource, this function returns ``NULL``. The + consumer must check for this ``NULL`` return. + +4. **Releasing the Resource**: After the consumer has finished using the + resource, it must call ``revocable_release()`` to exit the SRCU critic= al + section. This signals that the consumer no longer requires access. T= he + ``REVOCABLE()`` macro is provided as a convenient and safe way to mana= ge + the access-release cycle. + +5. **Revoking the Resource**: When the provider needs to remove the resou= rce + (e.g., the device is unplugged), it calls ``revocable_provider_free()`= `. + This function first sets the internal resource pointer to ``NULL``, + preventing any new consumers from accessing it. It then calls + ``synchronize_srcu()``, which waits for all existing consumers current= ly + in the SRCU critical section to finish their work. Once all consumers + have released their access, the resource can be safely deallocated. + +Revocable vs. Device-Managed (devm) Resources +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +It's important to understand the distinction between a standard +device-managed (devm) resource and a resource managed by a +``revocable_provider``. + +The key difference is their lifetime: + +* A **devm resource** is tied to the lifetime of the device. It is + automatically freed when the device is unbound. +* A **revocable_provider** persists as long as there are active referenc= es + to it from ``revocable`` consumer handles. + +This means that a ``revocable_provider`` can outlive the device that creat= ed +it. This is a deliberate design feature, allowing consumers to hold a +reference to a resource even after the underlying device has been removed, +without causing a fault. When the consumer attempts to access the resourc= e, +it will simply be informed that the resource is no longer available. + +API and Usage +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +For Resource Providers +---------------------- + +``struct revocable_provider *revocable_provider_alloc(void *res);`` + Allocates a provider handle for the given resource ``res``. It return= s a + pointer to the ``revocable_provider`` on success, or ``NULL`` on failu= re. + +``struct revocable_provider *devm_revocable_provider_alloc(struct device *= dev, void *res);`` + A device-managed version of ``revocable_provider_alloc``. It is + convenient to allocate providers via this function if the ``res`` is a= lso + tied to the lifetime of the ``dev``. ``revocable_provider_free`` will= be + called automatically when the device is unbound. + +``void revocable_provider_free(struct revocable_provider *rp);`` + Revokes the resource. This function marks the resource as unavailable= and + waits for all current consumers to finish before the underlying memory + can be freed. + +For Resource Consumers +---------------------- + +``struct revocable *revocable_alloc(struct revocable_provider *rp);`` + Allocates a consumer handle for a given provider ``rp``. + +``void revocable_free(struct revocable *rev);`` + Frees a consumer handle. + +``void *revocable_try_access(struct revocable *rev);`` + Attempts to gain access to the resource. Returns a pointer to the + resource on success or ``NULL`` if it has been revoked. + +``void revocable_release(struct revocable *rev);`` + Releases access to the resource, exiting the SRCU critical section. + +The ``REVOCABLE()`` Macro +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D + +The ``REVOCABLE()`` macro simplifies the access-release cycle for consumer= s, +ensuring that ``revocable_release()`` is always called, even in the case of +an early exit. + +``REVOCABLE(rev, res)`` + * ``rev``: The consumer's ``struct revocable *`` handle. + * ``res``: A pointer variable that will be assigned the resource. + +The macro creates a ``for`` loop that executes exactly once. Inside the l= oop, +``res`` is populated with the result of ``revocable_try_access()``. The +consumer code **must** check if ``res`` is ``NULL`` before using it. The +``revocable_release()`` function is automatically called when the scope of +the loop is exited. + +Example Usage +------------- + +.. code-block:: c + + void consumer_use_resource(struct revocable *rev) + { + struct foo_resource *res; + + REVOCABLE(rev, res) { + // Always check if the resource is valid. + if (!res) { + pr_warn("Resource is not available\n"); + return; + } + + // At this point, 'res' is guaranteed to be valid until + // this block exits. + do_something_with(res); + } + + // revocable_release() is automatically called here. + } diff --git a/MAINTAINERS b/MAINTAINERS index fa7f80bd7b2f..5d11aeeb546e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21877,6 +21877,13 @@ F: include/uapi/linux/rseq.h F: kernel/rseq.c F: tools/testing/selftests/rseq/ =20 +REVOCABLE RESOURCE MANAGEMENT +M: Tzung-Bi Shih +L: linux-kernel@vger.kernel.org +S: Maintained +F: drivers/base/revocable.c +F: include/linux/revocable.h + RFKILL M: Johannes Berg L: linux-wireless@vger.kernel.org diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 8074a10183dc..bdf854694e39 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -6,7 +6,7 @@ obj-y :=3D component.o core.o bus.o dd.o syscore.o \ cpu.o firmware.o init.o map.o devres.o \ attribute_container.o transport_class.o \ topology.o container.o property.o cacheinfo.o \ - swnode.o faux.o + swnode.o faux.o revocable.o obj-$(CONFIG_AUXILIARY_BUS) +=3D auxiliary.o obj-$(CONFIG_DEVTMPFS) +=3D devtmpfs.o obj-y +=3D power/ diff --git a/drivers/base/revocable.c b/drivers/base/revocable.c new file mode 100644 index 000000000000..80a48896b241 --- /dev/null +++ b/drivers/base/revocable.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 Google LLC + * + * Revocable resource management + */ + +#include +#include +#include +#include +#include + +/** + * DOC: Overview + * + * Some resources can be removed asynchronously, for example, resources + * provided by a hot-pluggable device like USB. When holding a reference + * to such a resource, it's possible for the resource to be removed and + * its memory freed, leading to use-after-free errors on subsequent access. + * + * Introduce the revocable to establish weak references to such resources. + * It allows a resource consumer to safely attempt to access a resource + * that might be freed at any time by the resource provider. + * + * The implementation uses a provider/consumer model built on Sleepable + * RCU (SRCU) to guarantee safe memory access: + * + * - A resource provider allocates a struct revocable_provider and + * initializes it with a pointer to the resource. + * + * - A resource consumer that wants to access the resource allocates a + * struct revocable which holds a reference to the provider. + * + * - To access the resource, the consumer uses revocable_try_access(). + * This function enters an SRCU read-side critical section and returns + * the pointer to the resource. If the provider has already freed the + * resource, it returns NULL. After use, the consumer calls + * revocable_release() to exit the SRCU critical section. The + * REVOCABLE() is a convenient helper for doing that. + * + * - When the provider needs to remove the resource, it calls + * revocable_provider_free(). This function sets the internal resource + * pointer to NULL and then calls synchronize_srcu() to wait for all + * current readers to finish before the resource can be completely torn + * down. + */ + +/** + * struct revocable_provider - A handle for resource provider. + * @srcu: The SRCU to protect the resource. + * @res: The pointer of resource. It can point to anything. + * @kref: The refcount for this handle. + */ +struct revocable_provider { + struct srcu_struct srcu; + void __rcu *res; + struct kref kref; +}; + +/** + * struct revocable - A handle for resource consumer. + * @rp: The pointer of resource provider. + * @idx: The index for the RCU critical section. + */ +struct revocable { + struct revocable_provider *rp; + int idx; +}; + +/** + * revocable_provider_alloc() - Allocate struct revocable_provider. + * @res: The pointer of resource. + * + * This holds an initial refcount to the struct. + * + * Return: The pointer of struct revocable_provider. NULL on errors. + */ +struct revocable_provider *revocable_provider_alloc(void *res) +{ + struct revocable_provider *rp; + + rp =3D kzalloc(sizeof(*rp), GFP_KERNEL); + if (!rp) + return NULL; + + init_srcu_struct(&rp->srcu); + rcu_assign_pointer(rp->res, res); + synchronize_srcu(&rp->srcu); + kref_init(&rp->kref); + + return rp; +} +EXPORT_SYMBOL_GPL(revocable_provider_alloc); + +static void revocable_provider_release(struct kref *kref) +{ + struct revocable_provider *rp =3D container_of(kref, + struct revocable_provider, kref); + + cleanup_srcu_struct(&rp->srcu); + kfree(rp); +} + +/** + * revocable_provider_free() - Free struct revocable_provider. + * @rp: The pointer of resource provider. + * + * This sets the resource `(struct revocable_provider *)->res` to NULL to + * indicate the resource has gone. + * + * This drops the refcount to the resource provider. If it is the final + * reference, revocable_provider_release() will be called to free the stru= ct. + */ +void revocable_provider_free(struct revocable_provider *rp) +{ + rcu_assign_pointer(rp->res, NULL); + synchronize_srcu(&rp->srcu); + kref_put(&rp->kref, revocable_provider_release); +} +EXPORT_SYMBOL_GPL(revocable_provider_free); + +static void devm_revocable_provider_free(void *data) +{ + struct revocable_provider *rp =3D data; + + revocable_provider_free(rp); +} + +/** + * devm_revocable_provider_alloc() - Dev-managed revocable_provider_alloc(= ). + * @dev: The device. + * @res: The pointer of resource. + * + * It is convenient to allocate providers via this function if the @res is + * also tied to the lifetime of the @dev. revocable_provider_free() will + * be called automatically when the device is unbound. + * + * This holds an initial refcount to the struct. + * + * Return: The pointer of struct revocable_provider. NULL on errors. + */ +struct revocable_provider *devm_revocable_provider_alloc(struct device *de= v, + void *res) +{ + struct revocable_provider *rp; + + rp =3D revocable_provider_alloc(res); + if (!rp) + return NULL; + + if (devm_add_action_or_reset(dev, devm_revocable_provider_free, rp)) + return NULL; + + return rp; +} +EXPORT_SYMBOL_GPL(devm_revocable_provider_alloc); + +/** + * revocable_alloc() - Allocate struct revocable_provider. + * @rp: The pointer of resource provider. + * + * This holds a refcount to the resource provider. + * + * Return: The pointer of struct revocable_provider. NULL on errors. + */ +struct revocable *revocable_alloc(struct revocable_provider *rp) +{ + struct revocable *rev; + + rev =3D kzalloc(sizeof(*rev), GFP_KERNEL); + if (!rev) + return NULL; + + rev->rp =3D rp; + kref_get(&rp->kref); + + return rev; +} +EXPORT_SYMBOL_GPL(revocable_alloc); + +/** + * revocable_free() - Free struct revocable. + * @rev: The pointer of struct revocable. + * + * This drops a refcount to the resource provider. If it is the final + * reference, revocable_provider_release() will be called to free the stru= ct. + */ +void revocable_free(struct revocable *rev) +{ + struct revocable_provider *rp =3D rev->rp; + + kref_put(&rp->kref, revocable_provider_release); + kfree(rev); +} +EXPORT_SYMBOL_GPL(revocable_free); + +/** + * revocable_try_access() - Try to access the resource. + * @rev: The pointer of struct revocable. + * + * This tries to de-reference to the resource and enters a RCU critical + * section. + * + * Return: The pointer to the resource. NULL if the resource has gone. + */ +void *revocable_try_access(struct revocable *rev) __acquires(&rev->rp->src= u) +{ + struct revocable_provider *rp =3D rev->rp; + + rev->idx =3D srcu_read_lock(&rp->srcu); + return rcu_dereference(rp->res); +} +EXPORT_SYMBOL_GPL(revocable_try_access); + +/** + * revocable_release() - Stop accessing to the resource. + * @rev: The pointer of struct revocable. + * + * Call this function to indicate the resource is no longer used. It exits + * the RCU critical section. + */ +void revocable_release(struct revocable *rev) __releases(&rev->rp->srcu) +{ + struct revocable_provider *rp =3D rev->rp; + + srcu_read_unlock(&rp->srcu, rev->idx); +} +EXPORT_SYMBOL_GPL(revocable_release); diff --git a/include/linux/revocable.h b/include/linux/revocable.h new file mode 100644 index 000000000000..17d9b7ce633d --- /dev/null +++ b/include/linux/revocable.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2025 Google LLC + */ + +#ifndef __LINUX_REVOCABLE_H +#define __LINUX_REVOCABLE_H + +#include + +struct device; +struct revocable; +struct revocable_provider; + +struct revocable_provider *revocable_provider_alloc(void *res); +void revocable_provider_free(struct revocable_provider *rp); +struct revocable_provider *devm_revocable_provider_alloc(struct device *de= v, + void *res); + +struct revocable *revocable_alloc(struct revocable_provider *rp); +void revocable_free(struct revocable *rev); +void *revocable_try_access(struct revocable *rev) __acquires(&rev->rp->src= u); +void revocable_release(struct revocable *rev) __releases(&rev->rp->srcu); + +DEFINE_FREE(revocable, struct revocable *, if (_T) revocable_release(_T)) + +#define _REVOCABLE(_rev, _label, _res) \ + for (struct revocable *__UNIQUE_ID(name) __free(revocable) =3D _rev; \ + (_res =3D revocable_try_access(_rev)) || true; ({ goto _label; })) \ + if (0) { \ +_label: \ + break; \ + } else + +#define REVOCABLE(_rev, _res) _REVOCABLE(_rev, __UNIQUE_ID(label), _res) + +#endif /* __LINUX_REVOCABLE_H */ --=20 2.51.0.384.g4c02a37b29-goog From nobody Thu Oct 2 19:03:40 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0BC4D2D6E6D; Fri, 12 Sep 2025 08:18:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757665101; cv=none; b=jzkM2boUzRi5sUm3y5mpG8VkdHTeXPtE8aPrTQAEboCt0eKsfDhXNc1Cs1VOa25OZ+EhN/gNZ+Y9QGBw3cTvxbpfx3L52gCNpBX+mNjqoCwpak5HH2tAthjUkQS4jTttmBnCOAPkvnnNLVfEqqcCpM8KP650HfuxRFJ7UdbSOl0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757665101; c=relaxed/simple; bh=spQeThlFrFG2oNH+ReAuZ4G4VATaAH+Se8jFpzGV7Z8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=VzPc3J0LCAZYVwig6jzJaSNVdwG0MBCtTg6RBOZqLVXElsi/YK5PLzFCb3/yVUQd7os0jJ/RtSRYTfKIpuqMLnaMEaTs3FMH5F0GCsuH6kn0/i0MqER082EKcCZQ9FbkZqv43sTFM5oJMf0Rd5z3CaRz/Jb3q4nIvYr4PbqQYUE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=WFEihX0i; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="WFEihX0i" Received: by smtp.kernel.org (Postfix) with ESMTPSA id B67ABC4CEF9; Fri, 12 Sep 2025 08:18:18 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1757665100; bh=spQeThlFrFG2oNH+ReAuZ4G4VATaAH+Se8jFpzGV7Z8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WFEihX0i1DmIO12katqR7FqTuZl/YsPOqi0ovNgSoz/qy5e1cIOHUlPERqyFJRI5k l4GzUYAbbPlAyCDUoc3OHq2mgrH74TfGpmBWkGS2zU/crvP6NYjU3JsZmeR8pqJRj9 Xo5DALJaI0keshGjNbKNC0C7AS7HwNIRMY6XDZhX6S0RV1noe/LzZJrDIgmiGyi8+M JmZ6yBG17tVOSSwZDLrTTbXjVa9BPkhtdJny76XfgId1JfiDpJst5Gisou+LzDQcPL iO883BkolVN3hDtBL+T00WZAzdWm143nJ3x7zV/gzxsRVRGcl91Jr0Rajmv2iy0Ifr cM+uZZ/GCDFPw== From: Tzung-Bi Shih To: Benson Leung , Greg Kroah-Hartman , "Rafael J . Wysocki" , Danilo Krummrich Cc: Jonathan Corbet , Shuah Khan , Dawid Niedzwiecki , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, chrome-platform@lists.linux.dev, linux-kselftest@vger.kernel.org, tzungbi@kernel.org Subject: [PATCH v3 2/5] revocable: Add Kunit test cases Date: Fri, 12 Sep 2025 08:17:14 +0000 Message-ID: <20250912081718.3827390-3-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0.384.g4c02a37b29-goog In-Reply-To: <20250912081718.3827390-1-tzungbi@kernel.org> References: <20250912081718.3827390-1-tzungbi@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add Kunit test cases for the revocable API. The test cases cover the following scenarios: - Basic: Verifies that a consumer can successfully access the resource provided via the provider. - Revocation: Verifies that after the provider revokes the resource, the consumer correctly receives a NULL pointer on a subsequent access. - Macro: Same as "Revocation" but uses the REVOCABLE(). A way to run the test: $ ./tools/testing/kunit/kunit.py run \ --kconfig_add CONFIG_REVOCABLE_KUNIT_TEST=3Dy \ revocable_test Signed-off-by: Tzung-Bi Shih --- v3: - 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 5d11aeeb546e..74930474f1bd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21882,6 +21882,7 @@ M: Tzung-Bi Shih L: linux-kernel@vger.kernel.org S: Maintained F: drivers/base/revocable.c +F: drivers/base/revocable_test.c F: include/linux/revocable.h =20 RFKILL diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 064eb52ff7e2..a6488035f63d 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -244,3 +244,11 @@ config FW_DEVLINK_SYNC_STATE_TIMEOUT work on. =20 endmenu + +# Kunit test cases +config REVOCABLE_KUNIT_TEST + tristate "Kunit tests for revocable" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + Kunit tests for the revocable API. diff --git a/drivers/base/Makefile b/drivers/base/Makefile index bdf854694e39..4185aaa9bbb9 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -35,3 +35,6 @@ ccflags-$(CONFIG_DEBUG_DRIVER) :=3D -DDEBUG # define_trace.h needs to know how to find our header CFLAGS_trace.o :=3D -I$(src) obj-$(CONFIG_TRACING) +=3D trace.o + +# Kunit test cases +obj-$(CONFIG_REVOCABLE_KUNIT_TEST) +=3D revocable_test.o diff --git a/drivers/base/revocable_test.c b/drivers/base/revocable_test.c new file mode 100644 index 000000000000..d1ec7e47cd1d --- /dev/null +++ b/drivers/base/revocable_test.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 Google LLC + * + * Kunit tests for the revocable API. + * + * The test cases cover the following scenarios: + * + * - Basic: Verifies that a consumer can successfully access the resource + * provided via the provider. + * + * - Revocation: Verifies that after the provider revokes the resource, + * the consumer correctly receives a NULL pointer on a subsequent access. + * + * - Macro: Same as "Revocation" but uses the REVOCABLE(). + */ + +#include +#include + +static void revocable_test_basic(struct kunit *test) +{ + struct revocable_provider *rp; + struct revocable *rev; + void *real_res =3D (void *)0x12345678, *res; + + rp =3D revocable_provider_alloc(real_res); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); + + rev =3D revocable_alloc(rp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); + + res =3D revocable_try_access(rev); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + revocable_release(rev); + + revocable_free(rev); + revocable_provider_free(rp); +} + +static void revocable_test_revocation(struct kunit *test) +{ + struct revocable_provider *rp; + struct revocable *rev; + void *real_res =3D (void *)0x12345678, *res; + + rp =3D revocable_provider_alloc(real_res); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); + + rev =3D revocable_alloc(rp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); + + res =3D revocable_try_access(rev); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + revocable_release(rev); + + revocable_provider_free(rp); + + res =3D revocable_try_access(rev); + KUNIT_EXPECT_PTR_EQ(test, res, NULL); + revocable_release(rev); + + revocable_free(rev); +} + +static void revocable_test_macro(struct kunit *test) +{ + struct revocable_provider *rp; + struct revocable *rev; + void *real_res =3D (void *)0x12345678, *res; + bool accessed; + + rp =3D revocable_provider_alloc(real_res); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp); + + rev =3D revocable_alloc(rp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev); + + accessed =3D false; + REVOCABLE(rev, res) { + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + accessed =3D true; + } + KUNIT_EXPECT_TRUE(test, accessed); + + revocable_provider_free(rp); + + accessed =3D false; + REVOCABLE(rev, res) { + KUNIT_EXPECT_PTR_EQ(test, res, NULL); + accessed =3D true; + } + KUNIT_EXPECT_TRUE(test, accessed); + + revocable_free(rev); +} + +static struct kunit_case revocable_test_cases[] =3D { + KUNIT_CASE(revocable_test_basic), + KUNIT_CASE(revocable_test_revocation), + KUNIT_CASE(revocable_test_macro), + {} +}; + +static struct kunit_suite revocable_test_suite =3D { + .name =3D "revocable_test", + .test_cases =3D revocable_test_cases, +}; + +kunit_test_suite(revocable_test_suite); --=20 2.51.0.384.g4c02a37b29-goog From nobody Thu Oct 2 19:03:40 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 24D042D4B55; Fri, 12 Sep 2025 08:18:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757665103; cv=none; b=XHkiU2hsbxrNiIzSXEB40JurHjg56rTI+CuYavcDJE6I1GyjkpdApE9jfIjvufGWyfyP9WLXVsbmXXXUaWA5blkjsclwkfDNuMW4Kzf0vX8YxBnHojCUxgvhdWHtJItFzkDzhqHT+3XNNMsLZpp8WwxAPc44DtGj64uzOKw9uZg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757665103; c=relaxed/simple; bh=sle8I572Scul2j3zL7W08UbC2UebKdR4kDhym9c1GRI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=L7TiLQbHIjM5AOjF12sFI+DNKUXIapgVH6+6dIWQcQyS8miP/+csgSXRMOpVBkahGSmryEh/GdjB800ECzlgtan+isaaQDSuKkQTsNARLBJrB6J2h6VZ0To4LezBpD9AxMns1kBy2EzhFKrnzETkkeNztcFr2fJgUWohl7ztce4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=r2BFH+OX; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="r2BFH+OX" Received: by smtp.kernel.org (Postfix) with ESMTPSA id DC970C4CEF7; Fri, 12 Sep 2025 08:18:20 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1757665102; bh=sle8I572Scul2j3zL7W08UbC2UebKdR4kDhym9c1GRI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=r2BFH+OXXocK9uvzPEkzvBfA9wa6DTZh0LtiJvZ26SC0FM4IwPL5NRh81ReXgnsoB QzRJYLsVqNc56VImNTpok4V8nfin87PsuxA2qctrkslBdYldWOMXNqbZsKdPXB6zVz OC1Rot+toznk9o/fvOFfMbceWT723bTsh2UdI6pvxVtvNbzYjlaTsqz3Pbip+uxxz1 IvlmkUY+VcNVwlKUYYoP9OwBG0ZW+D74szhgsYZHqz5vFWCbT5Nwpk2NgxbbytqnqN knd1qAv1tME6etcKL+GNdgkIkxXNJEcAv3HV6FpVBjYsflyTR42alI7kw06AKg+U3m lgfmdQm/2vYjQ== From: Tzung-Bi Shih To: Benson Leung , Greg Kroah-Hartman , "Rafael J . Wysocki" , Danilo Krummrich Cc: Jonathan Corbet , Shuah Khan , Dawid Niedzwiecki , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, chrome-platform@lists.linux.dev, linux-kselftest@vger.kernel.org, tzungbi@kernel.org Subject: [PATCH v3 3/5] selftests: revocable: Add kselftest cases Date: Fri, 12 Sep 2025 08:17:15 +0000 Message-ID: <20250912081718.3827390-4-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0.384.g4c02a37b29-goog In-Reply-To: <20250912081718.3827390-1-tzungbi@kernel.org> References: <20250912081718.3827390-1-tzungbi@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add kselftest cases for the revocable API. The test consists of three parts: - A kernel module (revocable_test.ko) that creates a debugfs interface with `/provider` and `/consumer` files. - A user-space C program (revocable_test) that uses the kselftest harness to interact with the debugfs files. - An orchestrating shell script (test-revocable.sh) that loads the module, runs the C program, and unloads the module. The test cases cover the following scenarios: - Basic: Verifies that a consumer can successfully access the resource provided via the provider. - Revocation: Verifies that after the provider revokes the resource, the consumer correctly receives a NULL pointer on a subsequent access. - Macro: Same as "Revocation" but uses the REVOCABLE(). Signed-off-by: Tzung-Bi Shih --- v3: - No changes. v2: https://lore.kernel.org/chrome-platform/20250820081645.847919-4-tzungbi= @kernel.org - New in the series. MAINTAINERS | 1 + tools/testing/selftests/Makefile | 1 + .../selftests/drivers/base/revocable/Makefile | 7 + .../drivers/base/revocable/revocable_test.c | 116 +++++++++++ .../drivers/base/revocable/test-revocable.sh | 39 ++++ .../base/revocable/test_modules/Makefile | 10 + .../revocable/test_modules/revocable_test.c | 188 ++++++++++++++++++ 7 files changed, 362 insertions(+) create mode 100644 tools/testing/selftests/drivers/base/revocable/Makefile create mode 100644 tools/testing/selftests/drivers/base/revocable/revocabl= e_test.c create mode 100755 tools/testing/selftests/drivers/base/revocable/test-rev= ocable.sh create mode 100644 tools/testing/selftests/drivers/base/revocable/test_mod= ules/Makefile create mode 100644 tools/testing/selftests/drivers/base/revocable/test_mod= ules/revocable_test.c diff --git a/MAINTAINERS b/MAINTAINERS index 74930474f1bd..583d818262c4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21884,6 +21884,7 @@ S: Maintained F: drivers/base/revocable.c F: drivers/base/revocable_test.c F: include/linux/revocable.h +F: tools/testing/selftests/drivers/base/revocable/ =20 RFKILL M: Johannes Berg diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Mak= efile index 850dacb09929..d9700ce9eb64 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -17,6 +17,7 @@ TARGETS +=3D damon TARGETS +=3D devices/error_logs TARGETS +=3D devices/probe TARGETS +=3D dmabuf-heaps +TARGETS +=3D drivers/base/revocable TARGETS +=3D drivers/dma-buf TARGETS +=3D drivers/ntsync TARGETS +=3D drivers/s390x/uvdevice diff --git a/tools/testing/selftests/drivers/base/revocable/Makefile b/tool= s/testing/selftests/drivers/base/revocable/Makefile new file mode 100644 index 000000000000..afa5ca0fa452 --- /dev/null +++ b/tools/testing/selftests/drivers/base/revocable/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 + +TEST_GEN_MODS_DIR :=3D test_modules +TEST_GEN_PROGS_EXTENDED :=3D revocable_test +TEST_PROGS :=3D test-revocable.sh + +include ../../../lib.mk diff --git a/tools/testing/selftests/drivers/base/revocable/revocable_test.= c b/tools/testing/selftests/drivers/base/revocable/revocable_test.c new file mode 100644 index 000000000000..cdef49dc4095 --- /dev/null +++ b/tools/testing/selftests/drivers/base/revocable/revocable_test.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 Google LLC + * + * A selftest for the revocable API. + * + * The test cases cover the following scenarios: + * + * - Basic: Verifies that a consumer can successfully access the resource + * provided via the provider. + * + * - Revocation: Verifies that after the provider revokes the resource, + * the consumer correctly receives a NULL pointer on a subsequent access. + * + * - Macro: Same as "Revocation" but uses the REVOCABLE(). + */ + +#include +#include + +#include "../../../kselftest_harness.h" + +#define DEBUGFS_PATH "/sys/kernel/debug/revocable_test" +#define TEST_CMD_RESOURCE_GONE "resource_gone" +#define TEST_DATA "12345678" +#define TEST_MAGIC_OFFSET 0x1234 + +FIXTURE(revocable_fixture) { + int pfd; + int cfd; +}; + +FIXTURE_SETUP(revocable_fixture) { + int ret; + + self->pfd =3D open(DEBUGFS_PATH "/provider", O_WRONLY); + ASSERT_NE(-1, self->pfd) + TH_LOG("failed to open provider fd"); + + ret =3D write(self->pfd, TEST_DATA, strlen(TEST_DATA)); + ASSERT_NE(-1, ret) { + close(self->pfd); + TH_LOG("failed to write test data"); + } + + self->cfd =3D open(DEBUGFS_PATH "/consumer", O_RDONLY); + ASSERT_NE(-1, self->cfd) + TH_LOG("failed to open consumer fd"); +} + +FIXTURE_TEARDOWN(revocable_fixture) { + close(self->cfd); + close(self->pfd); +} + +/* + * ASSERT_* is only available in TEST or TEST_F block. Use + * macro for the helper. + */ +#define READ_TEST_DATA(_fd, _offset, _data, _msg) \ + do { \ + int ret; \ + \ + ret =3D lseek(_fd, _offset, SEEK_SET); \ + ASSERT_NE(-1, ret) \ + TH_LOG("failed to lseek"); \ + \ + ret =3D read(_fd, _data, sizeof(_data) - 1); \ + ASSERT_NE(-1, ret) \ + TH_LOG(_msg); \ + data[ret] =3D '\0'; \ + } while (0) + +TEST_F(revocable_fixture, basic) { + char data[16]; + + READ_TEST_DATA(self->cfd, 0, data, "failed to read test data"); + EXPECT_STREQ(TEST_DATA, data); +} + +TEST_F(revocable_fixture, revocation) { + char data[16]; + int ret; + + READ_TEST_DATA(self->cfd, 0, data, "failed to read test data"); + EXPECT_STREQ(TEST_DATA, data); + + ret =3D write(self->pfd, TEST_CMD_RESOURCE_GONE, + strlen(TEST_CMD_RESOURCE_GONE)); + ASSERT_NE(-1, ret) + TH_LOG("failed to write resource gone cmd"); + + READ_TEST_DATA(self->cfd, 0, data, + "failed to read test data after resource gone"); + EXPECT_STREQ("(null)", data); +} + +TEST_F(revocable_fixture, macro) { + char data[16]; + int ret; + + READ_TEST_DATA(self->cfd, TEST_MAGIC_OFFSET, data, + "failed to read test data"); + EXPECT_STREQ(TEST_DATA, data); + + ret =3D write(self->pfd, TEST_CMD_RESOURCE_GONE, + strlen(TEST_CMD_RESOURCE_GONE)); + ASSERT_NE(-1, ret) + TH_LOG("failed to write resource gone cmd"); + + READ_TEST_DATA(self->cfd, TEST_MAGIC_OFFSET, data, + "failed to read test data after resource gone"); + EXPECT_STREQ("(null)", data); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/drivers/base/revocable/test-revocable.= sh b/tools/testing/selftests/drivers/base/revocable/test-revocable.sh new file mode 100755 index 000000000000..3a34be28001a --- /dev/null +++ b/tools/testing/selftests/drivers/base/revocable/test-revocable.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +mod_name=3D"revocable_test" +ksft_fail=3D1 +ksft_skip=3D4 + +if [ "$(id -u)" -ne 0 ]; then + echo "$0: Must be run as root" + exit "$ksft_skip" +fi + +if ! which insmod > /dev/null 2>&1; then + echo "$0: Need insmod" + exit "$ksft_skip" +fi + +if ! which rmmod > /dev/null 2>&1; then + echo "$0: Need rmmod" + exit "$ksft_skip" +fi + +insmod test_modules/"${mod_name}".ko + +if [ ! -d /sys/kernel/debug/revocable_test/ ]; then + mount -t debugfs none /sys/kernel/debug/ + + if [ ! -d /sys/kernel/debug/revocable_test/ ]; then + echo "$0: Error mounting debugfs" + exit "$ksft_fail" + fi +fi + +./revocable_test +ret=3D$? + +rmmod "${mod_name}" + +exit "${ret}" diff --git a/tools/testing/selftests/drivers/base/revocable/test_modules/Ma= kefile b/tools/testing/selftests/drivers/base/revocable/test_modules/Makefi= le new file mode 100644 index 000000000000..f29e4f909402 --- /dev/null +++ b/tools/testing/selftests/drivers/base/revocable/test_modules/Makefile @@ -0,0 +1,10 @@ +TESTMODS_DIR :=3D $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))= )) +KDIR ?=3D /lib/modules/$(shell uname -r)/build + +obj-m +=3D revocable_test.o + +all: + $(Q)$(MAKE) -C $(KDIR) M=3D$(TESTMODS_DIR) + +clean: + $(Q)$(MAKE) -C $(KDIR) M=3D$(TESTMODS_DIR) clean diff --git a/tools/testing/selftests/drivers/base/revocable/test_modules/re= vocable_test.c b/tools/testing/selftests/drivers/base/revocable/test_module= s/revocable_test.c new file mode 100644 index 000000000000..9166b860a55a --- /dev/null +++ b/tools/testing/selftests/drivers/base/revocable/test_modules/revocable= _test.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 Google LLC + * + * A kernel module for testing the revocable API. + */ + +#include +#include +#include +#include + +#define TEST_CMD_RESOURCE_GONE "resource_gone" +#define TEST_MAGIC_OFFSET 0x1234 + +static struct dentry *debugfs_dir; + +struct revocable_test_provider_priv { + struct revocable_provider *rp; + struct dentry *dentry; + char res[16]; +}; + +static int revocable_test_consumer_open(struct inode *inode, struct file *= filp) +{ + struct revocable *rev; + struct revocable_provider *rp =3D inode->i_private; + + rev =3D revocable_alloc(rp); + if (!rev) + return -ENOMEM; + filp->private_data =3D rev; + + return 0; +} + +static int revocable_test_consumer_release(struct inode *inode, + struct file *filp) +{ + struct revocable *rev =3D filp->private_data; + + revocable_free(rev); + return 0; +} + +static ssize_t revocable_test_consumer_read(struct file *filp, + char __user *buf, + size_t count, loff_t *offset) +{ + char *res; + char data[16]; + size_t len; + struct revocable *rev =3D filp->private_data; + + switch (*offset) { + case 0: + res =3D revocable_try_access(rev); + snprintf(data, sizeof(data), "%s", res ?: "(null)"); + revocable_release(rev); + break; + case TEST_MAGIC_OFFSET: + REVOCABLE(rev, res) + snprintf(data, sizeof(data), "%s", res ?: "(null)"); + break; + default: + return 0; + } + + len =3D min_t(size_t, strlen(data), count); + if (copy_to_user(buf, data, len)) + return -EFAULT; + + *offset =3D len; + return len; +} + +static const struct file_operations revocable_test_consumer_fops =3D { + .open =3D revocable_test_consumer_open, + .release =3D revocable_test_consumer_release, + .read =3D revocable_test_consumer_read, + .llseek =3D default_llseek, +}; + +static int revocable_test_provider_open(struct inode *inode, struct file *= filp) +{ + struct revocable_test_provider_priv *priv; + + priv =3D kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + filp->private_data =3D priv; + + return 0; +} + +static int revocable_test_provider_release(struct inode *inode, + struct file *filp) +{ + struct revocable_test_provider_priv *priv =3D filp->private_data; + + debugfs_remove(priv->dentry); + if (priv->rp) + revocable_provider_free(priv->rp); + kfree(priv); + + return 0; +} + +static ssize_t revocable_test_provider_write(struct file *filp, + const char __user *buf, + size_t count, loff_t *offset) +{ + size_t copied; + char data[64]; + struct revocable_test_provider_priv *priv =3D filp->private_data; + + copied =3D strncpy_from_user(data, buf, sizeof(data)); + if (copied < 0) + return copied; + if (copied =3D=3D sizeof(data)) + data[sizeof(data) - 1] =3D '\0'; + + /* + * Note: The test can't just close the FD for signaling the + * resource gone. Subsequent file operations on the opening + * FD of debugfs return -EIO after calling debugfs_remove(). + * See also debugfs_file_get(). + * + * Here is a side command channel for signaling the resource + * gone. + */ + if (!strcmp(data, TEST_CMD_RESOURCE_GONE)) { + revocable_provider_free(priv->rp); + priv->rp =3D NULL; + } else { + if (priv->res[0] !=3D '\0') + return 0; + + strscpy(priv->res, data); + + priv->rp =3D revocable_provider_alloc(&priv->res); + if (!priv->rp) + return -ENOMEM; + + priv->dentry =3D debugfs_create_file("consumer", 0400, + debugfs_dir, priv->rp, + &revocable_test_consumer_fops); + if (!priv->dentry) { + revocable_provider_free(priv->rp); + return -ENOMEM; + } + } + + return copied; +} + +static const struct file_operations revocable_test_provider_fops =3D { + .open =3D revocable_test_provider_open, + .release =3D revocable_test_provider_release, + .write =3D revocable_test_provider_write, +}; + +static int __init revocable_test_init(void) +{ + debugfs_dir =3D debugfs_create_dir("revocable_test", NULL); + if (!debugfs_dir) + return -ENOMEM; + + if (!debugfs_create_file("provider", 0200, debugfs_dir, NULL, + &revocable_test_provider_fops)) { + debugfs_remove_recursive(debugfs_dir); + return -ENOMEM; + } + + return 0; +} + +static void __exit revocable_test_exit(void) +{ + debugfs_remove_recursive(debugfs_dir); +} + +module_init(revocable_test_init); +module_exit(revocable_test_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tzung-Bi Shih "); +MODULE_DESCRIPTION("Revocable Kselftest"); --=20 2.51.0.384.g4c02a37b29-goog From nobody Thu Oct 2 19:03:40 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F17A52D7DDF; Fri, 12 Sep 2025 08:18:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757665105; cv=none; b=qThG4SIVZp3Mabp2mxXtqCjVNVXNl/t1VPN6aPCTzUiJLOovJ161IjQX7X3E23BOMloy8Pl7blYH7U8oMuChtKzJN6fHUSN+Iu0nIz95IDzk63oNJIIrWCzPg1pct2mcIeXurtzY+HnTkrH7oZMoVJbQJHRtTgyFk4ODUf1xg/4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757665105; c=relaxed/simple; bh=Rbg3cLqijdLmtkNpgyEe7p44E/PCsoI9YpDR32oNjK4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=LF+hBU95Clnevt3bZDIuQqjO4caIP/hBcypGCbpft1m6/fOJMVXafBD6ooDeZAeA4F3zqirMTCP5gqYH6NnGN92TGFTk9v6BmprimbUEPu1Zdd0WtasrOsqJZf4u1X9xtYuXP+5cN267ZT15uc4MrpM5YltZBesR4kWpmkuW+zc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=k0eGVbdC; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="k0eGVbdC" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 0DD17C4CEFA; Fri, 12 Sep 2025 08:18:22 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1757665104; bh=Rbg3cLqijdLmtkNpgyEe7p44E/PCsoI9YpDR32oNjK4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=k0eGVbdCVLF+MqeBrWVV1I3lZMJU6lu5pAuKsnFjigafi8L0zy0iqwSdV3NA+BxCX h/FLKYSRtaH/3TnZ97PLJJsW0K1jcDhvbjmMpNP9DLQvhEoDbeGQUA4Gb9fL4TtoRJ qvMST/w5D9o/eRGSUv8/laG6fS2Z3b9zLTYcZhonH1TbwVQwq56kOTOUjz5hvvkUp9 DFud4LYF2AskbHlIzK2AowMviL7MzZZ8X6A0pxrFWqx6wkYHGUfqyThOnzFDmi7Dli O4oL4x+zwk+Fghu46W9eWSHq4c9nVmBhmaz3aQKOOihBj/CJqi3kBZrTUS9QoXaeA3 3yLDbnYIpLVYw== From: Tzung-Bi Shih To: Benson Leung , Greg Kroah-Hartman , "Rafael J . Wysocki" , Danilo Krummrich Cc: Jonathan Corbet , Shuah Khan , Dawid Niedzwiecki , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, chrome-platform@lists.linux.dev, linux-kselftest@vger.kernel.org, tzungbi@kernel.org Subject: [PATCH v3 4/5] platform/chrome: Protect cros_ec_device lifecycle with revocable Date: Fri, 12 Sep 2025 08:17:16 +0000 Message-ID: <20250912081718.3827390-5-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0.384.g4c02a37b29-goog In-Reply-To: <20250912081718.3827390-1-tzungbi@kernel.org> References: <20250912081718.3827390-1-tzungbi@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The cros_ec_device can be unregistered when the underlying device is removed. Other kernel drivers that interact with the EC may hold a pointer to the cros_ec_device, creating a risk of a use-after-free error if the EC device is removed while still being referenced. To prevent this, leverage the revocable and convert the underlying device drivers to resource providers of cros_ec_device. Signed-off-by: Tzung-Bi Shih --- v3: - 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-tzungb= i@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/cr= os_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 #include #include +#include #include #include =20 @@ -47,6 +48,10 @@ struct cros_ec_device *cros_ec_device_alloc(struct devic= e *dev) if (!ec_dev) return NULL; =20 + ec_dev->revocable_provider =3D devm_revocable_provider_alloc(dev, ec_dev); + if (!ec_dev->revocable_provider) + return NULL; + ec_dev->din_size =3D 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/pl= atform_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 #include #include +#include =20 #include =20 @@ -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; =20 struct blocking_notifier_head panic_notifier; + + struct revocable_provider *revocable_provider; }; =20 /** --=20 2.51.0.384.g4c02a37b29-goog From nobody Thu Oct 2 19:03:40 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5CA692E401; Fri, 12 Sep 2025 08:18:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757665107; cv=none; b=gpyy/4vHNFJtvuFOHeIab7CWZ/N3EJ0OxhNFucIHOlbYgNip1Nu6Lxunx+UMB6i3uSKE7/SPQFbvMjthuZyfTfGdySj/mdcX5wLcScMGt0A7RgCH6nVKa58SZIOMnjpf76Jwqd2Buaa2Qfq/jB2qtiT2IHKxWjetf58aemA5zSo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757665107; c=relaxed/simple; bh=LXH+SCB4NdyM2LOa87h4dCyYL3cFhysaqeVzB1y5l+8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=EnqxfKTvQkR3U9Wx8P6dVnV1Dosf7Pnl+gnD2V5gDVfhzV4/mduVwd0FLhU7QAC4NjVqllf32si2/oZHU005k+lSTeNqVtuxh+IdDemD0aMPozBbZQbPnjqV66xgeRuEpkyMuh4tvP1xQ+clcNDy3LIM9fl2rMpeylIy/qrhw3E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=PSTprtzm; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="PSTprtzm" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 33C2AC4CEF9; Fri, 12 Sep 2025 08:18:25 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1757665107; bh=LXH+SCB4NdyM2LOa87h4dCyYL3cFhysaqeVzB1y5l+8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=PSTprtzm9jmMz9S4lfLL6xX6qV67wiq97cKlAQXFBFgYj3WK81nPaMlSOvArGNwNF ZFnWgYO/S/qPW/1/YBPYaHNUqMcApzvH1gEoiLAHnLXmaytxu1mHZw0TgILejyGVTh 4oTes070KhUNWS83gT5CtfRsc/WzQTlobmSGl9f6t1vAZx3rmQQcRH+NPHtScdi9F5 3+I3RRWLtOiVp+MXb0C96OLsvli6dPe47FGz75fUCjMj2CVxHbXXoD3qlU4HNUcNwa nWMckyJ8qDT1WJEscZrfeZ2pwb75Qm2nCXaTKz+2TsPVCiQy2h2GVkJazfA/BL3fwM uTqpqzzs9+G+w== From: Tzung-Bi Shih To: Benson Leung , Greg Kroah-Hartman , "Rafael J . Wysocki" , Danilo Krummrich Cc: Jonathan Corbet , Shuah Khan , Dawid Niedzwiecki , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, chrome-platform@lists.linux.dev, linux-kselftest@vger.kernel.org, tzungbi@kernel.org Subject: [PATCH v3 5/5] platform/chrome: cros_ec_chardev: Consume cros_ec_device via revocable Date: Fri, 12 Sep 2025 08:17:17 +0000 Message-ID: <20250912081718.3827390-6-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0.384.g4c02a37b29-goog In-Reply-To: <20250912081718.3827390-1-tzungbi@kernel.org> References: <20250912081718.3827390-1-tzungbi@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The cros_ec_chardev driver provides a character device interface to the ChromeOS EC. A file handle to this device can remain open in userspace even if the underlying EC device is removed. This creates a classic use-after-free vulnerability. Any file operation (ioctl, release, etc.) on the open handle after the EC device has gone would access a stale pointer, leading to a system crash. To prevent this, leverage the revocable and convert cros_ec_chardev to a resource consumer of cros_ec_device. Signed-off-by: Tzung-Bi Shih --- v3: - Use specific labels for different cleanup in cros_ec_chardev_open(). v2: https://lore.kernel.org/chrome-platform/20250820081645.847919-6-tzungbi= @kernel.org - Rename "ref_proxy" -> "revocable". - Fix a sparse warning by removing the redundant __rcu annotation. v1: https://lore.kernel.org/chrome-platform/20250814091020.1302888-4-tzungb= i@kernel.org drivers/platform/chrome/cros_ec_chardev.c | 124 +++++++++++++++------- 1 file changed, 84 insertions(+), 40 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_chardev.c b/drivers/platform/c= hrome/cros_ec_chardev.c index c9d80ad5b57e..2677b756e31c 100644 --- a/drivers/platform/chrome/cros_ec_chardev.c +++ b/drivers/platform/chrome/cros_ec_chardev.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -32,7 +33,7 @@ #define CROS_MAX_EVENT_LEN PAGE_SIZE =20 struct chardev_priv { - struct cros_ec_device *ec_dev; + struct revocable *ec_dev_rev; struct notifier_block notifier; wait_queue_head_t wait_event; unsigned long event_mask; @@ -55,6 +56,7 @@ static int ec_get_version(struct chardev_priv *priv, char= *str, int maxlen) }; struct ec_response_get_version *resp; struct cros_ec_command *msg; + struct cros_ec_device *ec_dev; int ret; =20 msg =3D kzalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL); @@ -64,12 +66,19 @@ static int ec_get_version(struct chardev_priv *priv, ch= ar *str, int maxlen) msg->command =3D EC_CMD_GET_VERSION + priv->cmd_offset; msg->insize =3D sizeof(*resp); =20 - ret =3D cros_ec_cmd_xfer_status(priv->ec_dev, msg); - if (ret < 0) { - snprintf(str, maxlen, - "Unknown EC version, returned error: %d\n", - msg->result); - goto exit; + REVOCABLE(priv->ec_dev_rev, ec_dev) { + if (!ec_dev) { + ret =3D -ENODEV; + goto exit; + } + + ret =3D cros_ec_cmd_xfer_status(ec_dev, msg); + if (ret < 0) { + snprintf(str, maxlen, + "Unknown EC version, returned error: %d\n", + msg->result); + goto exit; + } } =20 resp =3D (struct ec_response_get_version *)msg->data; @@ -92,22 +101,30 @@ static int cros_ec_chardev_mkbp_event(struct notifier_= block *nb, { struct chardev_priv *priv =3D container_of(nb, struct chardev_priv, notifier); - struct cros_ec_device *ec_dev =3D priv->ec_dev; + struct cros_ec_device *ec_dev; struct ec_event *event; - unsigned long event_bit =3D 1 << ec_dev->event_data.event_type; - int total_size =3D sizeof(*event) + ec_dev->event_size; + unsigned long event_bit; + int total_size; + + REVOCABLE(priv->ec_dev_rev, ec_dev) { + if (!ec_dev) + return NOTIFY_DONE; + + event_bit =3D 1 << ec_dev->event_data.event_type; + total_size =3D sizeof(*event) + ec_dev->event_size; =20 - if (!(event_bit & priv->event_mask) || - (priv->event_len + total_size) > CROS_MAX_EVENT_LEN) - return NOTIFY_DONE; + if (!(event_bit & priv->event_mask) || + (priv->event_len + total_size) > CROS_MAX_EVENT_LEN) + return NOTIFY_DONE; =20 - event =3D kzalloc(total_size, GFP_KERNEL); - if (!event) - return NOTIFY_DONE; + event =3D kzalloc(total_size, GFP_KERNEL); + if (!event) + return NOTIFY_DONE; =20 - event->size =3D ec_dev->event_size; - event->event_type =3D ec_dev->event_data.event_type; - memcpy(event->data, &ec_dev->event_data.data, ec_dev->event_size); + event->size =3D ec_dev->event_size; + event->event_type =3D ec_dev->event_data.event_type; + memcpy(event->data, &ec_dev->event_data.data, ec_dev->event_size); + } =20 spin_lock(&priv->wait_event.lock); list_add_tail(&event->node, &priv->events); @@ -166,7 +183,12 @@ static int cros_ec_chardev_open(struct inode *inode, s= truct file *filp) if (!priv) return -ENOMEM; =20 - priv->ec_dev =3D ec_dev; + priv->ec_dev_rev =3D revocable_alloc(ec_dev->revocable_provider); + if (!priv->ec_dev_rev) { + ret =3D -ENOMEM; + goto free_priv; + } + priv->cmd_offset =3D ec->cmd_offset; filp->private_data =3D priv; INIT_LIST_HEAD(&priv->events); @@ -178,9 +200,14 @@ static int cros_ec_chardev_open(struct inode *inode, s= truct file *filp) &priv->notifier); if (ret) { dev_err(ec_dev->dev, "failed to register event notifier\n"); - kfree(priv); + goto free_revocable; } =20 + return 0; +free_revocable: + revocable_free(priv->ec_dev_rev); +free_priv: + kfree(priv); return ret; } =20 @@ -251,11 +278,15 @@ static ssize_t cros_ec_chardev_read(struct file *filp= , char __user *buffer, static int cros_ec_chardev_release(struct inode *inode, struct file *filp) { struct chardev_priv *priv =3D filp->private_data; - struct cros_ec_device *ec_dev =3D priv->ec_dev; + struct cros_ec_device *ec_dev; struct ec_event *event, *e; =20 - blocking_notifier_chain_unregister(&ec_dev->event_notifier, - &priv->notifier); + REVOCABLE(priv->ec_dev_rev, ec_dev) { + if (ec_dev) + blocking_notifier_chain_unregister(&ec_dev->event_notifier, + &priv->notifier); + } + revocable_free(priv->ec_dev_rev); =20 list_for_each_entry_safe(event, e, &priv->events, node) { list_del(&event->node); @@ -273,6 +304,7 @@ static long cros_ec_chardev_ioctl_xcmd(struct chardev_p= riv *priv, void __user *a { struct cros_ec_command *s_cmd; struct cros_ec_command u_cmd; + struct cros_ec_device *ec_dev; long ret; =20 if (copy_from_user(&u_cmd, arg, sizeof(u_cmd))) @@ -299,10 +331,17 @@ static long cros_ec_chardev_ioctl_xcmd(struct chardev= _priv *priv, void __user *a } =20 s_cmd->command +=3D priv->cmd_offset; - ret =3D cros_ec_cmd_xfer(priv->ec_dev, s_cmd); - /* Only copy data to userland if data was received. */ - if (ret < 0) - goto exit; + REVOCABLE(priv->ec_dev_rev, ec_dev) { + if (!ec_dev) { + ret =3D -ENODEV; + goto exit; + } + + ret =3D cros_ec_cmd_xfer(ec_dev, s_cmd); + /* Only copy data to userland if data was received. */ + if (ret < 0) + goto exit; + } =20 if (copy_to_user(arg, s_cmd, sizeof(*s_cmd) + s_cmd->insize)) ret =3D -EFAULT; @@ -313,24 +352,29 @@ static long cros_ec_chardev_ioctl_xcmd(struct chardev= _priv *priv, void __user *a =20 static long cros_ec_chardev_ioctl_readmem(struct chardev_priv *priv, void = __user *arg) { - struct cros_ec_device *ec_dev =3D priv->ec_dev; + struct cros_ec_device *ec_dev; struct cros_ec_readmem s_mem =3D { }; long num; =20 - /* Not every platform supports direct reads */ - if (!ec_dev->cmd_readmem) - return -ENOTTY; + REVOCABLE(priv->ec_dev_rev, ec_dev) { + if (!ec_dev) + return -ENODEV; =20 - if (copy_from_user(&s_mem, arg, sizeof(s_mem))) - return -EFAULT; + /* Not every platform supports direct reads */ + if (!ec_dev->cmd_readmem) + return -ENOTTY; =20 - if (s_mem.bytes > sizeof(s_mem.buffer)) - return -EINVAL; + if (copy_from_user(&s_mem, arg, sizeof(s_mem))) + return -EFAULT; =20 - num =3D ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes, - s_mem.buffer); - if (num <=3D 0) - return num; + if (s_mem.bytes > sizeof(s_mem.buffer)) + return -EINVAL; + + num =3D ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes, + s_mem.buffer); + if (num <=3D 0) + return num; + } =20 if (copy_to_user((void __user *)arg, &s_mem, sizeof(s_mem))) return -EFAULT; --=20 2.51.0.384.g4c02a37b29-goog