|
|
Log in / Subscribe / Register

Suspending and resuming BPF programs

By Daroc Alden
June 19, 2026

LSFMM+BPF

BPF programs can be used to extend many aspects the Linux kernel, but BPF programs must run to completion in the same context that they began. Kumar Kartikeya Dwivedi is working on changing that by allowing BPF programs to be expressed as coroutines. He spoke about his work at the 2026 Linux Storage, Filesystem, Memory-Management and BPF Summit. While still experimental, the change promises to make long-running BPF tasks significantly easier to write.

Frequently, a single logical task is spread across both space and time, he explained. Execution jumps between different locations, computations can be suspended, and so on. Being able to express this in BPF would make some kinds of extensions to kernel functionality much easier to write. For example, consider the task of collecting stack traces. The kernel has tracing facilities to gather combined stack traces for kernel and user-space code. It's more efficient to collect the user-space portion of the stack trace right before the kernel performs a context switch back to user space. When a stack trace is requested, the kernel part runs immediately, then the computation is suspended, and the user-space part of the collection runs later. Adding BPF into this workflow requires splitting a single logical operation across multiple independent functions, because there is currently no way to suspend an executing BPF program.

$ sudo subscribe today

Subscribe today and elevate your LWN privileges. You’ll have access to all of LWN’s high-quality articles as soon as they’re published, and help support LWN in the process. Act now and you can start with a free trial subscription.

Similar problems come up when implementing user-space networking. Attempting to send a packet with sendmsg() eventually makes a call to qdisc_run(), which can also do work for other threads, sending something that they queued. This is another case of follow-up work being done at a different time and place than the main task. People have experimented with writing whole applications in BPF, Dwivedi said, and that turns up even more of the same kind of problem.

[Kumar Kartikeya Dwivedi]

Historically, BPF programs have used hooks and callbacks. There's nothing wrong with that approach; it can express all of the semantics that kernel developers need. It does force programmers to write custom suspend/resume logic and break their program up over multiple functions, however. Dwivedi's solution is to introduce coroutines to BPF: functions that can be suspended, transfer control back to the kernel, and then be resumed later. He then went over his design for BPF coroutines in the kernel.

The best solution would be stackless coroutines, akin to Rust's asynchronous functions or C++'s coroutines, he said. In these languages, the compiler is responsible for rewriting straight-line code into a form that can be suspended and resumed. The advantage is that, from the BPF verifier's point of view, not much would need to change. Resuming a coroutine would look like a normal indirect function call, and the compiler would handle the details of saving and loading intermediate values. The verifier would still need to ensure that the overall program's control flow is valid, and may need some changes to handle code that is split across kernel contexts.

Dwivedi went into a bit of detail about how C++ implements coroutines to illustrate the interface that the verifier would see. In short, he said, C++ creates a structure that contains two function pointers that can be used to resume() or destroy() the coroutine. resume() uses an index recording where the coroutine last suspended, stored in that same structure, to choose which code to execute using a switch statement. Any variables that need to be saved across suspension points are stored in the same structure; any that don't need to be saved across suspension points remain on the stack, and are implicitly discarded when the task is suspended. The verifier's normal check that kernel resources are freed instead of just forgotten about prevents BPF programs from doing that with any locks or reference-counted structures. The two function pointers are always the first two elements of the coroutine structure, so that generic code doesn't need to know how big the coroutine's frame is or how it is laid out.

This is pretty similar to how Rust compiles asynchronous functions, except that it uses its type system to convey the resume() and destroy() callbacks. The Rust compiler can be easily coerced to produce a structure with the same layout as C++, however, so the verifier will probably only need to handle that one layout. From the point of view of tracking the types of BPF values, the coroutine's associated structure can be handled in the same way as the stack: values are spilled to it and loaded from it, but it doesn't need special handling.

The things the verifier will have to do to make sure a coroutine is safe, beyond verifying the constraints that apply to all BPF code, include making sure the resume() and destroy() function pointers are not overwritten, making sure that the index only takes on valid values, and making sure that it's always legal to call destroy() when the coroutine is suspended. The verifier will also need to check that locks aren't held across suspension points, but that check can reuse the verifier's logic for determining that locks are released before a function returns. By the same logic, the verifier may need to invalidate map values held across suspension points, depending on their types.

Andrii Nakryiko asked how the verifier is supposed to ensure that the coroutine doesn't enter an infinite loop, perhaps by setting the index back to an earlier value. The body of the coroutine is still verified, and at every suspension point the verifier checks the call to resume(), so the verifier can identify any loops in the same way that it finds existing loops, Dwivedi said. Nakryiko asked about subtler loops, but Dwivedi pointed out that it was already possible to have two BPF programs arm each other's timers, and that such infinite loops don't cause an actual problem as long as they don't cause the kernel to become unavailable. For that matter, even with the verifier limiting BPF programs to less than one million verified instructions, there's nothing preventing a user from attaching a 999,999 instruction BPF program that makes lots of expensive kfunc calls to every available kernel hook and slowing the system to a crawl. The point of the limit is to prevent infinite loops that might cause deadlocks, so that the system can continue to make forward progress, not to prevent BPF programs from wasting CPU time.

As long as the verifier checks that, from every suspended state, it is always valid to destroy() the BPF program (in case it is unloaded) and resume() it, the program can't subvert BPF's safety guarantees. Even if there is a clever way to set up an infinite loop, it still won't be able to deadlock the kernel, since every time the coroutine is suspended it will have to release any held locks and give control back to the kernel.

Dwivedi has a prototype implementation in progress, but there is still more work to be done. He wants to extend the BTF debugging information for programs that use coroutines, for example. His prototype also had to enable aggregate return types from functions to make his test C++ programs work correctly, so that will need verifier support. Adding Rust support should not be too much more difficult.

A more experimental idea for the future is to allow suspended computations to switch between user space and kernel space. Dwivedi had one of his students work on a prototype for that, but it is definitely not ready yet. If that ever does work, it will enable applications to perform setup in user space, then transition to the kernel and use native BPF capabilities, before potentially switching back to user space. That kind of interface would blur the distinction between user space and the kernel — but it will be a long time before it becomes a reality, if it ever does.

In the nearer term, Dwivedi intends to polish up his current work to prepare it for the kernel. While incorporating coroutines into BPF will not technically enable anything new, his hope is that it will make it easier to integrate BPF programs into the parts of the kernel that can't easily be simplified down to a single hook or callback.


Index entries for this article
KernelBPF
ConferenceStorage, Filesystem, Memory-Management and BPF Summit/2026


to post comments

Give up on proving termination

Posted Jun 19, 2026 23:34 UTC (Fri) by roc (subscriber, #30627) [Link] (9 responses)

> For that matter, even with the verifier limiting BPF programs to less than one million verified instructions, there's nothing preventing a user from attaching a 999,999 instruction BPF program that makes lots of expensive kfunc calls to every available kernel hook and slowing the system to a crawl. The point of the limit is to prevent infinite loops that might cause deadlocks, so that the system can continue to make forward progress, not to prevent BPF programs from wasting CPU time.

This makes no sense to me. What is the value of "guaranteed forward progress" if in practice that progress can be made arbitrarily slow? You can't tell someone whose system has locked up that it's not a bug because their system will unfreeze in an hour.

It seems necessary and sufficient to have some kind of counter/timer that can reliably kill BPF programs that run too long, in a safe way that leaves the kernel in a good state. Once you have that, just give up on trying to verify termination.

Give up on proving termination

Posted Jun 19, 2026 23:49 UTC (Fri) by Cyberax (✭ supporter ✭, #52523) [Link] (2 responses)

This means admitting that the verifier has been useless all along, and all the gratuitous complexity could have been avoided.

Give up on proving termination

Posted Jun 21, 2026 10:33 UTC (Sun) by summentier (subscriber, #100638) [Link] (1 responses)

Is that so? I thought the main point of the verifier was to check for illegal memory access, which, since BPF programs are AFAIK not sandboxed, could lead to crashes, corruption, and other calamaties.

The instruction limit stops the verifier from having to solve the halting problem. The guaranteed runtime limit seems to be a secondary benefit.

Give up on proving termination

Posted Jun 21, 2026 16:57 UTC (Sun) by Cyberax (✭ supporter ✭, #52523) [Link]

BPF is runtime-bounds-checked in practice. Eliding a few checks here and there is not significant.

> The instruction limit stops the verifier from having to solve the halting problem. The guaranteed runtime limit seems to be a secondary benefit.

BPF now has unlimited loops with the help of iterators. Even without them, it has access to string instructions, so you can easily make programs that take seconds to run. That's why BPF had to add asynchronous termination support.

As keep I saying, they should have just taken WASM instead of reinventing the wheel. Badly.

Give up on proving termination

Posted Jun 20, 2026 7:22 UTC (Sat) by kleptog (subscriber, #1183) [Link] (4 responses)

> What is the value of "guaranteed forward progress" if in practice that progress can be made arbitrarily slow?

You're not defending against a malicious actor here, it's (IMHO) to prevent people writing stupid BPF programs by accident. People don't write "make my system slow but not freeze" scripts by accident.

What you are basically suggesting is adding support for exceptions which would allow a BPF program to be aborted at any point. And every caller has to decide what to do with an OutOfInstructions exception. Given the opinion of exceptions in the rest of the kernel and the complexity isn't adds to e.g. C++, I can understand why they don't want to go that route.

Give up on proving termination

Posted Jun 20, 2026 11:24 UTC (Sat) by roc (subscriber, #30627) [Link]

> You're not defending against a malicious actor here, it's (IMHO) to prevent people writing stupid BPF programs by accident.

Then termination detection doesn't need to be in the kernel verifier, it can just be a lint that the compiler applies to warn you if your program might run too long.

> What you are basically suggesting is adding support for exceptions which would allow a BPF program to be aborted at any point. And every caller has to decide what to do with an OutOfInstructions exception. Given the opinion of exceptions in the rest of the kernel and the complexity isn't adds to e.g. C++, I can understand why they don't want to go that route.

One problem with that argument is that BPF programs already support aborts/exceptions: https://lpc.events/event/17/contributions/1578/attachment...
So the additional work that would be required would just be to define, for each context in which a BPF program can be called, where the BPF function's return value is not void, a default return value.

Give up on proving termination

Posted Jun 20, 2026 12:21 UTC (Sat) by jengelh (subscriber, #33263) [Link] (1 responses)

>People don't write "make my system slow but not freeze" scripts by accident.

If only. An unsuspecting writer might add a for loop in one function ("should be fine") and silently raising existing O(n^m) behavior into O(n^(m+1)). Works for his machine, and grinds on another with a sufficiently large dataset n.

Give up on proving termination

Posted Jun 21, 2026 10:28 UTC (Sun) by farnz (subscriber, #17727) [Link]

The variation I've seen on this is to write a loop with O(n^m) behaviour, where m is the number of CPU cores. Works fine on the small test EC2 instance with 1 or 2 vCPUs; blows up on production instances with 16 or 32 vCPUs.

Give up on proving termination

Posted Jun 22, 2026 8:29 UTC (Mon) by taladar (subscriber, #68407) [Link]

> People don't write "make my system slow but not freeze" scripts by accident.

Probably a lot more people than people who write "make the script not terminate" bugs by accident (where non-termination is defined as actually taking forever, not just taking a few years since that would technically fall into the slow category).

Give up on proving termination

Posted Jul 12, 2026 18:42 UTC (Sun) by yanjun.zhu (guest, #173290) [Link]

I am wondering if we can make eBPF program sleep. The eBPF program sleep when it is not needed, When it is needed, this eBPF program is waken up. Is it a solution to this resume problem?


Copyright © 2026, 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