freenode
Kernel & Low-Level

BPF exception cleanup pads clear path for Rust panics

Kernel support for LLVM 23 unwind tables lets bpf_throw() run Drop glue before discarding frames that hold locks or referenced pointers.

Yonghong Song has posted a 20-patch series for bpf-next that teaches the BPF verifier and JITs to run exception cleanup landing pads when bpf_throw() unwinds the call stack. The work removes the main reason Rust BPF programs still cannot treat bpf_throw() as a panic path.

Today bpf_throw() walks to the exception boundary and discards every frame in between. A frame that owns an RCU read lock, a preemption-disabled section, or a referenced kernel pointer never gets a chance to release it, so the verifier simply forbids throwing from such frames. Rust Drop glue is exactly that release work, and until now there was nowhere to run it.

LLVM 23 already emits the compiler half. A function that owns a value across a call marked unwindable lowers to an invoke with a cleanup landing pad; the BPF backend records each region as a flat triple of byte offsets (begin, end, landing pad) in a .bpf_cleanup section. The contract is simple: if a frame unwinds with its return address inside the range, run the pad before discarding the frame. Pads finish with a call to bpf_unwind_resume(), supplied by the kernel as a kfunc.

Song's series is the kernel half. At program load the table is ingested, the verifier is taught that a covered call may also transfer to its landing pad, and bpf_throw() executes the pads as it walks. C has no native unwinding, so the selftests spell the same structure by hand with labels and explicit cleanup records. Programs that previously failed with "bpf_throw cannot be used inside bpf_rcu_read_lock-ed region" can now load once the unlock sits in the landing pad.

The change is limited to programs that carry a cleanup table; ordinary C BPF code is unaffected. Architecture support lands first for x86-64 and arm64, with the JITs arranging a stable frame for the pad so it can address the discarded frame's stack correctly. Once merged, Rust BPF code that takes guards across fallible calls can finally panic without leaving the kernel holding locks or live references.