|
|
Subscribe / Log in / New account

Revocable references for transient devices

By Jonathan Corbet
September 22, 2025
Computers were once relatively static devices; if a peripheral was present at boot, it was unlikely to disappear while the system was operating. Those days are far behind us, though; devices can come and go at any time, often with no notice. That impermanence can create challenges for kernel code, which may not be expecting resources it is managing to make an abrupt exit. The revocable resource management patch set from Tzung-Bi Shih is meant to help with the creation of more robust — and more secure — kernel subsystems in a dynamic world.

Low-level drivers that manage transient devices must be prepared for those devices to vanish; there is no way around that. But those drivers often create data structures that are associated with the devices they manage, and export those structures to higher-level software. That creates a different sort of problem: the low-level driver knows when it can no longer communicate with a departed device, but it can be harder to know when it can safely free those data structures. Higher-level code may hold references to those structures for some time after the device disappears; freeing them prematurely would expose the kernel to use-after-free bugs and the exploits that are likely to follow.

Shih's patch set addresses this problem by turning those problematic references into a form of weak reference. The holder of such a reference must validate it prior to each use, and access to the resource can be revoked at (almost) any time. When used correctly, this API is meant to ensure that resources stay around as long as users exist, and to let the provider of those resources know when they can be safely freed.

Consider an entirely made-up example of a serial-port device that can come and go at the user's whim. When one of these devices is discovered, the driver will create a structure describing it that can be used by higher levels of software. An example user of this structure could be the SLIP network-interface driver, which many Linux users happily forgot about decades ago, but which still exists and is useful to, for example, communicate with your vape-powered web server. The serial and SLIP drivers must have some sort of agreement regarding when the low-level structures can be accessed, or the state of the system will take a turn for the worse.

The resource provider (the low-level driver) starts by creating a revocable_provider structure for the structure that it will make available to higher levels:

    struct revocable_provider *stuff = revocable_provider_alloc(void *res);

The res parameter should point to the resource that is being managed. The user of this resource (the higher-level driver), instead, should obtain a revocable structure with call to:

    struct revocable *revocable_alloc(struct revocable_provider *prov);

In the example code provided with the patch set, the resource consumer has direct access to the revocable_provider structure and makes the call itself. One could also imagine an API where the provider hides that structure and allocates the revocable structure for the user.

Sooner or later, the user will need access to the underlying resource. It does not have a pointer to that resource, though; it only has the revocable structure. To get that pointer, it must make a call to:

    void *revocable_try_access(struct revocable *resource);

In normal operation, where the device still exists, this call will do two things: it enters a sleepable RCU (SRCU) read-side critical section, and it returns the pointer that the caller needs. The caller can then use that pointer to access the resource, secure in the knowledge that the pointer is valid and will continue to be for the duration of the access. Once the task at hand is complete, the user calls:

    void revocable_release(struct revocable *resource);

That exits the SRCU critical section, and releases the reference to the resource; the caller should not retain the pointer to that resource past this call. There is also a convenience macro provided for this pair of calls:

    REVOCABLE(struct revocable *resource, void *ptr) {
    	/*
	 * "ptr" will point to the resource here.  It may be NULL,
	 * though, so code must always check.
	 */
    }

As the comment above suggests, a call to revocable_try_access() might return NULL, which is an indication that access to the resource has been revoked. Needless to say, the caller must always check for that case and respond accordingly.

Revocation happens on the provider side, for example, if the owner of the vape unplugs it to put it to its more traditional use. Once the low-level driver becomes aware that the device has evaporated, it revokes access to the resource it exported with:

    void revocable_provider_free(struct revocable_provider *stuff);

This call may, of course, race with the activity of users that have legitimately obtained a pointer to the resource with revocable_try_access(); as long as those users are out there, the provider cannot free that resource. To handle this situation, revocable_provider_free() makes a call to synchronize_srcu(), which will block for as long as any read-side SRCU critical sections for that resource remain active. Once revocable_provider_free() returns, the provider knows that no users of the resource remain, so it can be safely freed.

This whole interface may seem familiar to those who have been watching the effort to enable kernel development in Rust: it is patterned after the Revocable type used there.

The initial response to this work has been mostly positive; Greg Kroah-Hartman, for example, said:

This is, frankly, wonderful work. Thanks so much for doing this, it's what many of us have been wanting to see for a very long time but none of us got around to actually doing it.

Danilo Krummrich, who wrote the Rust Revocable implementation, had some more specific comments, suggesting changes to the naming and the API. Shih, meanwhile, has requested a discussion of the work at the Kernel Summit, which will be held this December in Tokyo. This is relatively new work, and it seems likely to evolve considerably before it is ready for use in the mainline kernel. Given the potential it has to address the sorts of life-cycle bugs that have plagued kernel developers for years, though, it seems almost certain that some future form of this API will be adopted.

Index entries for this article
KernelDevice drivers/Support APIs
KernelRace conditions


to post comments

I thought that had a familiar ring to it

Posted Sep 22, 2025 20:57 UTC (Mon) by fman (subscriber, #121579) [Link]

Reminds me of the Macintosh toolbox Handle's..
HLock(h) & HUnlock(h) anyone ..

Cancellations in debugfs

Posted Sep 23, 2025 7:23 UTC (Tue) by benzea (subscriber, #96937) [Link]

That reminds me of the debugfs file removal race conditions we had in cfg80211. To solve those, we added the debugfs_enter_cancellation()/debugfs_leave_cancellation() API. The idea there is to have a callback that permits notifying the user asynchronously that the resource (the debugfs file in this case) is being removed and any outstanding file operation need to be cancelled ASAP.

In the cfg80211 case, the user file operations are handled by queuing a work item that will then hold the wiphy mutex. However, if the debugfs file is removed before it runs, then the work item is simply cancelled so that the file removal can go ahead immediately.

It does not look to me like this API can easily be used for this debugfs use in cfg80211. That is probably fine, but it could still be an interesting usage scenario to look at.

Revocable references vs. krefs

Posted Sep 25, 2025 18:18 UTC (Thu) by Alan.Stern (subscriber, #12437) [Link] (7 responses)

In what way are revocable references better than the existing practice of reference-counting using krefs?

Revocable references vs. krefs

Posted Sep 26, 2025 11:13 UTC (Fri) by farnz (subscriber, #17727) [Link] (6 responses)

The difference to krefs is in intention. With krefs, you've got a data structure, and you are expected to keep it alive for as long as you want it, releasing it when you're finally finished with it. Revocable references work slightly differently; you're being given the right to claim a reference to a data structure for a short period of time (one RCU critical section), as long as you release it quickly. Outside the RCU critical section, the data structure can be destroyed, and you will not be able to claim it again.

So the advantage over krefs is simply that it's clear with a revocable reference that you have to check that the referent still exists every time you enter a critical section that needs access; you can, of course, implement this in an ad-hoc way for every piece of hardware that benefits from this, but it's clearer for reviewers if you implement it once and have a common pattern to reuse.

Revocable references vs. krefs

Posted Sep 26, 2025 14:54 UTC (Fri) by Alan.Stern (subscriber, #12437) [Link] (5 responses)

This sounds more like a disadvantage. With krefs you _don't_ have to check every time you want to access the data structure, which certainly is easier. Instead, the owner of the data structure has to tell you (usually by making some sort of unregistration function call, which has to be made anyway) when the hardware is gone.

From your description, it seems like the other main difference is that with revocable references the data structure can usually be deallocated quite soon after the hardware goes away (delayed only by the length of an SRCU read-side critical section), whereas with krefs the data structure has to hang around for as long as anybody keeps a reference to it. This doesn't sound like a big deal, since the amount of memory used by these data structures tends not to be very large.

Overall my impression is that revocable references are somewhat harder to use than krefs with no compensating advantages.

Revocable references vs. krefs

Posted Sep 27, 2025 7:53 UTC (Sat) by farnz (subscriber, #17727) [Link] (4 responses)

I can't see anything in krefs that requires someone who's done a kref_get to register for notification that the underlying hardware is actually gone, nor is there anything that bounds the time between the hardware going away and the kref_put. With revocable references, that's implicit in the fact that, once the reference is revoked, you can't get access ever again.

That, in turn, makes a huge difference at code review time; you literally cannot forget to register with the device driver for notification that the hardware's gone away, because it's implicit in the design of revocable references.

Revocable references vs. krefs

Posted Sep 27, 2025 15:47 UTC (Sat) by Alan.Stern (subscriber, #12437) [Link] (3 responses)

You are correct that nothing bounds the time interval between device removal and the final kref_put(). As I mentioned above, this should not be considered a problem because the data structures being pinned by the kref generally don't occupy a lot of memory. (Although I admit there is always the possibility of missing puts causing a memory leak.)

Nevertheless, what you say leaves a strong impression that revocable references are best suited for scenarios other than registration of hot-removable devices. With removable devices, the subsystem to which the device is registered will keep its reference until the device is unregistered, and it will expect to be able to use the device whenever it wants, up until that time. It's not that the subsystem registers with the device driver for notifications; it's the other way around: The driver registers and unregisters the device with the subsystem. This does not pose any problems for code review, because reviewers always expect to see drivers both registering and unregistering their devices -- that is the accepted pattern and a reviewer would definitely notice if it weren't being followed.

All right, given that revocable references aren't well suited for registering removable devices with subsystems, then what are the intended use cases for revocable references?

Revocable references vs. krefs

Posted Sep 29, 2025 9:10 UTC (Mon) by farnz (subscriber, #17727) [Link] (2 responses)

The use case is where you have something claiming a removable device, like a PPP driver (for example); in other words, where the subsystem registers with the removable device, rather than the normal way round.

You want the subsystem to stop doing work that depends on the removable device being present as quickly as possible - there is no point spending lots of compute on building a new PPP frame to send, or a new work item to pass across to the GPU, if the underlying hardware is gone. Instead, you want it to error out early and stop wasting time doing things that it's going to submit to a driver that's already deregistered from the subsystem.

Revocable references vs. krefs

Posted Sep 29, 2025 13:55 UTC (Mon) by Alan.Stern (subscriber, #12437) [Link] (1 responses)

Okay, that makes sense. Jon's original article did not draw any distinction between a subsystem claiming a removable device and the device's driver registering with a subsystem.

Revocable references vs. krefs

Posted Sep 30, 2025 0:50 UTC (Tue) by riking (subscriber, #95706) [Link]

Of course, for the subsystems that the removable device's driver does register with, it *cannot* process those unregistrations until it is satisfied that its consumers have stopped trying to use the device (now, via struct revokable). Once the SRCU critical section has passed, the subsystem deregistrations can happen.


Copyright © 2025, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds