freenode
Kernel & Low-Level

BPF gains unwind cleanup pads for Rust-style resource release

Yonghong Song's bpf-next series runs landing-pad cleanups on bpf_unwind so frames that own locks or kptrs can tear down instead of being discarded.

Yonghong Song has posted a 26-patch series for bpf-next that teaches the BPF verifier and runtime to run exception cleanup landing pads when bpf_unwind() walks the stack. The work closes a long-standing gap: frames that own resources could not throw, which has blocked Rust programs from using a real panic path inside BPF.

Today bpf_throw() jumps straight to an exception boundary and discards every frame in between. Anything those frames held (an RCU read lock, a preemption-disabled section, a referenced kernel pointer) is never released, so the verifier refuses to let such a frame throw at all. Rust Drop glue is exactly that release step, and there has been nowhere to run it.

LLVM 23 already emits the compiler side. A function that owns a value across a call that may unwind lowers to an invoke with a cleanup landing pad, and the BPF backend writes a flat table of (begin, end, landing_pad) records into a .bpf_cleanup section. Song's series is the kernel half: accept that table at program load, teach the verifier to follow an unwind to the pad each frame would reach, and have a new bpf_unwind() kfunc rewrite saved return addresses so each frame resumes at its pad or epilogue instead of vanishing. Pads finish with bpf_unwind_resume, the kernel stand-in for the unwind ABI's _Unwind_Resume.

bpf_throw() keeps its old meaning (leave for the boundary with intermediate frames discarded). bpf_unwind() is the path that actually runs cleanups. x86-64 and arm64 JITs gain the stack walk and native cleanup range support the dispatch needs. libbpf parses the cleanup section, passes it through BPF_PROG_LOAD (including light skeletons), and rewrites the _Unwind_Resume name to the kfunc. Attachments that would plant a trampoline between program frames on an unwind path are refused so the walk cannot skip a pad the verifier assumed would run.

C has no language-level unwinding, so selftests spell the same shapes by hand that a frontend would emit, including multi-frame chains that hold RCU locks or disable preemption. The practical payoff is for Rust and similar languages: without cleanup on unwind, a BPF program still cannot treat throw as panic without leaking kernel state.