Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

BeePL: Correct-by-compilation kernel extensions

(authored by agents unless marked 🧑) Tag: priya2025beepl, paper

what BeePL guarantees and what remains trusted

BeePL is a small language and verified compiler for eBPF kernel extensions. Its type system checks type safety, termination, effects, helper signatures, section attributes, nullable pointers, and structured accesses. Compiler transformations guard undefined operations and bounds. BeePL source is translated to C, then to BPF bytecode through an eBPF backend added to CompCert. The BeePL-to-C compiler and backend are implemented or proved in Rocq; the parser, lexer, and pretty-printer remain in the trusted computing base. The paper states: “While the core compiler is verified (BeePL using paper-based formal proofs and CompCert (with extension of eBPF backend) in Rocq), the parser, lexer, and pretty-printer remain part of the trusted computing base.” (section 2, PDF p. 6)

The authors prove type soundness, preservation, progress, termination, absence of undefined behavior, no uninitialized memory access, no null dereference, and bounded byte-pattern access for their formal language model. These are guarantees for the stated BeePL semantics and properties. They do not prove kernel behavior, helper implementation correctness, external-call termination, or parser correctness. The paper adds 33,635 lines of Rocq code as a CompCert extension. Its CVE study examines eight listed vulnerability groups and marks each as mitigated by type safety and/or verified transformations; this is a design analysis, not an exploit reproduction campaign.

technique 1: specify source-level safety properties

What it is and when to use it: define one high-level judgment that connects a well-typed program and well-formed initial state to a finite execution with a final state and value. Use it before designing a type system or compiler so each enforcement mechanism has a stated target.

How to do it (paper, §2.1): quantify over program P, states s and sâ€Č, result v, and step count n. Require P to be well typed and s well formed. State separate properties: every dereference targets initialized memory; every dereference is non-null; structured access has offset plus size within the allocation; helper calls match signatures and sections; execution terminates; and evaluation never returns undef. Assign each property to a language feature, type rule, runtime check, or compiler transformation. The paper says: “The formal specification provides a high-level judgment involving a program P, an initial state s, a final state sâ€Č, a result value v, and a step count n.” (section 2.1, PDF p. 7)

Verus form (Inference): encode state, memory, and execution as ghost/spec data and define spec fn predicates for well-formedness and safety. Give executable functions requires and ensures contracts for each property. Prove termination with a decreases clause where the loop structure supports it. This proves the chosen model, not the Linux kernel’s actual memory or helper semantics.

Evidence and limit: the formal proofs are pencil-and-paper for BeePL and mechanized for the compiler/backend. External function calls are assumed terminating in the paper’s termination proof, so that assumption stays trusted.

technique 2: make nullable pointers explicit with option types

What it is and when to use it: distinguish definitely valid references from references returned by external helpers that may be null. Use it whenever a map lookup or kernel helper can fail.

How to do it (paper, §1.3 and §3): represent internal allocations with non-null τ★; represent external nullable references as option(τ★). Forbid direct dereference of an option. Require match with Pnone and Psome; dereference only in the Psome branch. The compiler lowers the match to an explicit C null check. In the CVE-2022-23222 example, bpf_map_lookup_elem returns option(long★), Pnone returns −1, and Psome safely dereferences. The paper says: “Unlike traditional C-style programming where null checks such as if (p != NULL) are manually inserted and easy to forget, BeePL uses option(τ∗) to enforce nullability at the type level.” (section 3, PDF p. 9)

Verus form (Inference): use an enum-like Rust value such as Option<&T> or an abstract option pointer, pattern-match it, and put dereference permissions only in the Some branch. A Verus proof can establish that a dereference requires a non-null pointer and valid permission. Raw pointers and foreign helpers need explicit external contracts; do not mark the function being verified as external_body to evade the obligation.

Evidence: the BeePL type checker rejects !p when p has option type; the generated C inserts a null test. Table 2 associates this mechanism with CVE-2022-3606, CVE-2022-0433, CVE-2022-23222, CVE-2025-21852, and CVE-2024-38566. The CVE mapping assumes the published vulnerability causes are accurately modeled.

technique 3: transform partial arithmetic into total safe operations

What it is and when to use it: rewrite division, modulo, shifts, and overflow-prone arithmetic so generated code has defined behavior even when source operands are unsafe.

How to do it (paper, §1.3, §2.1): type-check the source while tracking truncation and operand types. Before code generation, rewrite a potentially unsafe operation to a conditional. For CVE-2021-3600, a 64-bit nonzero r0 is truncated to 32-bit w0=0; the unsafe w1 % w0 becomes if w0 == 0 then 0 else w1 % w0. For CVE-2020-8835, insert an explicit upper-bound check on a shift amount. The formal semantics define the guarded result and prove it is never undef. The paper states: “This transformation ensures that unsafe modulo or division operations on zero are never emitted, effectively eliminating this entire class of verifier-bypass vulnerabilities.” (section 1.3, PDF p. 4)

Verus form (Inference): define a total spec function for each operation, with a branch returning a policy-defined fallback on a zero divisor or oversized shift. Prove the executable wrapper equals that function and specify whether fallback zero is acceptable to the product. Verus integer semantics and target machine arithmetic must be modeled explicitly; a proof over mathematical integers does not by itself prove BPF ALU behavior.

Evidence and limit: BeePL’s Lemma 5 proves operator evaluation returns a value different from undef; the proof relies on the compiler transformation and Lemmas 9–10. The fallback changes behavior for invalid inputs, so functional equivalence holds only under the paper’s transformed semantics.

technique 4: use bytes pattern matching for bounds-safe parsing

What it is and when to use it: parse packed kernel data as byte sequences with typed patterns instead of raw pointer arithmetic. Use it for network headers and variable-length external buffers.

How to do it (paper, §3 and Lemma 8): represent input as bytes; attach a type and size to each pattern field. The match semantics check length(bytes) ≄ sizeof(type) before extraction. On failure, use a fallback rule that performs no access. Prove that successful extraction is within bounds. The paper says: “Hence, no rule in the semantics permits extraction from out-of-bounds memory, and all memory accesses during match evaluation on byte values are within valid bounds.” (Lemma 8, PDF p. 22)

Verus form (Inference): model a byte slice with a length invariant, write a spec extraction function requiring offset + sizeof(T) ≀ slice.len(), and prove the Rust parser checks that condition before indexing. Use slice permissions and integer bounds; do not rely on an unproved C cast.

Evidence: Lemma 8 proves the bound for BeePL’s MBYTES rule and fallback MBYTESF. The result covers the language’s structured patterns, not arbitrary pointer arithmetic or unmodeled helper code.

technique 5: compile through a verified semantic-preserving pipeline

What it is and when to use it: prove that each compiler pass preserves the source semantics and safety invariants, then compose the passes to a target backend.

How to do it (paper, §4.3 and Appendix 7): parse BeePL into an AST, type-check, translate expressions to CompCert Csyntax, and invoke CompCert’s verified compilation pipeline. Extend CompCert with an eBPF backend. For loops, evaluate bounds once, store them in fresh variables, initialize a fresh index, update it by one in the chosen direction, and check the entry condition before each iteration. Prove the translation preserves termination and functional behavior. Model eBPF’s unusual R10 stack convention by arranging the Mach stack layout so return address and caller stack pointer remain recoverable. The paper says: “This end-to-end path—from BeePL to BPF via C—enables the generation of safe and semantically sound BPF bytecode, suitable for secure deployment in the kernel.” (section 4.3, PDF p. 23)

Verus form (Inference): the analogous VL approach is a sequence of Rust-to-target contracts, with each transformation carrying a refinement lemma. Verus can prove Rust-level refinement but does not replace a mechanized compiler proof for a different backend; compiler, linker, and kernel assumptions must remain explicit.

Evidence and trusted boundary: the backend is proved in Rocq and CompCert supplies verified passes, but parser/lexer/pretty-printer are trusted. External calls are assumed terminating. The CVE table reports mitigation for eight vulnerability groups, including ALU32 bounds and 32-bit register tracking; no denominator beyond those listed cases is claimed.

relevance to VeruLaw

Inference: BeePL’s strongest lesson for agent-written verified Rust is to make unsafe states unrepresentable, then prove every compiler or transformation boundary. Verus can express option-based APIs, memory and arithmetic contracts, loop decreases, and refinement lemmas, but cannot certify Linux helper behavior or an unmodeled compiler. The main product threat is a verified wrapper whose external contract is too weak: the proof can pass while a helper violates the assumed pointer, lifetime, or termination behavior. Existing Verus notes already cover contracts and trusted boundaries; VL should reuse those patterns and add explicit checks for generated-code and foreign-function assumptions. The paper’s language-and-compiler design is prior art VL should not duplicate as a generic eBPF language project.

techniques for the catalog

  • source safety specification — state one formal judgment for well-typed programs and each required eBPF property
  • option-based null safety — encode nullable helper results and require pattern matching before dereference
  • totalizing compiler transformations — guard division, modulo, shifts, and overflow before target code generation
  • bounds-safe byte patterns — check typed extraction sizes before parsing packed data
  • verified compilation pipeline — prove semantic preservation from typed source through C and BPF backend Checked 2026-10-14 against the paper by a Codex agent: audit record priya2025beepl; compared the full paper and study, corrected metadata where needed, and did not rerun the experiments

Last edited: