Exceptions - Resource Cleanup - Redux
Subsystems (27)
Kumar Kartikeya Dwivedi
maintainercover Exceptions - Resource Cleanup - Redux
Currently bpf_throw() can only be called while the program holds no resources. A throw unwinds to the exception callback of the main program and discards every frame in between. Any reference, lock, saved IRQ flags, or RCU or preemption disabled section held by those frames would leak, so the verifier rejects the throw. This makes assertions unusable in most places where a program wants one: in a loop over an iterator, under a lock, or in an RCU section.
This series allows the main program and its subprogs, static and global, to throw while holding resources. The unwinder releases the resources of every frame it discards.
To release a resource the unwinder needs its handle: a pointer to an object, a ringbuf record or a lock, or the address of an iterator or of saved IRQ flags on the stack. The verifier tells the unwinder where to find each handle, in one of two ways.
1. In the location where the program already keeps it. A frame can only be unwound at a call it has not returned from: bpf_throw(), or a call to a subprog that may throw. Such a call is a site. On every visit to a site the verifier records where the frame keeps each handle: the stack slot of an iterator or of saved IRQ flags, the stack slot or register that holds a pointer to a referenced object, and the address of a lock or a pointer to the object that contains it. It also records how many RCU and preemption disabled sections the frame is in. When every visit finds a resource held, with its handle in the same location, that location goes into a descriptor for the site. The program pays nothing for this at runtime.
2. In a hidden stack slot. When the visits to a site do not agree, the verifier reserves a hidden stack slot in the frame for the instruction that acquires the resource. Instructions inserted around the acquiring call store the handle in the slot, and instructions inserted before the releasing call clear it. Sections are counted the same way, in one hidden slot per kind.
A resource that is never held at a site needs neither. That is the case for every resource that is released before the next call that may throw.
A callee may release a resource that its caller holds. If it is a reference passed in an argument register, the call transfers it: the callee owns the reference from its entry and tracks it in either of the two ways above. Any other resource, such as a lock, stays with the caller. The caller's sites then describe it, or the caller clears its hidden slot before the call. A callx is handled the same way, for every subprog it may call.
At runtime the unwinder walks the frames once per phase: locks and IRQ flags first, then objects whose destructor needs RCU protection, then RCU and preemption sections, then all remaining objects. This way no destructor runs under a lock or with IRQs disabled, a destructor that may sleep runs after every section has been left, and an RCU protected iterator is destroyed before its RCU section ends. For each frame the unwinder releases the resources in the frame's hidden slots and those listed in the descriptor of the frame's site. It finds the site from the frame's return address, in a table that the JIT fills.
To read handles from registers, bpf_throw() gets a native entry point that captures the registers of the throw site before any C code runs. R6-R9 are callee-saved, so the unwinder recovers them for each outer frame from the locations where the inner frames saved them in their prologues, which the JIT records. R0-R5 are known only for the frame that calls bpf_throw(). That is still useful: bpf_throw() does not return, so a compiler leaves a reference in whichever register it was in.
The unwinder restores the callee-saved registers of the program's caller from the same saved locations. The main program of a program that throws therefore no longer saves all of them in its prologue for the exception callback to restore, and uses a normal prologue.
Programs that cannot throw are not affected.
Limits on program patterns:
- A throw inside a callback stays rejected. This will be lifted in a follow up version or set, along with support for normal unwinding.
- A hidden slot belongs to the frame that acquired the resource. Some resources end up held by another frame: a lock that a subprog takes and returns with, an iterator or saved IRQ flags that a callee creates on its caller's stack, or an RCU or preemption section that a callee enters or leaves for its caller. These are accepted when the site descriptors describe the resource, or when no throw can happen while it is held. They are rejected when a hidden slot would be needed. This does not need to be fixed: the usual wrappers load, and only a site that is reached both with and without such a resource is rejected.
- A hidden slot holds one handle. When a frame holds two resources acquired by the same instruction, for example in a loop, the program is accepted only if no throw can happen while both are held. This can be lifted in a follow up for resources that a site can describe.
- The instructions that maintain a hidden slot are emitted once per call. A resource that needs a hidden slot is rejected if its call acquires it only on some paths or in different ways on different paths, if its handle is not in a register at the acquiring or at a releasing call, or if the acquiring call may itself unwind. In well-behaved programs using RAII, this pattern is unlikely to appear. The last case is expected to go away with support for normal unwinding. The others do not need to be fixed.
- A callee cannot access a hidden slot of its caller. When the caller keeps a resource in a hidden slot, the program is rejected if:
- a callee that was not handed the resource may throw before it releases it, or releases it only on some paths;
- a callee that was handed the resource returns without releasing it on some paths;
- a callx may call one subprog that releases the resource and another that does not;
- a callback releases the resource.
Likewise, a subprog that keeps a reference it was handed in a hidden slot of its own is rejected if another call passes it something else in that argument.
Only the second case is expected in practice: a callee that consumes a reference on some paths and leaves it to its caller on others. It loads today if the callee returns the pointer instead, and can be lifted in a follow up. The others do not need to be fixed.
- A throw is rejected while the program holds a reference that the unwinder cannot release: its type has no cleanup destructor, the program is not allowed to call its release kfunc, or the object has NMI unsafe fields and the program is a tracing program. The first case is fixed by registering a destructor for the type. In the other two the program could not release the reference itself either, so they do not need to be fixed.
- A program that throws must not overwrite an iterator or saved IRQ flags on its stack, release an object while it holds the lock inside that object, or keep an RCU protected iterator beyond its RCU section. Such programs are rejected without a throw as well, so this does not need to be fixed.
Other limits:
- A register or stack slot that points to a held resource stays in the verifier state after the program has read it for the last time, so that a site can describe it. Two states are equivalent only if these locations match, which can reduce pruning. Liveness is unchanged. This does not need to be fixed: it only matters when two states keep a held resource in different locations.
- Hidden slots count towards the stack depth limit. This does not need to be fixed: a slot is eight bytes, and only a resource that no site can describe takes one.
- Only x86-64 and arm64 are supported. Other architectures keep the existing behavior, and can be added in follow ups.
Kumar Kartikeya Dwivedi (29):
bpf: Record where JITs save callee-saved registers
bpf: Add bpf_throw_unwind() and an arch entry hook bpf, x86: Capture registers in a native bpf_throw() entry bpf, arm64: Capture registers in a native bpf_throw() entry
bpf: Restore callee-saved registers from unwound frames
bpf: Keep last insn's aux data in add_hidden_subprog()
bpf: Add one allocator for hidden stack slots
bpf: Add flags and release kfuncs to the dtor registry
bpf: Register cleanup destructors for more types
bpf: Record where JITed frames keep the BPF stack
bpf: Give every reference an owner frame
bpf: Release resources in hidden slots on bpf_throw()
bpf: Track held resources in hidden stack slots
bpf: Describe held resources at throw sites
bpf: Describe handles in registers at throw sites
bpf: Describe locks at throw sites
bpf: Apply hidden slot limits only when a slot is needed
bpf: Allow a callee to leave resources with its caller
bpf: Allow a callee to release its caller's resources
bpf: Transfer a reference to the callee that releases it
bpf: Support callx when a callee releases resources selftests/bpf: Test rejected throws that hold resources selftests/bpf: Test resource cleanup on bpf_throw() selftests/bpf: Test cleanup of handles on the stack selftests/bpf: Test cleanup of handles in registers selftests/bpf: Test callees releasing caller resources selftests/bpf: Test resources a callee leaves with its caller selftests/bpf: Test cleanup of locks described at sites selftests/bpf: Test callee releases through callx
arch/arm64/net/Makefile | 1 + arch/arm64/net/bpf_jit_comp.c | 168 +- arch/arm64/net/bpf_throw.S | 33 + arch/x86/net/Makefile | 1 + arch/x86/net/bpf_jit_comp.c | 158 +- arch/x86/net/bpf_throw.S | 45 + drivers/base/power/wakeup.c | 25 +- drivers/hid/bpf/hid_bpf_dispatch.c | 23 + drivers/hid/bpf/hid_bpf_dispatch.h | 1 + fs/bpf_fs_kfuncs.c | 27 +- include/linux/bpf.h | 206 +- include/linux/bpf_throw.h | 71 + include/linux/bpf_verifier.h | 255 ++ include/linux/btf.h | 23 +- include/linux/filter.h | 1 + include/net/netfilter/nf_conntrack_bpf.h | 1 + include/net/xfrm.h | 1 + kernel/bpf/Makefile | 2 +- kernel/bpf/bpf_iter.c | 6 + kernel/bpf/btf.c | 101 +- kernel/bpf/cfg.c | 23 +- kernel/bpf/cgroup_iter.c | 6 + kernel/bpf/core.c | 21 + kernel/bpf/cpumask.c | 7 +- kernel/bpf/crypto.c | 7 +- kernel/bpf/dmabuf_iter.c | 6 + kernel/bpf/fixups.c | 293 ++- kernel/bpf/frame_desc.c | 2143 +++++++++++++++++ kernel/bpf/helpers.c | 135 +- kernel/bpf/join.c | 26 +- kernel/bpf/kmem_cache_iter.c | 6 + kernel/bpf/states.c | 43 +- kernel/bpf/task_iter.c | 18 + kernel/bpf/unwind.c | 346 +++ kernel/bpf/verifier.c | 378 ++- kernel/sched/ext/ext.c | 41 + mm/bpf_memcontrol.c | 28 +- net/bpf/test_run.c | 14 +- net/core/bpf_ksock.c | 3 + net/core/filter.c | 2 +- net/netfilter/nf_conntrack_bpf.c | 38 + net/sched/bpf_qdisc.c | 10 +- net/xfrm/xfrm_state_bpf.c | 26 +- .../bpf/prog_tests/exceptions_cleanup.c | 323 +++ .../selftests/bpf/prog_tests/verifier.c | 2 + .../selftests/bpf/progs/exceptions_cleanup.c | 1997 +++++++++++++++ .../selftests/bpf/progs/exceptions_fail.c | 300 ++- .../selftests/bpf/progs/verifier_frame_desc.c | 1121 +++++++++ .../bpf/progs/verifier_private_stack.c | 12 +- .../selftests/bpf/test_kmods/bpf_testmod.c | 43 +- 50 files changed, 8224 insertions(+), 343 deletions(-) create mode 100644 arch/arm64/net/bpf_throw.S create mode 100644 arch/x86/net/bpf_throw.S create mode 100644 include/linux/bpf_throw.h create mode 100644 kernel/bpf/frame_desc.c create mode 100644 kernel/bpf/unwind.c create mode 100644 tools/testing/selftests/bpf/prog_tests/exceptions_cleanup.c create mode 100644 tools/testing/selftests/bpf/progs/exceptions_cleanup.c create mode 100644 tools/testing/selftests/bpf/progs/verifier_frame_desc.c
base-commit: 0fb18ce8a2e9de830433923cf65838933d615359
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 1/29 bpf: Record where JITs save callee-saved registers
Currently bpf_throw() discards the frames between the throw site and the main program without running their epilogues. The main program restores the callee-saved registers that these frames clobbered: its prologue saves all of them, and the exception callback pops them.
To restore the registers from the discarded frames instead, the unwinder must know where each JITed function saved the R6-R9 of its caller. Walking outwards from the throw site, a register is either:
- still in the physical register, if no inner frame saved it; or
- in the prologue save area of the nearest inner frame that saved it.
Add callee_reg_off[4] to struct bpf_prog_aux. Each element is the offset of a saved register from the native frame pointer that arch_bpf_stack_walk() reports for the frame, or 0 if the register is not saved. The x86-64 and arm64 JITs fill it in. Nothing uses it yet.
On x86-64 the frame holds, below rbp from high to low addresses:
- the program stack of round_up(stack_depth, 8) bytes, or nothing with a private stack;
- the tail call count and its pointer, when tail_call_reachable;
- r12, when the program is an exception boundary or uses an arena;
- rbx, r13, r14 and r15 in push order, each only if it is pushed. An exception boundary pushes all four.
On arm64:
- the frame record is at x29, and the tail call count pair is right below it;
- the callee-saved registers follow as 16 byte pairs, in the order that find_used_callee_regs() lists them, with the first register of a pair at the lower address. The i-th pushed register is therefore at -32 - 16 * (i / 2) + 8 * (i % 2);
- an exception boundary pushes x19-x28 in order, so R6-R9 take the first four slots.
--- arch/arm64/net/bpf_jit_comp.c | 37 +++++++++++++++++++++++++++++++++++ arch/x86/net/bpf_jit_comp.c | 33 +++++++++++++++++++++++++++++++ include/linux/bpf.h | 6 ++++++ 3 files changed, 76 insertions(+)
arch/arm64/net/bpf_jit_comp.c +37 -0
@@ -854,6 +854,42 @@ static void find_used_callee_regs(struct jit_ctx *ctx) ctx->nr_used_callee_reg = i; } +/* +* Offset from the frame pointer of the @idx-th register pushed by push_callee_regs(). The +* frame record sits at the frame pointer, the tail call count pair right below it, and +* then the pushed pairs, each 16 bytes with its first register at the lower address. +*/ +static int callee_reg_slot_off (int idx) +{ +return -32 -16 * (idx /2 ) +8 * (idx %2 ); +} + +/* +* Record where push_callee_regs() saved the caller's R6..R9, relative to the frame +* pointer that arch_bpf_stack_walk() reports for this frame, for the exception unwinder. +* An exception boundary pushes x19..x28 in order, so R6..R9 take the first four slots. +*/ +static void record_callee_reg_off (struct jit_ctx *ctx) +{ +struct bpf_prog_aux *aux = ctx->prog->aux; +int i, j; + +if (aux->exception_boundary) { +for (i =0 ; i <4 ; i++) + aux->callee_reg_off[i] =callee_reg_slot_off (i); +return ; + } +for (i =0 ; i <4 ; i++) { + aux->callee_reg_off[i] =0 ; +for (j =0 ; j < ctx->nr_used_callee_reg; j++) { +if (ctx->used_callee_reg[j] != bpf2a64[BPF_REG_6 + i]) +continue ; + aux->callee_reg_off[i] =callee_reg_slot_off (j); +break ; + } + } +} +/* Save callee-saved registers */ static void push_callee_regs (struct jit_ctx *ctx) {
@@ -884,6 +920,7 @@ static void push_callee_regs(struct jit_ctx *ctx)emit (A64_PUSH (reg1,A64_ZR ,A64_SP ), ctx); } } +record_callee_reg_off (ctx); }/* Restore callee-saved registers */
arch/x86/net/bpf_jit_comp.c +33 -0
@@ -382,6 +382,24 @@ static void pop_callee_regs(u8 **pprog, bool *callee_regs_used) *pprog = prog; } +/* +* Record where push_callee_regs() saved the caller's R6..R9 relative to rbp, for the +* exception unwinder. @off is the offset of rsp from rbp right before the first push. +*/ +static void record_callee_reg_off (struct bpf_prog *prog,s32 off,bool *callee_regs_used) +{ +int i; + +for (i =0 ; i <4 ; i++) { +if (!callee_regs_used[i]) { + prog->aux->callee_reg_off[i] =0 ; +continue ; + } + off -=8 ; + prog->aux->callee_reg_off[i] = off; + } +} +/* add rsp, depth */ static void emit_add_rsp (u8 **pprog,u32 depth) {
@@ -2402,6 +2420,7 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int */* it has to be saved around calls of helpers and kfuncs */ bool arena_sp_save = arena_sp &&calls_clobber_arena_sp (bpf_prog);s32 arena_sp_off =0 ; +s32 callee_regs_off;int err; stack_depth = bpf_prog->aux->stack_depth;
@@ -2472,6 +2491,20 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *push_callee_regs (&prog, callee_regs_used); } +/* +* Record where the pushes above saved the caller's R6..R9, relative to the +* rbp that arch_bpf_stack_walk() reports for this frame. Below rbp lie, from +* high to low addresses: round_up(stack_depth, 8) bytes of program stack +* (none with a private stack), the tail call count and its pointer (16 bytes) +* when tail_call_reachable, r12 when exception_boundary or arena_vm_start, +* and then rbx, r13, r14 and r15 in push order, each only when pushed. +*/ + callee_regs_off = -(s32 )round_up (stack_depth,8 ) - (tail_call_reachable ?16 :0 ); +if (bpf_prog->aux->exception_boundary || arena_vm_start) + callee_regs_off -=8 ; +record_callee_reg_off (bpf_prog, callee_regs_off, + bpf_prog->aux->exception_boundary ? all_callee_regs_used : callee_regs_used); +/* Compute callee-saved register area size. */ callee_saved_size =0 ;if (bpf_prog->aux->exception_boundary || arena_vm_start)
include/linux/bpf.h +6 -0
@@ -1997,6 +1997,12 @@ struct bpf_prog_aux {u64 (*bpf_exception_cb)(u64 cookie,u64 sp,u64 bp,u64 ,u64 );u16 stack_arg_sp_adjust;u16 freplace_link_cnt;/* counts freplace links extending this prog */ +/* +* Offsets from the native frame pointer of this JITed program at which +* its prologue saved the caller's R6..R9, or 0 when that register is not +* saved. Filled by the JIT; consumed by the exception unwinder. +*/ +s16 callee_reg_off[4 ];#ifdef CONFIG_SECURITY void *security;#endif
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 2/29 bpf: Add bpf_throw_unwind() and an arch entry hook
In reply to Kumar Kartikeya DwivediCurrently bpf_throw() is a C function. It walks the stack to the frame of the main program and calls its exception callback, which resets the stack to that frame and returns from the program. To restore the callee-saved registers of the frames it discards, the unwinder needs their values at the throw site. By the time C code runs, the compiler may have clobbered them.
Move the body of bpf_throw() into bpf_throw_unwind(), which takes the callee-saved registers of the throw site, R6-R9, along with the cookie:
- An architecture can provide a native entry for the kfunc, which captures the registers and then calls bpf_throw_unwind(). bpf_arch_throw_entry() returns that entry.
- The verifier redirects a call to bpf_throw() to the entry when it specializes the kfunc. The JIT still finds the function model of the call through the specialized descriptor.
- Without an entry, bpf_throw() calls bpf_throw_unwind() with NULL, as before.
Nothing uses the registers yet.
bpf_throw_unwind() never returns. It transfers to a JITed callback, which returns to the kernel code that called the program. On arm64 with a shadow call stack, the function's prologue would push a return address that is never popped. The caller of the program would then pop it instead of its own return address, and trap when it returns. So:
- Mark the function __noscs, which keeps a static shadow call stack out of it.
- A dynamic shadow call stack is patched in at boot, in place of the pointer authentication instructions of each function. Under CONFIG_DYNAMIC_SCS, build the function without pointer authentication but with branch target identification, as drivers/misc/lkdtm/cfi.c does for the same reason.
The function loses nothing by this, since it never returns through its return address.
For the same reason, mark bpf_throw_unwind() notrace, and add it and bpf_throw() to the functions that an fexit program cannot attach to: a function that does not return never leaves the trampoline.
--- include/linux/bpf.h | 1 + include/linux/bpf_throw.h | 52 +++++++++++++++++++++++++++++++++++++++ kernel/bpf/core.c | 9 +++++++ kernel/bpf/helpers.c | 17 ++++++++++--- kernel/bpf/verifier.c | 7 ++++++ 5 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 include/linux/bpf_throw.h
include/linux/bpf.h +1 -0
@@ -7,6 +7,7 @@#include <uapi/linux/bpf.h> #include <uapi/linux/filter.h> #include <linux/bpf_defs.h> +#include <linux/bpf_throw.h> #include <crypto/sha2.h> #include <linux/workqueue.h>
include/linux/bpf_throw.h new file +52 -0
52 lines not on this page · raw message
kernel/bpf/core.c +9 -0
@@ -3930,6 +3930,15 @@ void __weak arch_bpf_stack_walk(bool (*consume_fn)(void *cookie, u64 ip, u64 sp, { } +/* +* The native entry of bpf_throw(), which captures the BPF register file of +* the throw site, or NULL when the architecture calls the kfunc directly. +*/ +void *__weak bpf_arch_throw_entry (void ) +{ +return NULL ; +} +bool __weak bpf_jit_supports_timed_may_goto (void ) {return false;
kernel/bpf/helpers.c +14 -3
@@ -3460,7 +3460,13 @@ static bool bpf_stack_walker(void *cookie, u64 ip, u64 sp, u64 bp)return false; } -__bpf_kfunc void bpf_throw(u64 cookie) +/* +* Unwind the stack to the exception callback of the main program. @regs holds +* the callee-saved registers of the throw site, or is NULL when the +* architecture has no native entry and the kfunc was called directly. +*/ +__bpf_unwind_entry +notrace void bpf_throw_unwind(struct bpf_throw_regs *regs, u64 cookie) {struct bpf_throw_ctx ctx = {};
@@ -3472,8 +3478,8 @@ __bpf_kfunc void bpf_throw(u64 cookie)WARN_ON_ONCE (!ctx.cnt);/* * Prevent KASAN false positives for CONFIG_KASAN_STACK by unpoisoning -* deeper stack depths than ctx.sp as we do not return from bpf_throw, -* which skips compiler generated instrumentation to do the same. Some +* deeper stack depths than ctx.sp as we do not return from here, which +* skips compiler generated instrumentation to do the same. Some * architectures cannot recover sp while unwinding, so fall back to bp. */ kasan_unpoison_task_stack_below ((void *)(long )(ctx.sp ?: ctx.bp));
@@ -3481,6 +3487,11 @@ __bpf_kfunc void bpf_throw(u64 cookie)WARN (1 ,"A call to BPF exception callback should never return\n" ); } +__bpf_kfunc void bpf_throw(u64 cookie) +{ +bpf_throw_unwind (NULL , cookie); +} + __bpf_kfunc int bpf_wq_init(struct bpf_wq *wq, void *p__const_map, unsigned int flags) {struct bpf_async_kern *async = (struct bpf_async_kern *)wq;
kernel/bpf/verifier.c +7 -0
@@ -24434,6 +24434,8 @@ BTF_ID(func, do_exit) BTF_ID(func, do_group_exit) BTF_ID(func, kthread_complete_and_exit) BTF_ID(func, make_task_dead) +BTF_ID(func, bpf_throw) +BTF_ID(func, bpf_throw_unwind)BTF_SET_END (noreturn_deny) static bool can_be_sleepable(struct bpf_prog *prog)
@@ -24793,6 +24795,11 @@ static int specialize_kfunc(struct bpf_verifier_env *env, struct bpf_kfunc_desc }else if (func_id == special_kfunc_list[KF_bpf_arena_free_pages]) {if (env->insn_aux_data[insn_idx].non_sleepable) addr = (unsigned long )bpf_arena_free_pages_non_sleepable; + }else if (func_id == special_kfunc_list[KF_bpf_throw]) { +void *entry =bpf_arch_throw_entry (); + +if (entry) + addr = (unsigned long )entry; } desc->addr = addr;return 0 ;
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 3/29 bpf, x86: Capture registers in a native bpf_throw() entry
In reply to Kumar Kartikeya DwivediAdd bpf_throw_entry, which the verifier calls instead of bpf_throw(). It saves the BPF registers of the throw site, R0-R9, on the stack in the layout of struct bpf_throw_regs, passes the cookie from R1, and calls bpf_throw_unwind(). That function does not return, so a ud2 follows the call.
The unwinder needs the callee-saved R6-R9 to restore them for the frames it discards. The entry also captures R0-R5:
- later patches describe which register holds a reference at a throw site, and any of R0-R9 can hold one there;
- bpf_throw() does not return, so the compiler has no reason to move a reference out of a caller-saved register before the call.
R0-R5 are stored before R6-R9, so the unwinder can index the saved registers by BPF register number. An architecture may append callee-saved registers that do not map to a BPF register after R9. None does yet.
The JIT calls the entry directly through __bpf_call_base + imm, so it needs no ENDBR. objtool generates ORC data for its frame, so the stack walk in bpf_throw_unwind() passes through the entry to the BPF frames, where the frame pointer fallback of the ORC unwinder takes over as before.
The entry calls bpf_throw_unwind(), which is only built with CONFIG_BPF_SYSCALL. Build the entry with CONFIG_BPF_SYSCALL as well, and have the JIT return it only then. The JIT itself is built with CONFIG_BPF_JIT alone, for cBPF.
--- arch/x86/net/Makefile | 1 + arch/x86/net/bpf_jit_comp.c | 8 +++++++ arch/x86/net/bpf_throw.S | 43 +++++++++++++++++++++++++++++++++++++ include/linux/bpf_throw.h | 17 ++++++++++----- 4 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 arch/x86/net/bpf_throw.S
arch/x86/net/Makefile +1 -0
@@ -7,4 +7,5 @@ ifeq ($(CONFIG_X86_32),y) obj-$(CONFIG_BPF_JIT) += bpf_jit_comp32.o else obj-$(CONFIG_BPF_JIT) += bpf_jit_comp.o bpf_timed_may_goto.o + obj-$(CONFIG_BPF_SYSCALL) += bpf_throw.o endif
arch/x86/net/bpf_jit_comp.c +8 -0
@@ -5135,6 +5135,14 @@ void arch_bpf_stack_walk(bool (*consume_fn)(void *cookie, u64 ip, u64 sp, u64 bp#endif } +#ifdef CONFIG_BPF_SYSCALL +/* arch/x86/net/bpf_throw.S, which calls bpf_throw_unwind(); both need the syscall. */ +void *bpf_arch_throw_entry (void ) +{ +return (void *)bpf_throw_entry; +} +#endif +void bpf_arch_poke_desc_update (struct bpf_jit_poke_descriptor *poke,struct bpf_prog *new,struct bpf_prog *old) {
arch/x86/net/bpf_throw.S new file +43 -0
43 lines not on this page · raw message
include/linux/bpf_throw.h +12 -5
@@ -6,21 +6,28 @@#ifndef _LINUX_BPF_THROW_H#define _LINUX_BPF_THROW_H +/* +* The callee-saved registers of the architecture that no BPF register maps +* to, which an entry captures after R9. None yet. +*/ +#define BPF_THROW_NR_EXTRA 0/* The callee-saved registers the unwinder tracks: R6..R9. */ #define BPF_THROW_NR_CALLEE_SAVED 4 -/* The size of struct bpf_throw_regs, for the entries. */ -#define BPF_THROW_REGS_SIZE (8 * BPF_THROW_NR_CALLEE_SAVED) +/* The size of struct bpf_throw_regs, for the entries: R0..R9 and the extra ones. */ +#define BPF_THROW_REGS_SIZE (8 * (10 + BPF_THROW_NR_EXTRA))#ifndef __ASSEMBLER__#include <linux/build_bug.h> #include <linux/types.h> +#include <uapi/linux/bpf.h> /* -* The callee-saved registers captured at a throw site, indexed like -* bpf_prog_aux::callee_reg_off. +* The register file captured at a throw site: the BPF registers R0..R9, then +* the extra callee-saved registers of the architecture; from R6 on, indexed +* like bpf_prog_aux::callee_reg_off. */ struct bpf_throw_regs { -u64 saved[BPF_THROW_NR_CALLEE_SAVED ]; +u64 r[BPF_REG_10 +BPF_THROW_NR_EXTRA ]; };static_assert (sizeof (struct bpf_throw_regs ) ==BPF_THROW_REGS_SIZE );
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 4/29 bpf, arm64: Capture registers in a native bpf_throw() entry
In reply to Kumar Kartikeya DwivediAdd a native entry for bpf_throw() on arm64, as on x86-64. The verifier redirects a call to bpf_throw() to bpf_throw_entry(), which:
- pushes a frame record whose return address points into the program;
- saves the BPF registers of the throw site next to it as a struct
bpf_throw_regs: R0 from x8, R1-R5 from x0-x4, and R6-R9 from x19-x22;
- calls bpf_throw_unwind() with it.
Nothing uses the registers yet.
--- arch/arm64/net/Makefile | 1 + arch/arm64/net/bpf_jit_comp.c | 8 ++++++++ arch/arm64/net/bpf_throw.S | 30 ++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 arch/arm64/net/bpf_throw.S
arch/arm64/net/Makefile +1 -0
@@ -3,3 +3,4 @@ # ARM64 networking code # obj-$(CONFIG_BPF_JIT) += bpf_jit_comp.o bpf_timed_may_goto.o +obj-$(CONFIG_BPF_SYSCALL) += bpf_throw.o
arch/arm64/net/bpf_jit_comp.c +8 -0
@@ -10,6 +10,7 @@#include <linux/arm-smccc.h> #include <linux/bitfield.h> #include <linux/bpf.h> +#include <linux/bpf_throw.h> #include <linux/cfi.h> #include <linux/extable.h> #include <linux/filter.h>
@@ -3965,6 +3966,13 @@ bool bpf_jit_supports_timed_may_goto(void)return true; } +#ifdef CONFIG_BPF_SYSCALL +void *bpf_arch_throw_entry (void ) +{ +return (void *)bpf_throw_entry; +} +#endif +bool bpf_jit_inlines_helper_call (s32 imm) {switch (imm) {
arch/arm64/net/bpf_throw.S new file +30 -0
@@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ + +#include <linux/linkage.h> +#include <linux/bpf_throw.h> + +/* + * Native entry of the bpf_throw() kfunc. The verifier retargets the call to + * bpf_throw() here, so the BPF register file of the throw site is still intact + * on entry and x30 is the return address into the program. Push a frame record + * whose link register is that address and save the file below it as a struct + * bpf_throw_regs: r[i] sits at [sp + 16 + 8 * i], with R0 in x8, R1-R5 in + * x0-x4 and R6-R9 in x19-x22. Then hand it to bpf_throw_unwind(), which never + * returns. + */ +SYM_FUNC_START(bpf_throw_entry) + stp x29, x30, [sp, #-(16 + BPF_THROW_REGS_SIZE)]! + mov x29, sp + stp x8, x0, [sp, #16] + stp x1, x2, [sp, #32] + stp x3, x4, [sp, #48] + stp x19, x20, [sp, #64] + stp x21, x22, [sp, #80] + + /* bpf_throw_unwind(regs, cookie); the cookie is R1 of the caller. */ + mov x1, x0 + add x0, sp, #16 + bl bpf_throw_unwind + brk #0x1 +SYM_FUNC_END(bpf_throw_entry)
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 5/29 bpf: Restore callee-saved registers from unwound frames
In reply to Kumar Kartikeya DwivediCurrently a program that may throw is an exception boundary. Its prologue pushes every callee-saved register that the JIT could clobber, five on x86-64 and ten on arm64, whether or not the program uses them. The exception callback pops them all from the boundary frame before it runs. This restores the registers that a discarded frame clobbered, since the epilogues of the frames between the throw site and the boundary never run.
Every invocation of such a program pays for the pushes and pops: 18 bytes of code on x86-64 for a program that uses none of the registers, and 80 bytes of stack on arm64.
Have the unwinder restore the registers instead, on the throw path only:
- Each JITed function already records where its prologue saved the R6-R9 of its caller. Extend this to every other callee-saved register: r12 on x86-64 and x23-x28 on arm64.
- The native entries capture the extra registers after R9, before any C code runs.
- The unwinder starts with the values at the throw site. Walking outwards, it takes from each frame the values that the frame saved for its caller. After the main frame, these are the values that the caller of the program expects.
- The exception callback receives them as its fourth argument and loads them before it reuses the main frame, instead of popping the saved registers of the boundary. It then sets up the rest of its frame like any function.
The extra registers are:
- r12 on x86-64, which holds the arena base;
- x23 on arm64, the stack pointer of the stack in arena;
- x25-x28 on arm64: the BPF frame pointer, the tail call count pointer, the private stack pointer and the arena base;
- x24 on arm64. No JITed code touches it, but the C code of the unwinder may still be using it when it enters the callback, whose epilogue never runs.
The boundary prologue becomes an ordinary prologue. The x86-64 stack pointer adjustment that the callback needed to find the saved registers of the boundary is removed along with the pops.
Architectures without a native entry, s390 and powerpc, are not affected. There bpf_throw() calls the unwinder with NULL, the boundary prologue still saves every register, and the callback restores them from the frame as before.
bpf_prog_frame_aux() and bpf_throw_caller_regs() are the two steps of the walk. A later patch reuses them to release the resources that the frames hold.
The JITed code of the two verifier_private_stack programs that throw changes on arm64 with the prologue, so update the expected output.
--- arch/arm64/net/bpf_jit_comp.c | 126 +++++++---------- arch/arm64/net/bpf_throw.S | 7 +- arch/x86/net/bpf_jit_comp.c | 132 +++++++----------- arch/x86/net/bpf_throw.S | 6 +- include/linux/bpf.h | 41 +++++- include/linux/bpf_throw.h | 20 ++- kernel/bpf/fixups.c | 1 - kernel/bpf/helpers.c | 30 +++- .../bpf/progs/verifier_private_stack.c | 12 +- 9 files changed, 190 insertions(+), 185 deletions(-)
arch/arm64/net/bpf_jit_comp.c +52 -74
@@ -866,24 +866,29 @@ static int callee_reg_slot_off(int idx) }/* -* Record where push_callee_regs() saved the caller's R6..R9, relative to the frame -* pointer that arch_bpf_stack_walk() reports for this frame, for the exception unwinder. -* An exception boundary pushes x19..x28 in order, so R6..R9 take the first four slots. +* Record where the prologue saved the caller's callee-saved registers, relative to +* the frame pointer that arch_bpf_stack_walk() reports for this frame, for the +* exception unwinder. The registers are x19..x28 in the order of struct +* bpf_throw_regs, R6..R9 first: the tail call count pointer is saved with the frame +* record, the others where push_callee_regs() put them, and x24 nowhere, since +* JITed code leaves it alone. */ static void record_callee_reg_off (struct jit_ctx *ctx) {struct bpf_prog_aux *aux = ctx->prog->aux;int i, j; -if (aux->exception_boundary) { -for (i =0 ; i <4 ; i++) - aux->callee_reg_off[i] =callee_reg_slot_off (i); -return ; - } -for (i =0 ; i <4 ; i++) { +BUILD_BUG_ON (BPF_THROW_NR_CALLEE_SAVED !=10 ); +for (i =0 ; i <BPF_THROW_NR_CALLEE_SAVED ; i++) { +u8 reg =A64_R (19 + i); + aux->callee_reg_off[i] =0 ; +if (reg == bpf2a64[TCCNT_PTR ]) { + aux->callee_reg_off[i] =-8 ; +continue ; + }for (j =0 ; j < ctx->nr_used_callee_reg; j++) { -if (ctx->used_callee_reg[j] != bpf2a64[BPF_REG_6 + i]) +if (ctx->used_callee_reg[j] != reg)continue ; aux->callee_reg_off[i] =callee_reg_slot_off (j);break ;
@@ -891,35 +896,25 @@ static void record_callee_reg_off(struct jit_ctx *ctx) } } -/* Save callee-saved registers */ +/* +* Save the callee-saved registers this function uses. An exception boundary +* saves nothing more: the unwinder recovers the registers of the frames it +* discards and hands them to the exception callback. +*/ static void push_callee_regs (struct jit_ctx *ctx) {int reg1, reg2, i; -/* -* Program acting as exception boundary should save all ARM64 -* Callee-saved registers as the exception callback needs to recover -* all ARM64 Callee-saved registers in its epilogue. -*/ -if (ctx->prog->aux->exception_boundary) { -emit (A64_PUSH (A64_R (19 ),A64_R (20 ),A64_SP ), ctx); -emit (A64_PUSH (A64_R (21 ),A64_R (22 ),A64_SP ), ctx); -emit (A64_PUSH (A64_R (23 ),A64_R (24 ),A64_SP ), ctx); -emit (A64_PUSH (A64_R (25 ),A64_R (26 ),A64_SP ), ctx); -emit (A64_PUSH (A64_R (27 ),A64_R (28 ),A64_SP ), ctx); - ctx->fp_used = true; - }else { -find_used_callee_regs (ctx); -for (i =0 ; i +1 < ctx->nr_used_callee_reg; i +=2 ) { - reg1 = ctx->used_callee_reg[i]; - reg2 = ctx->used_callee_reg[i +1 ]; -emit (A64_PUSH (reg1, reg2,A64_SP ), ctx); - } -if (i < ctx->nr_used_callee_reg) { - reg1 = ctx->used_callee_reg[i]; -/* keep SP 16-byte aligned */ -emit (A64_PUSH (reg1,A64_ZR ,A64_SP ), ctx); - } +find_used_callee_regs (ctx); +for (i =0 ; i +1 < ctx->nr_used_callee_reg; i +=2 ) { + reg1 = ctx->used_callee_reg[i]; + reg2 = ctx->used_callee_reg[i +1 ]; +emit (A64_PUSH (reg1, reg2,A64_SP ), ctx); + } +if (i < ctx->nr_used_callee_reg) { + reg1 = ctx->used_callee_reg[i]; +/* keep SP 16-byte aligned */ +emit (A64_PUSH (reg1,A64_ZR ,A64_SP ), ctx); }record_callee_reg_off (ctx); }
@@ -927,34 +922,19 @@ static void push_callee_regs(struct jit_ctx *ctx)/* Restore callee-saved registers */ static void pop_callee_regs (struct jit_ctx *ctx) { -struct bpf_prog_aux *aux = ctx->prog->aux;int reg1, reg2, i; -/* -* Program acting as exception boundary pushes R23 and R24 in addition -* to BPF callee-saved registers. Exception callback uses the boundary -* program's stack frame, so recover these extra registers in the above -* two cases. -*/ -if (aux->exception_boundary || aux->exception_cb) { -emit (A64_POP (A64_R (27 ),A64_R (28 ),A64_SP ), ctx); -emit (A64_POP (A64_R (25 ),A64_R (26 ),A64_SP ), ctx); -emit (A64_POP (A64_R (23 ),A64_R (24 ),A64_SP ), ctx); -emit (A64_POP (A64_R (21 ),A64_R (22 ),A64_SP ), ctx); -emit (A64_POP (A64_R (19 ),A64_R (20 ),A64_SP ), ctx); - }else { - i = ctx->nr_used_callee_reg -1 ; -if (ctx->nr_used_callee_reg %2 !=0 ) { - reg1 = ctx->used_callee_reg[i]; -emit (A64_POP (reg1,A64_ZR ,A64_SP ), ctx); - i--; - } -while (i >0 ) { - reg1 = ctx->used_callee_reg[i -1 ]; - reg2 = ctx->used_callee_reg[i]; -emit (A64_POP (reg1, reg2,A64_SP ), ctx); - i -=2 ; - } + i = ctx->nr_used_callee_reg -1 ; +if (ctx->nr_used_callee_reg %2 !=0 ) { + reg1 = ctx->used_callee_reg[i]; +emit (A64_POP (reg1,A64_ZR ,A64_SP ), ctx); + i--; + } +while (i >0 ) { + reg1 = ctx->used_callee_reg[i -1 ]; + reg2 = ctx->used_callee_reg[i]; +emit (A64_POP (reg1, reg2,A64_SP ), ctx); + i -=2 ; } }
@@ -1055,24 +1035,22 @@ static int build_prologue(struct jit_ctx *ctx, bool ebpf_from_cbpf)/* BTI landing pad for the tail call, done with a BR */ emit_bti (A64_BTI_J , ctx); } -push_callee_regs (ctx); }else {/* -* Exception callback receives FP of Main Program as third -* parameter +* The exception callback receives the main program's frame +* pointer as its third parameter and, as its fourth, the +* callee-saved registers x19..x28 the unwinder recovered from +* the frames it discarded: the values the main program's caller +* expects back. Load them, then build the rest of the frame +* below the main program's frame record like any function. */ +for (int i =0 ; i <BPF_THROW_NR_CALLEE_SAVED ; i++) +emit (A64_LDR64I (A64_R (19 + i),A64_R (3 ),8 * i), ctx);emit (A64_MOV (1 ,A64_FP ,A64_R (2 )), ctx); -/* -* Main Program already pushed the frame record and the -* callee-saved registers. The exception callback will not push -* anything and re-use the main program's stack. -* -* 12 registers are on the stack -*/ -emit (A64_SUB_I (1 ,A64_SP ,A64_FP ,96 ), ctx); -/* The callback may use its own BPF stack, set up fp for it. */ - ctx->fp_used = true; +emit (A64_MOV (1 ,A64_SP ,A64_FP ), ctx); +prepare_bpf_tail_call_cnt (ctx); } +push_callee_regs (ctx);/* Stack must be multiples of 16B */ ctx->stack_size =round_up (prog->aux->stack_depth,16 );
arch/arm64/net/bpf_throw.S +5 -2
@@ -10,8 +10,8 @@ * on entry and x30 is the return address into the program. Push a frame record * whose link register is that address and save the file below it as a struct * bpf_throw_regs: r[i] sits at [sp + 16 + 8 * i], with R0 in x8, R1-R5 in - * x0-x4 and R6-R9 in x19-x22. Then hand it to bpf_throw_unwind(), which never - * returns. + * x0-x4, R6-R9 in x19-x22 and the extra callee-saved registers x23-x28 after + * them. Then hand it to bpf_throw_unwind(), which never returns. */ SYM_FUNC_START(bpf_throw_entry) stp x29, x30, [sp, #-(16 + BPF_THROW_REGS_SIZE)]!
@@ -21,6 +21,9 @@ SYM_FUNC_START(bpf_throw_entry) stp x3, x4, [sp, #48] stp x19, x20, [sp, #64] stp x21, x22, [sp, #80] + stp x23, x24, [sp, #96] + stp x25, x26, [sp, #112] + stp x27, x28, [sp, #128] /* bpf_throw_unwind(regs, cookie); the cookie is R1 of the caller. */ mov x1, x0
arch/x86/net/bpf_jit_comp.c +47 -85
202 lines not on this page · raw message
arch/x86/net/bpf_throw.S +4 -2
@@ -10,7 +10,8 @@ /* * Native entry of the bpf_throw() kfunc. The verifier retargets the call to * bpf_throw() here, so the BPF register file of the throw site is still intact - * on entry. Capture it on the stack as a struct bpf_throw_regs and hand it to + * on entry. Capture it on the stack as a struct bpf_throw_regs, along with + * r12, the callee-saved register no BPF register maps to, and hand it to * bpf_throw_unwind(), which never returns. */ SYM_FUNC_START(bpf_throw_entry)
@@ -20,7 +21,8 @@ SYM_FUNC_START(bpf_throw_entry) pushq %rbp movq %rsp, %rbp - /* Save R9..R0 so that [rsp + 8 * i] is r[i]. */ + /* Save r12, then R9..R0 so that [rsp + 8 * i] is r[i]. */ + pushq %r12 /* r[10] */ pushq %r15 /* R9 */ pushq %r14 /* R8 */ pushq %r13 /* R7 */
include/linux/bpf.h +36 -5
57 lines not on this page · raw message
include/linux/bpf_throw.h +16 -4
@@ -8,11 +8,23 @@/* * The callee-saved registers of the architecture that no BPF register maps -* to, which an entry captures after R9. None yet. +* to, which the entries capture after R9 and the unwinder restores along +* with R6..R9 when it discards frames: r12 on x86-64, which holds the arena +* base, and x23..x28 on arm64, of which x23 is the stack pointer of the +* stack in arena and x25..x28 are the BPF frame pointer, the tail call count +* pointer, the private stack pointer and the arena base, while x24 is left +* alone by JITed code but may be in use by the C code of the unwinder when +* it enters the exception callback, whose epilogue never runs. */ -#define BPF_THROW_NR_EXTRA 0 -/* The callee-saved registers the unwinder tracks: R6..R9. */ -#define BPF_THROW_NR_CALLEE_SAVED 4 +#if defined(CONFIG_X86_64 ) +#define BPF_THROW_NR_EXTRA 1 +#elif defined(CONFIG_ARM64 ) +#define BPF_THROW_NR_EXTRA 6 +#else +#define BPF_THROW_NR_EXTRA 0 +#endif +/* The callee-saved registers the unwinder tracks: R6..R9, then the extra ones. */ +#define BPF_THROW_NR_CALLEE_SAVED (4 + BPF_THROW_NR_EXTRA)/* The size of struct bpf_throw_regs, for the entries: R0..R9 and the extra ones. */ #define BPF_THROW_REGS_SIZE (8 * (10 + BPF_THROW_NR_EXTRA))
kernel/bpf/fixups.c +0 -1
@@ -2103,7 +2103,6 @@ static int jit_subprogs(struct bpf_verifier_env *env) prog->aux->real_func_cnt = env->subprog_cnt; prog->aux->bpf_exception_cb = (void *)func[env->exception_callback_subprog]->bpf_func; prog->aux->exception_boundary = func[0 ]->aux->exception_boundary; - prog->aux->stack_arg_sp_adjust = func[0 ]->aux->stack_arg_sp_adjust;bpf_prog_jit_attempt_done (prog);return 0 ; out_free:
kernel/bpf/helpers.c +26 -4
@@ -3434,6 +3434,13 @@ struct bpf_throw_ctx {u64 sp;u64 bp;int cnt; +/* +* The callee-saved registers of the frame being walked, R6-R9 then the +* architecture's own: what the throw site had, then what each frame +* recovered of its caller's. After the main frame they are the ones +* its caller expects back. +*/ +u64 saved[BPF_THROW_NR_CALLEE_SAVED ]; };static bool bpf_stack_walker (void *cookie,u64 ip,u64 sp,u64 bp)
@@ -3451,6 +3458,12 @@ static bool bpf_stack_walker(void *cookie, u64 ip, u64 sp, u64 bp)rcu_read_unlock ();if (!prog)return !ctx->cnt; +/* +* Recover the caller's callee-saved registers: those this frame's +* prologue saved sit in its frame, the rest are still the values the +* frame itself has. +*/ +bpf_throw_caller_regs (bpf_prog_frame_aux (prog), bp, ctx->saved); ctx->cnt++;if (bpf_is_subprog (prog))return true;
@@ -3461,15 +3474,24 @@ static bool bpf_stack_walker(void *cookie, u64 ip, u64 sp, u64 bp) }/* -* Unwind the stack to the exception callback of the main program. @regs holds -* the callee-saved registers of the throw site, or is NULL when the -* architecture has no native entry and the kfunc was called directly. +* Unwind the stack to the exception callback of the main program. @regs is the +* register file of the throw site, which the entry of the architecture +* captured: the callee-saved registers of the frames unwound are restored from +* it for the callback. It is NULL when the architecture has no native entry +* and the kfunc was called directly. */ __bpf_unwind_entry notrace void bpf_throw_unwind(struct bpf_throw_regs *regs, u64 cookie) {struct bpf_throw_ctx ctx = {}; +/* +* Without the file, on an architecture that has no native entry, the +* prologue of the main program has saved every callee-saved register +* and the callback restores them from its frame, not from @saved. +*/ +if (regs) +memcpy (ctx.saved, ®s->r[BPF_REG_6 ],sizeof (ctx.saved));arch_bpf_stack_walk (bpf_stack_walker, &ctx);WARN_ON_ONCE (!ctx.aux);if (ctx.aux)
@@ -3483,7 +3505,7 @@ notrace void bpf_throw_unwind(struct bpf_throw_regs *regs, u64 cookie)* architectures cannot recover sp while unwinding, so fall back to bp. */ kasan_unpoison_task_stack_below ((void *)(long )(ctx.sp ?: ctx.bp)); - ctx.aux->bpf_exception_cb (cookie, ctx.sp + ctx.aux->stack_arg_sp_adjust, ctx.bp,0 ,0 ); + ctx.aux->bpf_exception_cb (cookie, ctx.sp, ctx.bp, (u64 )(long )ctx.saved,0 );WARN (1 ,"A call to BPF exception callback should never return\n" ); }
tools/testing/selftests/bpf/progs/verifier_private_stack.c +4 -8
@@ -287,11 +287,7 @@ __jited(" mov x29, sp") __jited(" stp xzr, x26, [sp, #-0x10]!") __jited(" mov x26, sp") __jited("...") -__jited(" stp x19, x20, [sp, #-0x10]!") -__jited(" stp x21, x22, [sp, #-0x10]!") -__jited(" stp x23, x24, [sp, #-0x10]!") -__jited(" stp x25, x26, [sp, #-0x10]!") -__jited(" stp x27, x28, [sp, #-0x10]!") +__jited(" stp x25, x27, [sp, #-0x10]!") __jited(" mov x27, {{.*}}") __jited(" movk x27, {{.*}}, lsl #16")__jited (" movk x27, {{.*}}" )
@@ -303,7 +299,7 @@ __jited(" str x0, [x27]") __jited(" mov x0, #0x0") __jited(" bl 0x{{.*}}") __jited(" mov x8, x0") -__jited(" ldp x27, x28, [sp], #0x10") +__jited(" ldp x25, x27, [sp], #0x10")int private_stack_exception_main_prog (void ) { asm volatile (" \
@@ -331,7 +327,7 @@ __jited("...") __jited(" callq") __jited(" popq %r9") __arch_arm64 -__jited(" stp x27, x28, [sp, #-0x10]!") +__jited(" stp x25, x27, [sp, #-0x10]!") __jited(" mov x27, {{.*}}") __jited(" movk x27, {{.*}}, lsl #16")__jited (" movk x27, {{.*}}" )
@@ -342,7 +338,7 @@ __jited(" mov x0, #0x2a") __jited(" str x0, [x27]") __jited(" bl 0x{{.*}}") __jited(" mov x8, x0") -__jited(" ldp x27, x28, [sp], #0x10") +__jited(" ldp x25, x27, [sp], #0x10")int private_stack_exception_sub_prog (void ) { asm volatile (" \
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 6/29 bpf: Keep last insn's aux data in add_hidden_subprog()
In reply to Kumar Kartikeya Dwivediadd_hidden_subprog() appends its instructions by patching the last instruction of the program with a sequence that starts with a copy of that instruction. Like every patch, this moves the aux data of the patched instruction to the end of the new sequence. The aux data of the original last instruction therefore ends up on the exit of the hidden subprog, and the copy of the instruction, which stays in place, loses it.
Today only the JITs read the aux data of the last instruction after this point, for indirect_target, which the patch leaves with the instruction. But a noreturn call such as bpf_throw() can be the last instruction once the code after it is removed, and later patches attach per-instruction data to such calls.
Swap the two entries back after the patch, except for indirect_target.
--- kernel/bpf/fixups.c | 13 +++++++++++++ 1 file changed, 13 insertions(+)
kernel/bpf/fixups.c +13 -0
@@ -2280,8 +2280,10 @@ static int add_hidden_subprog(struct bpf_verifier_env *env, struct bpf_insn *pat {struct bpf_subprog_info *info = env->subprog_info;int err, cnt = env->subprog_cnt; +struct bpf_insn_aux_data *aux;struct bpf_insn *last;struct bpf_prog *prog; +bool indirect;/* We only reserve one slot for hidden subprogs in subprog_info. */ if (env->hidden_subprog_cnt) {
@@ -2299,6 +2301,17 @@ static int add_hidden_subprog(struct bpf_verifier_env *env, struct bpf_insn *patif (err)return err; prog = env->prog; +/* +* The patch starts with the original last insn, but the commit moved that +* insn's aux data to the end of the patch: give it back to the insn. All +* but indirect_target, which the commit left with the insn, since an +* indirect jump to it lands there still, see patch_list_adj_aux(). +*/ + aux = &env->insn_aux_data[prog->len - len]; + indirect = aux->indirect_target; +swap (aux[0 ], aux[len -1 ]); + aux[0 ].indirect_target = indirect; + aux[len -1 ].indirect_target = false; info[cnt +1 ].start = info[cnt].start; info[cnt].start = prog->len - len +1 ;/* The patch ends with the exit. */
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 7/29 bpf: Add one allocator for hidden stack slots
In reply to Kumar Kartikeya DwivediRewrites after verification need stack space that the program does not know about:
- the may_goto count, or the register that a timed may_goto saves;
- the spill area of an inlined bpf_loop();
- the context pointer that a generated epilogue reads back.
Each user computes its own offsets below the verified stack depth and bumps stack_depth at a subprogram boundary in its own loop. may_goto also keeps stack_extra, so that a prologue patch at the end of bpf_do_misc_fixups() knows how many slots to initialize, with a bug check that nothing else uses it.
Later patches need more hidden slots, for the handles of resources that an unwinder has to find. Add one allocator:
- bpf_reserve_stack_slots() hands out 8-byte slots, top down, below the verified depth of a subprogram rounded up to 8 bytes. It bumps stack_depth and stack_extra.
- bpf_stack_slot_init() records the value that a slot must hold on entry.
- bpf_emit_stack_inits() patches all initializing stores into the subprogram prologues in one place, in chunks that fit the instruction buffer. Jumps to the first instruction are redirected past the stores, as the may_goto patch did.
Convert the three users:
- The may_goto slot is reserved at the first may_goto of a subprogram and shared by the rest. The bpf_loop() spill area works the same way.
- The epilogue slot keeps its offset by asking for the next slot before gen_epilogue() decides whether it is needed.
The "stack size %d(extra %d) is too large" check for interpreted programs moves into the allocator, so it covers every user. Slots now start at an 8-byte aligned offset. Before, a subprogram with an unaligned depth got unaligned may_goto slots.
--- include/linux/bpf_verifier.h | 12 ++ kernel/bpf/fixups.c | 265 +++++++++++++++++++++++------------ kernel/bpf/verifier.c | 1 + 3 files changed, 190 insertions(+), 88 deletions(-)
include/linux/bpf_verifier.h +12 -0
@@ -1023,6 +1023,7 @@ struct bpf_subprog_info {u32 exit_idx;struct bpf_iarray *jt;/* jump table shared by all gotox of this subprogram */ u16 stack_depth;/* max. stack depth used by this function */ +/* bytes below the verified stack depth reserved for hidden slots, see bpf_reserve_stack_slots() */ u16 stack_extra;u16 arena_stack_depth;/* size of the frame in the stack in arena */ u32 insns_total;
@@ -1208,6 +1209,13 @@ void bpf_patch_image_install(struct bpf_prog *prog, const struct bpf_patch_image/* single container for all structs * one verifier_env per bpf_check() call */ +/* Initialization of a hidden stack slot, emitted in the subprogram's prologue. */ +struct bpf_stack_init { +u32 subprog; +s16 off; +s32 imm; +}; +struct bpf_verifier_env {u32 insn_idx;u32 prev_insn_idx;
@@ -1233,6 +1241,8 @@ struct bpf_verifier_env {u32 insn_array_map_cnt;/* number of used maps of type BPF_MAP_TYPE_INSN_ARRAY */ u32 id_gen;/* used to generate unique reg IDs */ u32 hidden_subprog_cnt;/* number of hidden subprogs */ +struct bpf_stack_init *stack_inits;/* hidden slot initializations, see bpf_stack_slot_init() */ +u32 stack_init_cnt;int exception_callback_subprog;bool explore_alu_limits;bool allow_ptr_leaks;
@@ -2174,6 +2184,8 @@ int bpf_check_attach_btf_id_multi(struct btf *btf, struct bpf_prog *prog, u32 bt/* Functions in fixups.c, called from bpf_check() */ void bpf_clear_insn_aux_data (struct bpf_verifier_env *env,int start,int len); +int bpf_reserve_stack_slots (struct bpf_verifier_env *env,int subprog,int nr,s16 *off); +int bpf_stack_slot_init (struct bpf_verifier_env *env,int subprog,s16 off,s32 imm);int bpf_remove_fastcall_spills_fills (struct bpf_verifier_env *env);int bpf_optimize_bpf_loop (struct bpf_verifier_env *env);void bpf_opt_hard_wire_dead_code_branches (struct bpf_verifier_env *env);
kernel/bpf/fixups.c +177 -88
368 lines not on this page · raw message
kernel/bpf/verifier.c +1 -0
@@ -25732,6 +25732,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,for (i =0 ; i < env->func_set_cnt; i++)kfree (env->func_sets[i]);kfree (env->func_sets); +kvfree (env->stack_inits);bpf_diag_free (env);kvfree (env->idoms);kvfree (env);
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 8/29 bpf: Add flags and release kfuncs to the dtor registry
In reply to Kumar Kartikeya DwivediThe BTF destructor registry maps a type to the void (*)(void *) function that destroys a kptr of that type when a map element is freed. This is its only use.
An unwinder that releases the references a program holds when it throws needs the same table, with two additions:
- An entry must say whether the unwinder may use it.
- An entry must name the kfunc that a program calls to release such a reference. The verifier uses this to decide whether a destructor may run at a given point: if the program could call the release kfunc there, running the equivalent destructor is allowed.
Add a flags field to struct btf_id_dtor_kfunc:
- BPF_DTOR_KPTR keeps today's use. btf_find_dtor_kfunc() takes the flags that an entry must have, so the kptr lookup in btf_parse_kptr() sees the same entries as before.
- BPF_DTOR_CLEANUP marks an entry that the unwinder may use. Such an entry carries release_btf_id: the KF_RELEASE kfunc of the type, or the KF_ITER_DESTROY kfunc of an iterator type.
btf_find_dtor() returns the whole entry for the unwinder. For a type of a module BTF it also searches the base BTF, and falls back to a vmlinux struct of the same name.
Registration rejects an entry without flags, a cleanup entry without a release kfunc, and a release kfunc that is not registered with KF_RELEASE or KF_ITER_DESTROY in a kfunc id set of the same BTF. The check runs at registration time, so a module or initcall must register its kfunc id sets before its destructors. Every current registration already does.
Mark the existing entries BPF_DTOR_KPTR. Also mark them BPF_DTOR_CLEANUP, with their release kfunc, where the type has one: task_struct, cgroup, bpf_cpumask, bpf_crypto_ctx, bpf_ksock, the sk_buff of a qdisc program, the two test types in test_run.c, and bpf_testmod_ctx.
--- include/linux/btf.h | 23 +++- kernel/bpf/btf.c | 101 ++++++++++++++++-- kernel/bpf/cpumask.c | 7 +- kernel/bpf/crypto.c | 7 +- kernel/bpf/helpers.c | 14 ++- net/bpf/test_run.c | 14 ++- net/core/bpf_ksock.c | 3 + net/sched/bpf_qdisc.c | 10 +- .../selftests/bpf/test_kmods/bpf_testmod.c | 5 +- 9 files changed, 155 insertions(+), 29 deletions(-)
include/linux/btf.h +19 -4
@@ -141,9 +141,19 @@ struct btf_kfunc_id_set {btf_kfunc_filter_t filter; }; +/* +* Flags for struct btf_id_dtor_kfunc::flags: BPF_DTOR_KPTR destroys the kptr fields of a map +* element when it is freed, BPF_DTOR_CLEANUP may release a reference held by a program that +* is being unwound. +*/ +#define BPF_DTOR_KPTR (1U << 0) +#define BPF_DTOR_CLEANUP (1U << 1) +struct btf_id_dtor_kfunc { -u32 btf_id; -u32 kfunc_btf_id; +u32 btf_id;/* type the destructor applies to */ +u32 kfunc_btf_id;/* BTF id of the void (*)(void *) destructor */ +u32 release_btf_id;/* KF_RELEASE kfunc of the type, required for BPF_DTOR_CLEANUP */ +u32 flags; };struct btf_struct_meta {
@@ -627,7 +637,8 @@ u32 *btf_kfunc_is_modify_return(const struct btf *btf, u32 kfunc_btf_id,int register_btf_kfunc_id_set (enum bpf_prog_type prog_type,const struct btf_kfunc_id_set *s);int register_btf_fmodret_id_set (const struct btf_kfunc_id_set *kset); -s32 btf_find_dtor_kfunc (struct btf *btf,u32 btf_id); +const struct btf_id_dtor_kfunc *btf_find_dtor (struct btf **btfp,u32 btf_id); +s32 btf_find_dtor_kfunc (struct btf *btf,u32 btf_id,u32 flags);int register_btf_id_dtor_kfuncs (const struct btf_id_dtor_kfunc *dtors,u32 add_cnt,struct module *owner);struct btf_struct_meta *btf_find_struct_meta (const struct btf *btf,u32 btf_id);
@@ -718,7 +729,11 @@ static inline int register_btf_kfunc_id_set(enum bpf_prog_type prog_type, {return 0 ; } -static inline s32 btf_find_dtor_kfunc (struct btf *btf,u32 btf_id) +static inline const struct btf_id_dtor_kfunc *btf_find_dtor (struct btf **btfp,u32 btf_id) +{ +return NULL ; +} +static inline s32 btf_find_dtor_kfunc (struct btf *btf,u32 btf_id,u32 flags) {return -ENOENT ; }
kernel/bpf/btf.c +92 -9
136 lines not on this page · raw message
kernel/bpf/cpumask.c +5 -2
@@ -511,14 +511,17 @@ static const struct btf_kfunc_id_set cpumask_kfunc_set = {BTF_ID_LIST (cpumask_dtor_ids) BTF_ID(struct, bpf_cpumask)BTF_ID (func, bpf_cpumask_release_dtor) +BTF_ID (func, bpf_cpumask_release) static int__init cpumask_kfunc_init (void) {int ret;const struct btf_id_dtor_kfunc cpumask_dtors[] = { { - .btf_id = cpumask_dtor_ids[0 ], - .kfunc_btf_id = cpumask_dtor_ids[1 ] + .btf_id = cpumask_dtor_ids[0 ], + .kfunc_btf_id = cpumask_dtor_ids[1 ], + .release_btf_id = cpumask_dtor_ids[2 ], + .flags =BPF_DTOR_KPTR |BPF_DTOR_CLEANUP , }, };
kernel/bpf/crypto.c +5 -2
@@ -336,14 +336,17 @@ static const struct btf_kfunc_id_set crypt_kfunc_set = {BTF_ID_LIST (bpf_crypto_dtor_ids) BTF_ID(struct, bpf_crypto_ctx)BTF_ID (func, bpf_crypto_ctx_release_dtor) +BTF_ID (func, bpf_crypto_ctx_release) static int__init crypto_kfunc_init (void) {int ret;const struct btf_id_dtor_kfunc bpf_crypto_dtors[] = { { - .btf_id = bpf_crypto_dtor_ids[0 ], - .kfunc_btf_id = bpf_crypto_dtor_ids[1 ] + .btf_id = bpf_crypto_dtor_ids[0 ], + .kfunc_btf_id = bpf_crypto_dtor_ids[1 ], + .release_btf_id = bpf_crypto_dtor_ids[2 ], + .flags =BPF_DTOR_KPTR |BPF_DTOR_CLEANUP , }, };
kernel/bpf/helpers.c +10 -4
@@ -5317,9 +5317,11 @@ static const struct btf_kfunc_id_set user_mem_kfunc_set = { BTF_ID_LIST(generic_dtor_ids) BTF_ID(struct, task_struct) BTF_ID(func, bpf_task_release_dtor) +BTF_ID(func, bpf_task_release) #ifdef CONFIG_CGROUPS BTF_ID(struct, cgroup) BTF_ID(func, bpf_cgroup_release_dtor) +BTF_ID(func, bpf_cgroup_release) #endif BTF_KFUNCS_START(common_btf_ids)
@@ -5429,13 +5431,17 @@ static int __init kfunc_init(void)int ret;const struct btf_id_dtor_kfunc generic_dtors[] = { { - .btf_id = generic_dtor_ids[0 ], - .kfunc_btf_id = generic_dtor_ids[1 ] + .btf_id = generic_dtor_ids[0 ], + .kfunc_btf_id = generic_dtor_ids[1 ], + .release_btf_id = generic_dtor_ids[2 ], + .flags =BPF_DTOR_KPTR |BPF_DTOR_CLEANUP , }, #ifdef CONFIG_CGROUPS { - .btf_id = generic_dtor_ids[2 ], - .kfunc_btf_id = generic_dtor_ids[3 ] + .btf_id = generic_dtor_ids[3 ], + .kfunc_btf_id = generic_dtor_ids[4 ], + .release_btf_id = generic_dtor_ids[5 ], + .flags =BPF_DTOR_KPTR |BPF_DTOR_CLEANUP , }, #endif };
net/bpf/test_run.c +10 -4
@@ -1888,19 +1888,25 @@ static const struct btf_kfunc_id_set bpf_prog_test_kfunc_set = {BTF_ID_LIST (bpf_prog_test_dtor_kfunc_ids) BTF_ID(struct, prog_test_ref_kfunc)BTF_ID (func, bpf_kfunc_call_test_release_dtor) +BTF_ID (func, bpf_kfunc_call_test_release)BTF_ID (struct, prog_test_member)BTF_ID (func, bpf_kfunc_call_memb_release_dtor) +BTF_ID (func, bpf_kfunc_call_memb_release) static int__init bpf_prog_test_run_init (void) {const struct btf_id_dtor_kfunc bpf_prog_test_dtor_kfunc[] = { { - .btf_id = bpf_prog_test_dtor_kfunc_ids[0 ], - .kfunc_btf_id = bpf_prog_test_dtor_kfunc_ids[1 ] + .btf_id = bpf_prog_test_dtor_kfunc_ids[0 ], + .kfunc_btf_id = bpf_prog_test_dtor_kfunc_ids[1 ], + .release_btf_id = bpf_prog_test_dtor_kfunc_ids[2 ], + .flags =BPF_DTOR_KPTR |BPF_DTOR_CLEANUP , }, { - .btf_id = bpf_prog_test_dtor_kfunc_ids[2 ], - .kfunc_btf_id = bpf_prog_test_dtor_kfunc_ids[3 ], + .btf_id = bpf_prog_test_dtor_kfunc_ids[3 ], + .kfunc_btf_id = bpf_prog_test_dtor_kfunc_ids[4 ], + .release_btf_id = bpf_prog_test_dtor_kfunc_ids[5 ], + .flags =BPF_DTOR_KPTR |BPF_DTOR_CLEANUP , }, };int ret;
net/core/bpf_ksock.c +3 -0
@@ -366,6 +366,7 @@ static const struct btf_kfunc_id_set ksock_kfunc_set = {BTF_ID_LIST (bpf_ksock_dtor_ids) BTF_ID(struct, bpf_ksock)BTF_ID (func, bpf_ksock_release_dtor) +BTF_ID (func, bpf_ksock_release) static int__init bpf_ksock_kfunc_init (void) {
@@ -374,6 +375,8 @@ static int __init bpf_ksock_kfunc_init(void) { .btf_id = bpf_ksock_dtor_ids[0], .kfunc_btf_id = bpf_ksock_dtor_ids[1], + .release_btf_id = bpf_ksock_dtor_ids[2], + .flags = BPF_DTOR_KPTR | BPF_DTOR_CLEANUP, }, };
net/sched/bpf_qdisc.c +7 -3
@@ -453,15 +453,19 @@ static struct bpf_struct_ops bpf_Qdisc_ops = { .owner = THIS_MODULE, }; -BTF_ID_LIST_SINGLE(bpf_sk_buff_dtor_ids, func, bpf_kfree_skb_dtor) +BTF_ID_LIST (bpf_sk_buff_dtor_ids) +BTF_ID (func ,bpf_kfree_skb_dtor ) +BTF_ID (func, bpf_kfree_skb) static int__init bpf_qdisc_kfunc_init (void) {int ret;const struct btf_id_dtor_kfunc skb_kfunc_dtors[] = { { - .btf_id = bpf_sk_buff_ids[0 ], - .kfunc_btf_id = bpf_sk_buff_dtor_ids[0 ] + .btf_id = bpf_sk_buff_ids[0 ], + .kfunc_btf_id = bpf_sk_buff_dtor_ids[0 ], + .release_btf_id = bpf_sk_buff_dtor_ids[1 ], + .flags =BPF_DTOR_KPTR |BPF_DTOR_CLEANUP , }, };
tools/testing/selftests/bpf/test_kmods/bpf_testmod.c +4 -1
@@ -1084,6 +1084,7 @@ BTF_KFUNCS_END(bpf_testmod_common_kfunc_ids)BTF_ID_LIST (bpf_testmod_dtor_ids) BTF_ID(struct, bpf_testmod_ctx)BTF_ID (func, bpf_testmod_ctx_release_dtor) +BTF_ID (func, bpf_testmod_ctx_release) static const struct btf_kfunc_id_set bpf_testmod_common_kfunc_set = { .owner =THIS_MODULE ,
@@ -2622,7 +2623,9 @@ static int bpf_testmod_init(void)const struct btf_id_dtor_kfunc bpf_testmod_dtors[] = { { .btf_id = bpf_testmod_dtor_ids[0 ], - .kfunc_btf_id = bpf_testmod_dtor_ids[1 ] + .kfunc_btf_id = bpf_testmod_dtor_ids[1 ], + .release_btf_id = bpf_testmod_dtor_ids[2 ], + .flags =BPF_DTOR_KPTR |BPF_DTOR_CLEANUP , }, };void **tramp;
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 9/29 bpf: Register cleanup destructors for more types
In reply to Kumar Kartikeya DwivediRegister a BPF_DTOR_CLEANUP entry for every referenced type that a program can hold but that has no kptr destructor, so that an unwinder can release the references a program holds when it throws. For an open-coded iterator, the type is the iterator struct and the release kfunc is its KF_ITER_DESTROY kfunc.
The release kfuncs take a typed pointer, and calling one through a void (*)(void *) pointer is a kCFI violation. Each entry therefore uses a void (*)(void *) wrapper next to the kfunc, as the kptr destructors do.
In kernel/bpf, these are the num, task, task_vma, css, css_task, bits, kmem_cache and dmabuf iterators, and bpf_key. They are registered from kfunc_init() with one list of ids per entry: the groups under CONFIG_CGROUPS, CONFIG_KEYS and CONFIG_DMA_SHARED_BUFFER would otherwise shift the indices of a single list. The common kfunc set contains the iterator destroy kfuncs, so it is now registered before the destructors.
Outside kernel/bpf, these are:
- xfrm_state and hid_bpf_ctx;
- nf_conn, released by bpf_ct_release();
- nf_conn___init, an entry that was allocated but not inserted. Its destructor frees the entry, as bpf_ct_insert_entry() does when the insert fails. bpf_ct_insert_entry() is the only kfunc that takes such an entry, so it is the release kfunc;
- mem_cgroup, released by bpf_put_mem_cgroup();
- file, released by bpf_put_file();
- bpf_ws_lock, the wakeup sources read lock. Its handle is the SRCU index plus one, never a pointer;
- the DSQ iterator of sched_ext;
- cpumask, for the masks that sched_ext hands out. scx_bpf_put_cpumask() and scx_bpf_put_idle_cpumask() both do nothing, so one entry covers both.
bpf_testmod gets entries for its two iterators.
--- drivers/base/power/wakeup.c | 25 +++++- drivers/hid/bpf/hid_bpf_dispatch.c | 23 +++++ drivers/hid/bpf/hid_bpf_dispatch.h | 1 + fs/bpf_fs_kfuncs.c | 27 +++++- include/linux/bpf.h | 14 ++++ include/net/netfilter/nf_conntrack_bpf.h | 1 + include/net/xfrm.h | 1 + kernel/bpf/bpf_iter.c | 6 ++ kernel/bpf/cgroup_iter.c | 6 ++ kernel/bpf/dmabuf_iter.c | 6 ++ kernel/bpf/helpers.c | 83 ++++++++++++++----- kernel/bpf/kmem_cache_iter.c | 6 ++ kernel/bpf/task_iter.c | 18 ++++ kernel/sched/ext/ext.c | 41 +++++++++ mm/bpf_memcontrol.c | 28 ++++++- net/netfilter/nf_conntrack_bpf.c | 38 +++++++++ net/xfrm/xfrm_state_bpf.c | 26 +++++- .../selftests/bpf/test_kmods/bpf_testmod.c | 30 +++++++ 18 files changed, 350 insertions(+), 30 deletions(-)
drivers/base/power/wakeup.c +24 -1
@@ -1170,6 +1170,7 @@ static const struct file_operations wakeup_sources_stats_fops = {#ifdef CONFIG_BPF_SYSCALL #include <linux/btf.h> +#include <linux/btf_ids.h> __bpf_kfunc_start_defs ();
@@ -1202,6 +1203,12 @@ __bpf_kfunc void bpf_wakeup_sources_read_unlock(struct bpf_ws_lock *lock)wakeup_sources_read_unlock ((int )(long )lock -1 ); } +__bpf_kfunc void bpf_wakeup_sources_read_unlock_dtor(void *lock) +{ +bpf_wakeup_sources_read_unlock (lock); +} +CFI_NOSEAL (bpf_wakeup_sources_read_unlock_dtor); +/** * bpf_wakeup_sources_get_head - Get the head of the wakeup sources list *
@@ -1224,9 +1231,25 @@ static const struct btf_kfunc_id_set wakeup_source_kfunc_set = { .set = &wakeup_source_kfunc_ids, }; +BTF_ID_LIST (wakeup_source_dtor_ids) +BTF_ID(struct, bpf_ws_lock) +BTF_ID (func, bpf_wakeup_sources_read_unlock_dtor) +BTF_ID (func, bpf_wakeup_sources_read_unlock) + static void__init wakeup_sources_bpf_init (void) { -if (register_btf_kfunc_id_set (BPF_PROG_TYPE_SYSCALL , &wakeup_source_kfunc_set)) +const struct btf_id_dtor_kfunc wakeup_source_dtors[] = { + { + .btf_id = wakeup_source_dtor_ids[0 ], + .kfunc_btf_id = wakeup_source_dtor_ids[1 ], + .release_btf_id = wakeup_source_dtor_ids[2 ], + .flags =BPF_DTOR_CLEANUP , + }, + }; + +if (register_btf_kfunc_id_set (BPF_PROG_TYPE_SYSCALL , &wakeup_source_kfunc_set) || +register_btf_id_dtor_kfuncs (wakeup_source_dtors,ARRAY_SIZE (wakeup_source_dtors), +THIS_MODULE ))pm_pr_dbg ("Wakeup: failed to register BTF kfuncs\n" ); }#else
drivers/hid/bpf/hid_bpf_dispatch.c +23 -0
@@ -357,6 +357,12 @@ hid_bpf_release_context(struct hid_bpf_ctx *ctx)hid_put_device (hid); } +__bpf_kfunc void hid_bpf_release_context_dtor(void *ctx) +{ +hid_bpf_release_context (ctx); +} +CFI_NOSEAL (hid_bpf_release_context_dtor); +static int __hid_bpf_hw_check_params (struct hid_bpf_ctx *ctx,__u8 *buf,size_t *buf__sz,enum hid_report_type rtype,bool hw_request)
@@ -659,8 +665,21 @@ int hid_bpf_device_init(struct hid_device *hdev) }EXPORT_SYMBOL_GPL (hid_bpf_device_init); +BTF_ID_LIST (hid_bpf_dtor_ids) +BTF_ID(struct, hid_bpf_ctx) +BTF_ID (func, hid_bpf_release_context_dtor) +BTF_ID (func, hid_bpf_release_context) + static int__init hid_bpf_init (void) { +const struct btf_id_dtor_kfunc hid_bpf_dtors[] = { + { + .btf_id = hid_bpf_dtor_ids[0 ], + .kfunc_btf_id = hid_bpf_dtor_ids[1 ], + .release_btf_id = hid_bpf_dtor_ids[2 ], + .flags =BPF_DTOR_CLEANUP , + }, + };int err;/* Note: if we exit with an error any time here, we would entirely break HID, which
@@ -681,6 +700,10 @@ static int __init hid_bpf_init(void)return 0 ; } + err =register_btf_id_dtor_kfuncs (hid_bpf_dtors,ARRAY_SIZE (hid_bpf_dtors),THIS_MODULE ); +if (err) +pr_warn ("error while registering HID BPF destructors: %d\n" , err); +return 0 ; }
drivers/hid/bpf/hid_bpf_dispatch.h +1 -0
@@ -16,6 +16,7 @@ void hid_put_device(struct hid_device *hid);int hid_bpf_allocate_event_data (struct hid_device *hdev);void __hid_bpf_ops_destroy_device (struct hid_device *hdev);int hid_bpf_reconnect (struct hid_device *hdev); +void hid_bpf_release_context_dtor (void *ctx);struct bpf_prog ;
fs/bpf_fs_kfuncs.c +23 -4
@@ -63,6 +63,12 @@ __bpf_kfunc void bpf_put_file(struct file *file)fput (file); } +__bpf_kfunc void bpf_put_file_dtor(void *file) +{ +bpf_put_file (file); +} +CFI_NOSEAL (bpf_put_file_dtor); +/** * bpf_path_d_path - resolve the pathname for the supplied path * @path: path to resolve the pathname for
@@ -584,15 +590,28 @@ static const struct btf_kfunc_id_set bpf_fs_kfunc_set = { .filter = bpf_fs_kfuncs_filter, }; +BTF_ID_LIST (bpf_fs_dtor_ids) +BTF_ID(struct, file) +BTF_ID (func, bpf_put_file_dtor) +BTF_ID (func, bpf_put_file) + static int__init bpf_fs_kfuncs_init (void) { +const struct btf_id_dtor_kfunc bpf_fs_dtors[] = { + { + .btf_id = bpf_fs_dtor_ids[0 ], + .kfunc_btf_id = bpf_fs_dtor_ids[1 ], + .release_btf_id = bpf_fs_dtor_ids[2 ], + .flags =BPF_DTOR_CLEANUP , + }, + };int ret; ret =register_btf_kfunc_id_set (BPF_PROG_TYPE_LSM , &bpf_fs_kfunc_set); -if (ret || !IS_ENABLED (CONFIG_BINFMT_MISC_BPF )) -return ret; -return register_btf_kfunc_id_set (BPF_PROG_TYPE_STRUCT_OPS , - &bpf_fs_kfunc_set); +if (!ret &&IS_ENABLED (CONFIG_BINFMT_MISC_BPF )) + ret =register_btf_kfunc_id_set (BPF_PROG_TYPE_STRUCT_OPS , &bpf_fs_kfunc_set); +return ret ?:register_btf_id_dtor_kfuncs (bpf_fs_dtors,ARRAY_SIZE (bpf_fs_dtors), +THIS_MODULE ); }late_initcall (bpf_fs_kfuncs_init);
include/linux/bpf.h +14 -0
@@ -3301,6 +3301,19 @@ int bpf_iter_map_fill_link_info(const struct bpf_iter_aux_info *aux,int bpf_iter_map_check_value_write (const struct bpf_prog *prog,const struct bpf_map *map); +/* +* void (*)(void *) wrappers of the open-coded iterator destroy kfuncs, which the BTF +* destructor registry refers to. +*/ +void bpf_iter_num_destroy_dtor (void *it); +void bpf_iter_task_destroy_dtor (void *it); +void bpf_iter_task_vma_destroy_dtor (void *it); +void bpf_iter_css_destroy_dtor (void *it); +void bpf_iter_css_task_destroy_dtor (void *it); +void bpf_iter_bits_destroy_dtor (void *it); +void bpf_iter_kmem_cache_destroy_dtor (void *it); +void bpf_iter_dmabuf_destroy_dtor (void *it); +int map_set_for_each_callback_args (struct bpf_verifier_env *env,struct bpf_func_state *caller,struct bpf_func_state *callee);
@@ -4105,6 +4118,7 @@ struct bpf_key *bpf_lookup_system_key(u64 id);struct bpf_key *bpf_lookup_keyring (void );bool bpf_keyring_enforced (void );void bpf_key_put (struct bpf_key *bkey); +void bpf_key_put_dtor (void *bkey);int bpf_verify_pkcs7_signature (const struct bpf_dynptr *data_p,const struct bpf_dynptr *sig_p,struct bpf_key *trusted_keyring);
include/net/netfilter/nf_conntrack_bpf.h +1 -0
@@ -15,6 +15,7 @@ struct nf_conn___init {extern int register_nf_conntrack_bpf (void );extern void cleanup_nf_conntrack_bpf (void ); +void bpf_ct_release_dtor (void *nfct);#else
include/net/xfrm.h +1 -0
@@ -2378,6 +2378,7 @@ static inline int register_xfrm_interface_bpf(void)#if IS_ENABLED (CONFIG_DEBUG_INFO_BTF )int register_xfrm_state_bpf (void ); +void bpf_xdp_xfrm_state_release_dtor (void *x);#else static inline int register_xfrm_state_bpf (void ) {
kernel/bpf/bpf_iter.c +6 -0
@@ -846,4 +846,10 @@ __bpf_kfunc void bpf_iter_num_destroy(struct bpf_iter_num *it)/* no-op */ } +__bpf_kfunc void bpf_iter_num_destroy_dtor(void *it) +{ +bpf_iter_num_destroy (it); +} +CFI_NOSEAL (bpf_iter_num_destroy_dtor); +__bpf_kfunc_end_defs ();
kernel/bpf/cgroup_iter.c +6 -0
@@ -372,4 +372,10 @@ __bpf_kfunc void bpf_iter_css_destroy(struct bpf_iter_css *it) { } +__bpf_kfunc void bpf_iter_css_destroy_dtor(void *it) +{ +bpf_iter_css_destroy (it); +} +CFI_NOSEAL (bpf_iter_css_destroy_dtor); +__bpf_kfunc_end_defs ();
kernel/bpf/dmabuf_iter.c +6 -0
@@ -189,4 +189,10 @@ __bpf_kfunc void bpf_iter_dmabuf_destroy(struct bpf_iter_dmabuf *it)dma_buf_put (kit->dmabuf); } +__bpf_kfunc void bpf_iter_dmabuf_destroy_dtor(void *it) +{ +bpf_iter_dmabuf_destroy (it); +} +CFI_NOSEAL (bpf_iter_dmabuf_destroy_dtor); +__bpf_kfunc_end_defs ();
kernel/bpf/helpers.c +61 -22
118 lines not on this page · raw message
kernel/bpf/kmem_cache_iter.c +6 -0
@@ -100,6 +100,12 @@ __bpf_kfunc void bpf_iter_kmem_cache_destroy(struct bpf_iter_kmem_cache *it)kmem_cache_destroy (s); } +__bpf_kfunc void bpf_iter_kmem_cache_destroy_dtor(void *it) +{ +bpf_iter_kmem_cache_destroy (it); +} +CFI_NOSEAL (bpf_iter_kmem_cache_destroy_dtor); +__bpf_kfunc_end_defs ();struct bpf_iter__kmem_cache {
kernel/bpf/task_iter.c +18 -0
@@ -1044,6 +1044,12 @@ __bpf_kfunc void bpf_iter_task_vma_destroy(struct bpf_iter_task_vma *it) } } +__bpf_kfunc void bpf_iter_task_vma_destroy_dtor(void *it) +{ +bpf_iter_task_vma_destroy (it); +} +CFI_NOSEAL (bpf_iter_task_vma_destroy_dtor); +__bpf_kfunc_end_defs ();#ifdef CONFIG_CGROUPS
@@ -1102,6 +1108,12 @@ __bpf_kfunc void bpf_iter_css_task_destroy(struct bpf_iter_css_task *it)bpf_mem_free (&bpf_global_ma, kit->css_it); } +__bpf_kfunc void bpf_iter_css_task_destroy_dtor(void *it) +{ +bpf_iter_css_task_destroy (it); +} +CFI_NOSEAL (bpf_iter_css_task_destroy_dtor); +__bpf_kfunc_end_defs ();#endif /* CONFIG_CGROUPS */
@@ -1192,6 +1204,12 @@ __bpf_kfunc void bpf_iter_task_destroy(struct bpf_iter_task *it) { } +__bpf_kfunc void bpf_iter_task_destroy_dtor(void *it) +{ +bpf_iter_task_destroy (it); +} +CFI_NOSEAL (bpf_iter_task_destroy_dtor); +__bpf_kfunc_end_defs (); DEFINE_PER_CPU(struct mmap_unlock_irq_work, mmap_unlock_work);
kernel/sched/ext/ext.c +41 -0
67 lines not on this page · raw message
mm/bpf_memcontrol.c +27 -1
@@ -82,6 +82,12 @@ __bpf_kfunc void bpf_put_mem_cgroup(struct mem_cgroup *memcg)css_put (&memcg->css); } +__bpf_kfunc void bpf_put_mem_cgroup_dtor(void *memcg) +{ +bpf_put_mem_cgroup (memcg); +} +CFI_NOSEAL (bpf_put_mem_cgroup_dtor); +/** * bpf_mem_cgroup_vm_events - Read memory cgroup's vm event counter * @memcg: memory cgroup
@@ -235,8 +241,21 @@ static const struct btf_kfunc_id_set bpf_memcontrol_reclaim_kfunc_set = { .set = &bpf_memcontrol_reclaim_kfuncs, }; +BTF_ID_LIST (bpf_memcontrol_dtor_ids) +BTF_ID(struct, mem_cgroup) +BTF_ID (func, bpf_put_mem_cgroup_dtor) +BTF_ID (func, bpf_put_mem_cgroup) + static int__init bpf_memcontrol_init (void) { +const struct btf_id_dtor_kfunc bpf_memcontrol_dtors[] = { + { + .btf_id = bpf_memcontrol_dtor_ids[0 ], + .kfunc_btf_id = bpf_memcontrol_dtor_ids[1 ], + .release_btf_id = bpf_memcontrol_dtor_ids[2 ], + .flags =BPF_DTOR_CLEANUP , + }, + };int err; err = register_btf_kfunc_id_set(BPF_PROG_TYPE_UNSPEC,
@@ -248,8 +267,15 @@ static int __init bpf_memcontrol_init(void) err =register_btf_kfunc_id_set (BPF_PROG_TYPE_SYSCALL , &bpf_memcontrol_reclaim_kfunc_set); -if (err) +if (err) {pr_warn ("error registering bpf reclaim kfuncs: %d\n" , err); +return err; + } + + err =register_btf_id_dtor_kfuncs (bpf_memcontrol_dtors,ARRAY_SIZE (bpf_memcontrol_dtors), +THIS_MODULE ); +if (err) +pr_warn ("error registering bpf memcontrol destructors: %d\n" , err);return err; }
net/netfilter/nf_conntrack_bpf.c +38 -0
@@ -457,6 +457,22 @@ __bpf_kfunc void bpf_ct_release(struct nf_conn *nfct)nf_ct_put (nfct); } +__bpf_kfunc void bpf_ct_release_dtor(void *nfct) +{ +bpf_ct_release (nfct); +} +CFI_NOSEAL (bpf_ct_release_dtor); + +/* +* An entry that was allocated and not inserted has no reference to put: it is +* freed, as bpf_ct_insert_entry() does with one it cannot insert. +*/ +__bpf_kfunc void bpf_ct_free_dtor(void *nfct) +{ +nf_conntrack_free (nfct); +} +CFI_NOSEAL (bpf_ct_free_dtor); +/* bpf_ct_set_timeout - Set timeout of allocated nf_conn * * Sets the default timeout of newly allocated nf_conn before insertion.
@@ -537,12 +553,34 @@ static const struct btf_kfunc_id_set nf_conntrack_kfunc_set = { .set = &nf_ct_kfunc_set, }; +BTF_ID_LIST (nf_ct_dtor_ids) +BTF_ID (func ,bpf_ct_release_dtor ) +BTF_ID (func, bpf_ct_release) +BTF_ID (func, bpf_ct_free_dtor) +BTF_ID (func, bpf_ct_insert_entry) + intregister_nf_conntrack_bpf (void) { +/* The only kfunc that takes an allocated entry off a program is bpf_ct_insert_entry() */ +const struct btf_id_dtor_kfunc nf_ct_dtors[] = { + { + .btf_id = btf_nf_conn_ids[0 ], + .kfunc_btf_id = nf_ct_dtor_ids[0 ], + .release_btf_id = nf_ct_dtor_ids[1 ], + .flags =BPF_DTOR_CLEANUP , + }, + { + .btf_id = btf_nf_conn_ids[1 ], + .kfunc_btf_id = nf_ct_dtor_ids[2 ], + .release_btf_id = nf_ct_dtor_ids[3 ], + .flags =BPF_DTOR_CLEANUP , + }, + };int ret; ret =register_btf_kfunc_id_set (BPF_PROG_TYPE_XDP , &nf_conntrack_kfunc_set); ret = ret ?:register_btf_kfunc_id_set (BPF_PROG_TYPE_SCHED_CLS , &nf_conntrack_kfunc_set); + ret = ret ?:register_btf_id_dtor_kfuncs (nf_ct_dtors,ARRAY_SIZE (nf_ct_dtors),THIS_MODULE );if (!ret) {mutex_lock (&nf_conn_btf_access_lock); nfct_btf_struct_access = _nf_conntrack_btf_struct_access;
net/xfrm/xfrm_state_bpf.c +24 -2
@@ -115,6 +115,12 @@ __bpf_kfunc void bpf_xdp_xfrm_state_release(struct xfrm_state *x)xfrm_state_put (x); } +__bpf_kfunc void bpf_xdp_xfrm_state_release_dtor(void *x) +{ +bpf_xdp_xfrm_state_release (x); +} +CFI_NOSEAL (bpf_xdp_xfrm_state_release_dtor); +__bpf_kfunc_end_defs ();BTF_KFUNCS_START (xfrm_state_kfunc_set)
@@ -127,8 +133,24 @@ static const struct btf_kfunc_id_set xfrm_state_xdp_kfunc_set = { .set = &xfrm_state_kfunc_set, }; +BTF_ID_LIST (xfrm_state_dtor_ids) +BTF_ID(struct, xfrm_state) +BTF_ID (func, bpf_xdp_xfrm_state_release_dtor) +BTF_ID (func, bpf_xdp_xfrm_state_release) + int__init register_xfrm_state_bpf (void) { -return register_btf_kfunc_id_set (BPF_PROG_TYPE_XDP , - &xfrm_state_xdp_kfunc_set); +const struct btf_id_dtor_kfunc xfrm_state_dtors[] = { + { + .btf_id = xfrm_state_dtor_ids[0 ], + .kfunc_btf_id = xfrm_state_dtor_ids[1 ], + .release_btf_id = xfrm_state_dtor_ids[2 ], + .flags =BPF_DTOR_CLEANUP , + }, + }; +int ret; + + ret =register_btf_kfunc_id_set (BPF_PROG_TYPE_XDP , &xfrm_state_xdp_kfunc_set); +return ret ?:register_btf_id_dtor_kfuncs (xfrm_state_dtors,ARRAY_SIZE (xfrm_state_dtors), +THIS_MODULE ); }
tools/testing/selftests/bpf/test_kmods/bpf_testmod.c +30 -0
@@ -281,6 +281,12 @@ __bpf_kfunc void bpf_iter_testmod_seq_destroy(struct bpf_iter_testmod_seq *it) it->cnt =0 ; } +__bpf_kfunc void bpf_iter_testmod_seq_destroy_dtor(void *it) +{ +bpf_iter_testmod_seq_destroy (it); +} +CFI_NOSEAL (bpf_iter_testmod_seq_destroy_dtor); + __bpf_kfunc int bpf_iter_testmod_current_new(struct bpf_iter_testmod_current *it) { it->remaining =2 ;
@@ -302,6 +308,12 @@ __bpf_kfunc void bpf_iter_testmod_current_destroy(struct bpf_iter_testmod_curren it->remaining =0 ; } +__bpf_kfunc void bpf_iter_testmod_current_destroy_dtor(void *it) +{ +bpf_iter_testmod_current_destroy (it); +} +CFI_NOSEAL (bpf_iter_testmod_current_destroy_dtor); + __bpf_kfunc void bpf_kfunc_common_test(void) { }
@@ -1085,6 +1097,12 @@ BTF_ID_LIST(bpf_testmod_dtor_ids) BTF_ID(struct, bpf_testmod_ctx) BTF_ID(func, bpf_testmod_ctx_release_dtor) BTF_ID(func, bpf_testmod_ctx_release) +BTF_ID(struct, bpf_iter_testmod_seq) +BTF_ID(func, bpf_iter_testmod_seq_destroy_dtor) +BTF_ID(func, bpf_iter_testmod_seq_destroy) +BTF_ID(struct, bpf_iter_testmod_current) +BTF_ID(func, bpf_iter_testmod_current_destroy_dtor) +BTF_ID(func, bpf_iter_testmod_current_destroy)static const struct btf_kfunc_id_set bpf_testmod_common_kfunc_set = { .owner =THIS_MODULE ,
@@ -2627,6 +2645,18 @@ static int bpf_testmod_init(void) .release_btf_id = bpf_testmod_dtor_ids[2], .flags = BPF_DTOR_KPTR | BPF_DTOR_CLEANUP, }, + { + .btf_id = bpf_testmod_dtor_ids[3], + .kfunc_btf_id = bpf_testmod_dtor_ids[4], + .release_btf_id = bpf_testmod_dtor_ids[5], + .flags = BPF_DTOR_CLEANUP, + }, + { + .btf_id = bpf_testmod_dtor_ids[6], + .kfunc_btf_id = bpf_testmod_dtor_ids[7], + .release_btf_id = bpf_testmod_dtor_ids[8], + .flags = BPF_DTOR_CLEANUP, + }, };void **tramp;int ret;
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 10/29 bpf: Record where JITed frames keep the BPF stack
In reply to Kumar Kartikeya DwivediThe unwinder will release resources that a frame keeps on its BPF stack. The verifier gives their locations relative to the frame's R10. The stack walk reports the native frame pointer of a frame, and the position of R10 relative to it depends on the architecture and on the stack that the frame uses:
- On x86-64, R10 is the native frame pointer.
- On arm64, R10 is the stack pointer after the frame record, the tail call count pair and the callee-saved pairs are pushed. This is -16 - 16 * ceil(pushed / 2) below x29. Record the distance as frame_fp_off in the program's aux data. x86-64 leaves it 0.
- A frame that uses a private stack keeps its BPF stack in the per-CPU private area, so its R10 is not derived from the frame pointer. The JIT knows that R10 is a fixed distance into the per-CPU private stack of the function. Record the distance as priv_frame_off.
The arm64 JIT sets up R10 only for a program that uses it, but records the distances for every program.
Add bpf_jit_supports_exception_cleanup(), which tells the verifier whether the JIT records these. The other JITs that support exceptions do not record them yet, and keep the rule that bpf_throw() must be reached with no resources held.
--- arch/arm64/net/bpf_jit_comp.c | 16 ++++++++++++++++ arch/x86/net/bpf_jit_comp.c | 7 +++++++ include/linux/bpf.h | 4 ++++ include/linux/filter.h | 1 + kernel/bpf/core.c | 10 ++++++++++ 5 files changed, 38 insertions(+)
arch/arm64/net/bpf_jit_comp.c +16 -0
@@ -1055,6 +1055,16 @@ static int build_prologue(struct jit_ctx *ctx, bool ebpf_from_cbpf)/* Stack must be multiples of 16B */ ctx->stack_size =round_up (prog->aux->stack_depth,16 ); +/* +* Where the exception unwinder finds this frame's BPF stack: the +* private stack, or below the frame record, the tail call count pair +* and the pairs push_callee_regs() pushed. +*/ +if (ctx->priv_sp_used) + prog->aux->priv_frame_off =PRIV_STACK_GUARD_SZ + ctx->stack_size; +else + prog->aux->frame_fp_off =-16 -16 *DIV_ROUND_UP (ctx->nr_used_callee_reg,2 ); +if (ctx->fp_used) {if (ctx->priv_sp_used) {/* Set up private stack pointer */
@@ -3904,6 +3914,12 @@ bool bpf_jit_supports_exceptions(void)return true; } +/* build_prologue() records where each frame keeps its BPF stack. */ +bool bpf_jit_supports_exception_cleanup (void ) +{ +return true; +} +bool bpf_jit_supports_arena (void ) {return true;
arch/x86/net/bpf_jit_comp.c +7 -0
@@ -2425,6 +2425,7 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int * priv_stack_ptr = bpf_prog->aux->priv_stack_ptr;if (priv_stack_ptr) { priv_frame_ptr = priv_stack_ptr +PRIV_STACK_GUARD_SZ +round_up (stack_depth,8 ); + bpf_prog->aux->priv_frame_off =PRIV_STACK_GUARD_SZ +round_up (stack_depth,8 ); stack_depth =0 ; }
@@ -5066,6 +5067,12 @@ bool bpf_jit_supports_exceptions(void)return IS_ENABLED (CONFIG_UNWINDER_ORC ); } +/* On x86-64 R10 is the frame pointer; do_jit() records where a private stack has it. */ +bool bpf_jit_supports_exception_cleanup (void ) +{ +return bpf_jit_supports_exceptions (); +} +bool bpf_jit_supports_private_stack (void ) {return true;
include/linux/bpf.h +4 -0
@@ -2010,6 +2010,10 @@ struct bpf_prog_aux {* by the JIT; consumed by the exception unwinder. */ s16 callee_reg_off[BPF_THROW_NR_CALLEE_SAVED ]; +/* BPF frame pointer relative to the native frame pointer the stack walker reports */ +s16 frame_fp_off; +/* With a private stack, the frame's R10 is this far into this CPU's priv_stack_ptr. */ +u32 priv_frame_off;#ifdef CONFIG_SECURITY void *security;#endif
include/linux/filter.h +1 -0
@@ -1391,6 +1391,7 @@ bool bpf_jit_supports_stack_args(void);bool bpf_jit_supports_arena_args (void );bool bpf_jit_supports_far_kfunc_call (void );bool bpf_jit_supports_exceptions (void ); +bool bpf_jit_supports_exception_cleanup (void );bool bpf_jit_supports_ptr_xchg (void );bool bpf_jit_supports_arena (void );bool bpf_jit_supports_insn (struct bpf_insn *insn,bool in_arena);
kernel/bpf/core.c +10 -0
@@ -3896,6 +3896,16 @@ bool __weak bpf_jit_supports_exceptions(void)return false; } +/* +* Does the JIT record where each frame keeps its BPF stack, see +* bpf_prog_aux::frame_fp_off, so that the unwinder can release what a frame +* holds when bpf_throw() discards it? +*/ +bool __weak bpf_jit_supports_exception_cleanup (void ) +{ +return false; +} +bool __weak bpf_jit_supports_private_stack (void ) {return false;
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 11/29 bpf: Give every reference an owner frame
In reply to Kumar Kartikeya DwivediThe unwinder will release the resources of each frame from that frame's own stack slots. Each reference in the verifier state must therefore belong to exactly one frame. The verifier has no such notion today: the state holds the references, and a frame only knows how many of the references held at its entry are still held.
Record the owner frame in the reference state:
- The frame that acquires a reference owns it.
- When a frame returns, the references it still holds pass to its caller, which is the only frame left that can release them.
- A function that is verified on its own inherits the references of its callers without their frames, see call_func_on_its_own(). No frame of the state owns these inherited references, so their owner is -1. References that the function returns with, beyond the inherited ones, pass to the caller when the call is resumed in bpf_flow_return().
Callbacks release everything they acquire, so nothing changes owner when a callback returns, and the transfer in prepare_func_exit() has nothing to do for them.
RCU read and preemption disabled sections have no reference state. The verifier tracks, per frame, how many of the sections held at its entry are still held, and release_context_state() lowers that count on every unlock. A frame that left a section of its caller therefore looks like a frame that was entered with fewer sections. Also count how many sections each frame released below its entry count, so that the exit of the frame can tell that it returns with fewer sections than it was called with.
--- include/linux/bpf_verifier.h | 13 +++++++++++++ kernel/bpf/verifier.c | 22 ++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-)
include/linux/bpf_verifier.h +13 -0
@@ -327,6 +327,12 @@ struct bpf_reference_state {* is used purely to inform the user of a reference leak. */ int insn_idx; +/* +* The frame that owns the reference: the one that acquired it, or the +* caller a returning frame handed it to. -1 for what the callers of a +* function verified on its own hold, which are not in the state. +*/ +s8 frameno;union {/* For REF_TYPE_PTR */ int parent_id;
@@ -503,6 +509,13 @@ struct bpf_func_state {u32 entry_refs;u32 entry_rcu_locks;u32 entry_preempt_locks; +/* +* How many of the RCU read and preemption disabled sections held when the +* frame was entered it left, or a callee of it did: a frame that returns +* after that is in fewer sections than it was called in. +*/ +u32 rcu_released_below; +u32 preempt_released_below;/* The following fields should be last. See copy_func_state() */ /* The state of the stack. Each element of the array describes BPF_REG_SIZE
kernel/bpf/verifier.c +20 -2
@@ -1545,6 +1545,7 @@ static struct bpf_reference_state *acquire_reference_state(struct bpf_verifier_eif (err)return NULL ; state->refs[new_ofs].insn_idx = insn_idx; + state->refs[new_ofs].frameno = state->curframe;return &state->refs[new_ofs]; }
@@ -1625,7 +1626,11 @@ static void release_reference_state(struct bpf_verifier_state *state, int idx)return ; } -/* bpf_rcu_read_unlock() or bpf_preempt_enable() is done, see release_reference_state() */ +/* +* bpf_rcu_read_unlock() or bpf_preempt_enable() is done, see release_reference_state(). +* A frame that was entered with more sections than are held now has left one of its +* callers', and counts that for its exit to tell. +*/ static void release_context_state (struct bpf_verifier_state *state) {struct bpf_func_state *frame;
@@ -1633,6 +1638,11 @@ static void release_context_state(struct bpf_verifier_state *state)for (fr =0 ; fr <= state->curframe; fr++) { frame = state->frame[fr]; +if (frame->entry_rcu_locks > state->active_rcu_locks) + frame->rcu_released_below += frame->entry_rcu_locks - state->active_rcu_locks; +if (frame->entry_preempt_locks > state->active_preempt_locks) + frame->preempt_released_below += frame->entry_preempt_locks - + state->active_preempt_locks; frame->entry_rcu_locks =min (frame->entry_rcu_locks, state->active_rcu_locks); frame->entry_preempt_locks =min (frame->entry_preempt_locks, state->active_preempt_locks);
@@ -11709,6 +11719,8 @@ static int call_func_on_its_own(struct bpf_verifier_env *env, int subprog, int i entry.refs[i].id =inherited_id (state, entry.refs[i].id);if (entry.refs[i].type ==REF_TYPE_PTR ) entry.refs[i].parent_id =inherited_id (state, entry.refs[i].parent_id); +/* the frames that own what is inherited are not in the state */ + entry.refs[i].frameno =-1 ; } entry.active_irq_id =inherited_id (state, entry.active_irq_id); entry.active_lock_id =inherited_id (state, entry.active_lock_id);
@@ -11878,11 +11890,12 @@ int bpf_flow_return(struct bpf_verifier_env *env, struct bpf_verifier_state *cal reg->parent_id =fresh_id (env, idmap, reg->parent_id); reg->map_uid =fresh_id (env, idmap, reg->map_uid); } -/* those that were held are the first */ +/* those that were held are the first; what the function took is the caller's now */ for (i = nheld; i < caller->acquired_refs; i++) { caller->refs[i].id =fresh_id (env, idmap, caller->refs[i].id);if (caller->refs[i].type ==REF_TYPE_PTR ) caller->refs[i].parent_id =fresh_id (env, idmap, caller->refs[i].parent_id); + caller->refs[i].frameno = caller->curframe; } caller->active_irq_id =fresh_id (env, idmap, caller->active_irq_id); caller->active_lock_id =fresh_id (env, idmap, caller->active_lock_id);
@@ -12688,6 +12701,11 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)return err;invalidate_stack_slices (env, callee->frameno,INT_MIN ,0 ); +/* What the frame holds when it returns is the caller's to release. */ +for (i =0 ; i < state->acquired_refs; i++) +if (state->refs[i].frameno == callee->frameno) + state->refs[i].frameno = caller->frameno; +/* for callbacks like bpf_loop or bpf_for_each_map_elem go back to callsite, * there function call logic would reschedule callback visit. */
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 12/29 bpf: Release resources in hidden slots on bpf_throw()
In reply to Kumar Kartikeya DwivediCurrently bpf_throw() is only allowed where the program holds no resources. The frames between the throw and the main program are discarded without releasing anything, so the verifier rejects a throw that is reached with a reference, a lock, or a section held.
Add the runtime half of releasing the resources of these frames. A frame will keep the handles of the kernel resources it holds in hidden stack slots, which the verifier maintains from the next patch on. A handle is the value that the release takes: a pointer to an object, a ringbuf record or a lock, or the address of an iterator or of saved IRQ flags on the stack.
A frame descriptor lists the hidden slots in the frames of one JITed function:
- Each entry gives the offset of a slot from the frame's R10 and the resource class of the handle in it. An empty slot holds 0.
- A resource class says how to release a handle: the kind of resource, and the destructor or the struct meta of the object.
- RCU read and preemption disabled sections have no handle. A frame counts the sections it entered in one slot per kind.
The descriptors and resource classes of a program live in a pool owned by the main program. Each JITed function refers to its descriptor by index, and identical descriptors are shared.
bpf_throw() releases the resources in the slots of each frame before it walks the stack for the exception callback. The release order is the same for all frames, so the frames are walked once per phase:
1. locks and IRQ flags;
2. objects whose destructor needs RCU protection;
3. the sections, using counts that the first walk summed over the
frames;
4. all other objects.
In the first phase, IRQ flags that a frame saved while interrupts were enabled are restored last. Every later save found interrupts disabled, so a frame has at most one such set, and the locks and the other flags can be released in any order before it.
The unwinder releases a socket with bpf_sk_release(), and the reader of a file dynptr with the function that bpf_dynptr_file_discard() frees it with. So bpf_sk_release_proto is no longer static, and that function becomes bpf_dynptr_file_free().
Nothing generates descriptors yet, so nothing is released and the verifier's rules do not change.
--- include/linux/bpf.h | 91 +++++++++++++ include/linux/bpf_verifier.h | 4 + kernel/bpf/Makefile | 2 +- kernel/bpf/core.c | 1 + kernel/bpf/fixups.c | 3 + kernel/bpf/helpers.c | 17 ++- kernel/bpf/unwind.c | 243 +++++++++++++++++++++++++++++++++++ kernel/bpf/verifier.c | 5 + net/core/filter.c | 2 +- 9 files changed, 364 insertions(+), 4 deletions(-) create mode 100644 kernel/bpf/unwind.c
include/linux/bpf.h +91 -0
@@ -1829,6 +1829,72 @@ struct bpf_jit_poke_descriptor {u32 insn_idx; }; +/* +* Frame descriptors: the hidden stack slots in which the frames of a function +* keep the handles of what they hold, and how the unwinder releases them, see +* kernel/bpf/frame_desc.c. +*/ +/* Kinds of resources the unwinder can release. */ +enum bpf_resource_kind { +BPF_RESOURCE_OBJ ,/* bpf_obj_new() object: bpf_obj_drop_impl(p, meta) */ +BPF_RESOURCE_PERCPU_OBJ ,/* bpf_percpu_obj_new() object: bpf_percpu_obj_drop_impl() */ +BPF_RESOURCE_KPTR ,/* referenced kernel object: the BTF registry's dtor(p) */ +BPF_RESOURCE_SOCK ,/* socket acquired by a helper: bpf_sk_release() */ +BPF_RESOURCE_RINGBUF ,/* ringbuf record, also of a dynptr: bpf_ringbuf_discard() */ +BPF_RESOURCE_DYNPTR_FILE ,/* reader of a file dynptr: bpf_dynptr_file_free() */ +BPF_RESOURCE_ITER ,/* open-coded iterator on the stack: its destroy kfunc */ +BPF_RESOURCE_SPIN_LOCK ,/* bpf_spin_lock() held: bpf_spin_unlock() */ +BPF_RESOURCE_RES_LOCK ,/* bpf_res_spin_lock() held: bpf_res_spin_unlock() */ +BPF_RESOURCE_IRQ_FLAG ,/* saved IRQ flags on the stack: bpf_local_irq_restore() */ +BPF_RESOURCE_RCU ,/* bpf_rcu_read_lock() sections: unlock each */ +BPF_RESOURCE_PREEMPT ,/* bpf_preempt_disable() sections: enable each */ +}; + +/* A resource class of a program; descriptor entries refer to it by index. */ +struct bpf_resource { +u8 kind;/* enum bpf_resource_kind */ +bool sleeps;/* the destructor may sleep */ +bool rcu;/* the destructor needs RCU protection */ +u32 btf_id; +struct btf *btf; +union { +void (*dtor)(void *); +struct btf_struct_meta *meta; + }; +}; + +/* +* The slot holds a count of sections instead of a handle, signed: the +* sections the frame entered less those of its callers it left. +*/ +#define BPF_FRAME_ENT_COUNTER BIT(0) + +/* +* One hidden slot of a frame: it holds the handle of a resource of the class +* the frame holds, or 0. The handle is what the release takes: the pointer +* to an object, to a ringbuf record or to a lock, or the address of an +* iterator or of saved IRQ flags on the stack of some frame. +*/ +struct bpf_frame_desc_entry { +u16 res;/* index into the program's resource classes */ +s16 off;/* the slot, relative to the frame pointer */ +u16 flags; +}; + +struct bpf_frame_desc { +u32 size;/* total size in bytes, including this header */ +u32 nent;/* number of entries that follow */ +struct bpf_frame_desc_entry ent[]; +}; + +/* Owned by the main program and shared by its subprograms. */ +struct bpf_frame_desc_pool { +u32 cnt; +u32 resource_cnt; +struct bpf_resource *resources; +struct bpf_frame_desc *desc[]; +}; +/* reg_type info for ctx arguments */ struct bpf_ctx_arg_aux {u32 offset;
@@ -2053,6 +2119,14 @@ struct bpf_prog_aux {struct module *mod;u32 num_exentries;struct exception_table_entry *extable; +/* +* The frame descriptor of this JITed function in the pool of the main +* program, plus one, or 0: the hidden slots its frames keep the handles +* of their resources in, see frame_desc.c. The main and sub progs share +* the pool, which is freed with the main prog only. +*/ +u32 frame_desc; +struct bpf_frame_desc_pool *frame_desc_pool;union {struct work_struct work;struct rcu_head rcu;
@@ -2999,6 +3073,12 @@ void bpf_obj_free_task_work(const struct btf_record *rec, void *obj);void bpf_obj_cancel_fields (struct bpf_map *map,void *obj);void bpf_obj_free_fields (const struct btf_record *rec,void *obj);void __bpf_obj_drop_impl (void *p,const struct btf_record *rec,bool percpu); +void bpf_obj_drop_impl (void *p__alloc,void *meta__ign); +void bpf_percpu_obj_drop_impl (void *p__alloc,void *meta__ign); +void bpf_rcu_read_unlock (void ); +void bpf_preempt_enable (void ); +void bpf_local_irq_restore (unsigned long *flags__irq_flag); +void bpf_res_spin_unlock (struct bpf_res_spin_lock *lock);struct bpf_map *bpf_map_get (u32 ufd);struct bpf_map *bpf_map_get_with_uref (u32 ufd);
@@ -4218,6 +4298,7 @@ extern const struct bpf_func_proto bpf_ringbuf_output_proto;extern const struct bpf_func_proto bpf_ringbuf_reserve_proto;extern const struct bpf_func_proto bpf_ringbuf_submit_proto;extern const struct bpf_func_proto bpf_ringbuf_discard_proto; +extern const struct bpf_func_proto bpf_sk_release_proto;extern const struct bpf_func_proto bpf_ringbuf_query_proto;extern const struct bpf_func_proto bpf_ringbuf_reserve_dynptr_proto;extern const struct bpf_func_proto bpf_ringbuf_submit_dynptr_proto;
@@ -4508,11 +4589,21 @@ void bpf_insn_array_remap(struct bpf_map *map, const u32 *new_off, const u32 *ne#ifdef CONFIG_BPF_SYSCALL void bpf_prog_update_insn_ptrs (struct bpf_prog *prog,u32 *offsets,void *image); +void bpf_unwind_release (const struct bpf_resource *res,void *handle); +void bpf_dynptr_file_free (void *reader); +void bpf_unwind_release_frames (void ); +void bpf_frame_desc_pool_free (struct bpf_frame_desc_pool *pool);#else static inline void bpf_prog_update_insn_ptrs (struct bpf_prog *prog,u32 *offsets,void *image) { } +static inline void bpf_frame_desc_pool_free (struct bpf_frame_desc_pool *pool) +{ +} +static inline void bpf_unwind_release_frames (void ) +{ +}#endif static inline bool bpf_map_is_percpu_map (enum bpf_map_type map_type)
include/linux/bpf_verifier.h +4 -0
@@ -1035,6 +1035,8 @@ struct bpf_subprog_info {*/ u32 exit_idx;struct bpf_iarray *jt;/* jump table shared by all gotox of this subprogram */ +/* the frame descriptor of this subprogram in the pool of the program, plus one; see frame_desc.c */ +u32 frame_desc;u16 stack_depth;/* max. stack depth used by this function */ /* bytes below the verified stack depth reserved for hidden slots, see bpf_reserve_stack_slots() */ u16 stack_extra;
@@ -1293,6 +1295,8 @@ struct bpf_verifier_env {/* What a pointer that was read from a table of functions may point to. */ struct bpf_func_set **func_sets;u32 func_set_cnt; +/* frame descriptors of the subprograms, handed to prog->aux once verification succeeds */ +struct bpf_frame_desc_pool *frame_desc_pool;bool has_callx;/* * Call graph edges created by callx instructions. A bitmap of
kernel/bpf/Makefile +1 -1
@@ -11,7 +11,7 @@ obj-$(CONFIG_BPF_SYSCALL) += bpf_iter.o map_iter.o task_iter.o prog_iter.o link_ obj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o map_in_map.o bloom_filter.o obj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o ringbuf.o bpf_insn_array.o obj-$(CONFIG_BPF_SYSCALL) += bpf_local_storage.o bpf_task_storage.o -obj-$(CONFIG_BPF_SYSCALL) += fixups.o cfg.o states.o check_btf.o flow.o join.o +obj-$(CONFIG_BPF_SYSCALL) += fixups.o cfg.o states.o check_btf.o flow.o join.o unwind.o obj-${CONFIG_BPF_LSM} += bpf_inode_storage.o obj-$(CONFIG_BPF_SYSCALL) += disasm.o mprog.o obj-$(CONFIG_BPF_JIT) += trampoline.o
kernel/bpf/core.c +1 -0
@@ -3459,6 +3459,7 @@ static void bpf_prog_free_images(struct work_struct *work)int i; aux = container_of(to_rcu_work(work), struct bpf_prog_aux, rwork); +bpf_frame_desc_pool_free (aux->frame_desc_pool);for (i =0 ; i < aux->real_func_cnt; i++) {/* We can just unlink the subprog poke descriptor table as * it was originally linked to the main program and is also
kernel/bpf/fixups.c +3 -0
@@ -2047,6 +2047,9 @@ static int jit_subprogs(struct bpf_verifier_env *env) func[i]->aux->nr_linfo = prog->aux->nr_linfo; func[i]->aux->jited_linfo = prog->aux->jited_linfo; func[i]->aux->linfo_idx = env->subprog_info[i].linfo_idx; +/* The pool is freed with the main prog. */ + func[i]->aux->frame_desc = env->subprog_info[i].frame_desc; + func[i]->aux->frame_desc_pool = env->frame_desc_pool; func[i]->aux->arena = prog->aux->arena; func[i]->aux->arena_stack_size = prog->aux->arena_stack_size; func[i]->aux->arena_stack_entry = env->subprog_info[i].arena_stack_entry;
kernel/bpf/helpers.c +15 -2
@@ -3492,6 +3492,8 @@ notrace void bpf_throw_unwind(struct bpf_throw_regs *regs, u64 cookie)*/ if (regs)memcpy (ctx.saved, ®s->r[BPF_REG_6 ],sizeof (ctx.saved)); +/* What the frames about to be discarded hold is released first. */ +bpf_unwind_release_frames ();arch_bpf_stack_walk (bpf_stack_walker, &ctx);WARN_ON_ONCE (!ctx.aux);if (ctx.aux)
@@ -5136,6 +5138,18 @@ int bpf_dynptr_from_file_sleepable(struct file *file, u32 flags, struct bpf_dynpreturn make_file_dynptr (file, flags, true, (struct bpf_dynptr_kern *)ptr__uninit); } +/* +* Free the reader of a file dynptr, which the dynptr and its clones point +* at: what the unwinder does for a dynptr the program did not discard. +*/ +void bpf_dynptr_file_free (void *reader) +{ +struct bpf_dynptr_file_impl *df = reader; + +freader_cleanup (&df->freader); +kfree_nolock (df); +} + __bpf_kfunc int bpf_dynptr_file_discard(struct bpf_dynptr *dynptr) {struct bpf_dynptr_kern *ptr = (struct bpf_dynptr_kern *)dynptr;
@@ -5144,8 +5158,7 @@ __bpf_kfunc int bpf_dynptr_file_discard(struct bpf_dynptr *dynptr)if (!df)return 0 ; -freader_cleanup (&df->freader); -kfree_nolock (df); +bpf_dynptr_file_free (df);bpf_dynptr_set_null (ptr);return 0 ; }
kernel/bpf/unwind.c new file +243 -0
243 lines not on this page · raw message
kernel/bpf/verifier.c +5 -0
@@ -25695,6 +25695,10 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,if (env->prog->type ==BPF_PROG_TYPE_EXT ) env->prog->expected_attach_type =0 ; +/* The program owns the frame descriptors from here on. */ + env->prog->aux->frame_desc_pool = env->frame_desc_pool; + env->frame_desc_pool =NULL ; + env->prog =__bpf_prog_select_runtime (env, env->prog, &ret); err_release_maps:
@@ -25731,6 +25735,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,release_btfs (env); err_free_env:bpf_free_subprog_jts (env); +bpf_frame_desc_pool_free (env->frame_desc_pool);if (env->insn_aux_data)bpf_clear_insn_aux_data (env,0 , env->insn_aux_data_len);vfree (env->insn_aux_data);
net/core/filter.c +1 -1
@@ -7577,7 +7577,7 @@ BPF_CALL_1(bpf_sk_release, struct sock *, sk)return 0 ; } -static const struct bpf_func_proto bpf_sk_release_proto = { +const struct bpf_func_proto bpf_sk_release_proto = { .func = bpf_sk_release, .gpl_only = false, .ret_type =RET_INTEGER ,
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 13/29 bpf: Track held resources in hidden stack slots
In reply to Kumar Kartikeya DwivediCurrently nothing generates frame descriptors, so bpf_throw() releases nothing and the verifier rejects a throw that is reached with a reference, a lock, or a section held.
Add the verifier half. In a program that may throw, every call that acquires a resource gets its own hidden stack slot in the frame that makes the call. The verifier inserts instructions that keep the handle of the resource in the slot while it is held. It makes no assumption about where the program itself keeps the handle.
The main pass records, per instruction:
- each call that acquires a resource, with the resource class and the location of the handle: R0 or an argument register;
- each call that releases a resource, with the register that holds the handle and the acquisitions that it may release;
- each call that enters or leaves an RCU read or preemption disabled section.
After the pass, the verifier reserves the slots and patches the program:
- A handle returned in R0 is stored after the call. A pointer into the object, as bpf_list_pop_front() returns, is first adjusted to the start of the object.
- A handle passed as an argument, the address of a lock, an iterator or saved IRQ flags, is stored before the call, so that a throw from inside an iterator loop or a critical section finds it. If the call can fail to acquire, the slot is cleared after a failed call.
- A releasing call compares its handle with the slot of each acquisition it may release, and clears the matching slot before the call.
- The handle of a dynptr, its ringbuf record or file reader, is loaded from the dynptr on the stack after the call that initializes it.
- A function that enters or leaves a section counts it in one slot per kind.
- A subprog that returns a reference in R0 returns holding it. The call is then the caller's acquisition, like a call to a kfunc that returns a reference.
- The referenced arguments of a struct_ops program are loaded from the context when the main program is entered.
- The slots of a frame are zeroed at its entry.
The frame descriptor of each function lists these slots, and bpf_throw() walks them.
A slot belongs to one frame and holds one handle. A program that throws is therefore rejected when:
- a callee releases a resource held by its caller, or leaves a section that its caller entered;
- a subprog returns holding anything other than one reference in R0, or in different sections than it was called in;
- a callee creates an iterator or saves IRQ flags on its caller's stack;
- a frame can hold two resources acquired by the same instruction;
- an instruction acquires a resource only on some paths, such as a lookup that returns a socket from a map of sockets and a plain value from another map, or resources of different classes on different paths;
- an object is released while the lock inside it is held;
- a reference has no destructor that the unwinder may run: its type has no cleanup destructor registered, or the program may not call its release kfunc.
Later patches lift most of the first five.
The unwinder destroys iterators and restores IRQ flags on the program's stack, so they must be intact at a throw:
- A program that throws must not overwrite an iterator or saved IRQ flags on its stack.
- A verifier state can stand for another only if the stack objects of both are in the same slots. This already holds: a stack object stays in a state whether or not it is read again, and is always compared.
- An iterator that needs RCU protection must not outlive the RCU section it was created in.
States are now also compared by the owner frame, the resource class and the acquiring instruction of each reference, since the instructions inserted for a release depend on them.
Frames are described only where the JIT reports bpf_jit_supports_exception_cleanup(), and only in a program where some subprog may throw. Other architectures and programs keep the existing rules. A throw from a callback stays rejected.
exceptions_fail has programs that expect a throw with resources held to be rejected. That still holds where the JIT does not support the cleanup, so restrict them to s390x, the other architecture with exceptions that the test loader can select. Two others throw from a callback and are still rejected here, but by that rule: update their expected message and restrict them to x86-64 and arm64.
--- include/linux/bpf_verifier.h | 84 ++ kernel/bpf/Makefile | 2 +- kernel/bpf/fixups.c | 2 +- kernel/bpf/frame_desc.c | 1186 +++++++++++++++++ kernel/bpf/join.c | 18 +- kernel/bpf/states.c | 23 +- kernel/bpf/verifier.c | 296 +++- .../selftests/bpf/progs/exceptions_fail.c | 16 +- 8 files changed, 1584 insertions(+), 43 deletions(-) create mode 100644 kernel/bpf/frame_desc.c
include/linux/bpf_verifier.h +84 -0
@@ -333,6 +333,14 @@ struct bpf_reference_state {* function verified on its own hold, which are not in the state. */ s8 frameno; +/* +* The resource class the unwinder releases it as, plus one, see +* frame_desc.c; 0 for a reference the frames keep no handle of, which +* a throw may not be reached with. +*/ +u16 res; +/* The acquisition whose slot holds its handle, plus one, see struct bpf_res_acq. */ +u16 acq;union {/* For REF_TYPE_PTR */ int parent_id;
@@ -908,6 +916,13 @@ struct bpf_insn_aux_data { u64 jump_target:1;unsigned int orig_idx;/* original instruction index, initialized once */ +/* +* Some visit of the call acquired nothing a hidden slot can hold where +* another may have acquired a resource, see frame_desc.c. +*/ +bool res_none; +/* Some visit of the call left an R0 that is not a resource. */ +bool res_none_r0;/* registers alive before this instruction. */ u16 live_regs_before;/*
@@ -1037,6 +1052,8 @@ struct bpf_subprog_info {struct bpf_iarray *jt;/* jump table shared by all gotox of this subprogram */ /* the frame descriptor of this subprogram in the pool of the program, plus one; see frame_desc.c */ u32 frame_desc; +/* hidden slots: the counts of the sections the frame entered, by kind; 0 when it has none */ +s16 section_off[2 ];u16 stack_depth;/* max. stack depth used by this function */ /* bytes below the verified stack depth reserved for hidden slots, see bpf_reserve_stack_slots() */ u16 stack_extra;
@@ -1224,6 +1241,42 @@ void bpf_patch_image_install(struct bpf_prog *prog, const struct bpf_patch_image/* single container for all structs * one verifier_env per bpf_check() call */ +#define BPF_RES_F_ARG BIT(0)/* the handle is the pointer the call takes in the register */ +#define BPF_RES_F_MAY_FAIL BIT(1)/* acquired only when the call returns 0 */ +#define BPF_RES_F_DEREF BIT(2)/* the handle is the first word of a dynptr on the stack */ +#define BPF_RES_F_CTX BIT(3)/* a __ref argument the kernel passes the main program */ + +/* +* A call that acquires a resource, see frame_desc.c: the handle of what it +* acquired is kept in a hidden slot of the frame that runs the call, one slot +* per acquiring call. +*/ +struct bpf_res_acq { +u32 insn;/* the insn as the main pass named it; the argument with BPF_RES_F_CTX */ +u16 res;/* the class of what it acquires */ +u8 reg;/* R0 for what the call returns, else the argument that is the handle */ +u8 flags;/* BPF_RES_F_* */ +s16 adj;/* R0 points that many bytes into the object: the node of a list */ +s16 obj_off;/* BPF_RES_F_DEREF: the dynptr, relative to R10; BPF_RES_F_CTX: in the context */ +s16 off;/* the slot, relative to R10 */ +}; + +/* A call that releases what an acquisition acquired: the slot of the acquisition is cleared. */ +struct bpf_res_rel { +u32 insn; +u16 acq;/* index of the acquisition */ +u8 reg;/* the argument the call releases */ +u8 flags;/* BPF_RES_F_DEREF */ +s16 adj;/* the argument points that many bytes into the object */ +}; + +/* A call that enters or leaves a section, for the count of its frame, see frame_desc.c. */ +struct bpf_section_insn { +u32 insn; +u8 kind;/* 0 RCU, 1 preemption */ +s8 delta; +}; +/* Initialization of a hidden stack slot, emitted in the subprogram's prologue. */ struct bpf_stack_init {u32 subprog;
@@ -1258,6 +1311,19 @@ struct bpf_verifier_env {u32 hidden_subprog_cnt;/* number of hidden subprogs */ struct bpf_stack_init *stack_inits;/* hidden slot initializations, see bpf_stack_slot_init() */ u32 stack_init_cnt; +struct bpf_resource *resources;/* resource classes of frame descriptors */ +u32 resource_cnt; +struct bpf_res_acq *res_acqs;/* see frame_desc.c */ +u32 res_acq_cnt; +struct bpf_res_rel *res_rels; +u32 res_rel_cnt; +struct bpf_section_insn *section_insns; +u32 section_insn_cnt; +int release_regno;/* the argument the current call releases, or -1 */ +u32 frame_desc_slots;/* hidden slots that hold handles and counts */ +u32 frame_desc_insns;/* instructions keeping them up to date */ +u32 frame_desc_bytes;/* descriptors and resource classes */ +bool describe_frames;/* frame descriptors are generated: the program may throw */ int exception_callback_subprog;bool explore_alu_limits;bool allow_ptr_leaks;
@@ -1929,6 +1995,7 @@ u32 btf_func_arg_align(const struct btf *btf, const struct btf_type *t);int bpf_find_subprog (struct bpf_verifier_env *env,int off);bool bpf_is_throw_kfunc (struct bpf_insn *insn); +bool bpf_nmi_unsafe_prog (struct bpf_verifier_env *env);int bpf_compute_const_regs (struct bpf_verifier_env *env);int bpf_prune_dead_branches (struct bpf_verifier_env *env);int bpf_check_cfg (struct bpf_verifier_env *env);
@@ -2202,7 +2269,24 @@ int bpf_check_attach_btf_id_multi(struct btf *btf, struct bpf_prog *prog, u32 bt/* Functions in fixups.c, called from bpf_check() */ void bpf_clear_insn_aux_data (struct bpf_verifier_env *env,int start,int len);int bpf_reserve_stack_slots (struct bpf_verifier_env *env,int subprog,int nr,s16 *off); +int bpf_adj_moved_insn (struct bpf_insn *insn,u32 delta);int bpf_stack_slot_init (struct bpf_verifier_env *env,int subprog,s16 off,s32 imm); + +/* Functions in frame_desc.c */ +int bpf_frame_desc_record_acquire (struct bpf_verifier_env *env,int idx,int regno,bool after, +bool may_fail); +void bpf_frame_desc_record_none (struct bpf_verifier_env *env,const struct bpf_reg_state *r0); +int bpf_frame_desc_record_release (struct bpf_verifier_env *env,int idx); +int bpf_frame_desc_record_ctx_arg (struct bpf_verifier_env *env,int idx,int argno); +int bpf_frame_desc_record_section (struct bpf_verifier_env *env,u8 kind,s8 delta); +int bpf_frame_desc_record_exit (struct bpf_verifier_env *env); +int bpf_frame_desc_record_return (struct bpf_verifier_env *env,struct bpf_verifier_state *st, +int idx,int callsite,const struct bpf_reg_state *r0); +int bpf_frame_desc_resolve (struct bpf_verifier_env *env); +int bpf_frame_desc_emit (struct bpf_verifier_env *env); +int bpf_frame_desc_build (struct bpf_verifier_env *env); +void bpf_frame_desc_reset (struct bpf_verifier_env *env); +void bpf_frame_desc_env_free (struct bpf_verifier_env *env);int bpf_remove_fastcall_spills_fills (struct bpf_verifier_env *env);int bpf_optimize_bpf_loop (struct bpf_verifier_env *env);void bpf_opt_hard_wire_dead_code_branches (struct bpf_verifier_env *env);
kernel/bpf/Makefile +1 -1
@@ -11,7 +11,7 @@ obj-$(CONFIG_BPF_SYSCALL) += bpf_iter.o map_iter.o task_iter.o prog_iter.o link_ obj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o map_in_map.o bloom_filter.o obj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o ringbuf.o bpf_insn_array.o obj-$(CONFIG_BPF_SYSCALL) += bpf_local_storage.o bpf_task_storage.o -obj-$(CONFIG_BPF_SYSCALL) += fixups.o cfg.o states.o check_btf.o flow.o join.o unwind.o +obj-$(CONFIG_BPF_SYSCALL) += fixups.o cfg.o states.o check_btf.o flow.o join.o frame_desc.o unwind.o obj-${CONFIG_BPF_LSM} += bpf_inode_storage.o obj-$(CONFIG_BPF_SYSCALL) += disasm.o mprog.o obj-$(CONFIG_BPF_JIT) += trampoline.o
kernel/bpf/fixups.c +1 -1
@@ -553,7 +553,7 @@ int bpf_patch_list_commit(struct bpf_verifier_env *env)* insn was moved down by delta insns inside its own patch. Operands relative * to the pc that point in front of the old position did not move with it. */ -static int bpf_adj_moved_insn (struct bpf_insn *insn,u32 delta) +int bpf_adj_moved_insn (struct bpf_insn *insn,u32 delta) {u8 class =BPF_CLASS (insn->code), op =BPF_OP (insn->code);s64 off = insn->imm, off_min =S32_MIN ;
kernel/bpf/frame_desc.c new file +1186 -0
1,186 lines not on this page · raw message
kernel/bpf/join.c +16 -2
@@ -462,6 +462,10 @@ static bool join_func(struct bpf_join_state *js, struct bpf_func_state *fd, fa->entry_rcu_locks != fb->entry_rcu_locks || fa->entry_preempt_locks != fb->entry_preempt_locks)return false; +/* a frame that left a section of its callers hands its count over, see frame_desc.c */ +if (env->describe_frames && (fa->rcu_released_below != fb->rcu_released_below || + fa->preempt_released_below != fb->preempt_released_below)) +return false;if (!join_loop_stack (fa, fb))return false; }
@@ -523,7 +527,7 @@ static bool join_func(struct bpf_join_state *js, struct bpf_func_state *fd, }/* Things that can't be different in two states that are joined */ -static bool join_check_state (const struct bpf_verifier_state *a, +static bool join_check_state (struct bpf_verifier_env *env,const struct bpf_verifier_state *a,const struct bpf_verifier_state *b) {int i;
@@ -546,6 +550,16 @@ static bool join_check_state(const struct bpf_verifier_state *a,for (i =0 ; i < a->acquired_refs; i++) {if (a->refs[i].type != b->refs[i].type)return false; +/* +* The frames of a program that may throw keep the handle of a +* reference in the slot of the call that acquired it, in the frame +* that holds it, see frame_desc.c, and the result keeps those of +* 'a'. +*/ +if (env->describe_frames && + (a->refs[i].frameno != b->refs[i].frameno || a->refs[i].res != b->refs[i].res || + (a->refs[i].frameno >=0 && a->refs[i].acq != b->refs[i].acq))) +return false;switch (a->refs[i].type) {case REF_TYPE_PTR :case REF_TYPE_IRQ :
@@ -632,7 +646,7 @@ int bpf_states_join(struct bpf_verifier_env *env, struct bpf_verifier_state *dst js->npairs =0 ; js->pairs_overflow = false; -if (!join_check_state (a, b)) +if (!join_check_state (env, a, b))return 1 ; err =bpf_copy_verifier_state (dst, a);if (err)
kernel/bpf/states.c +20 -3
@@ -562,8 +562,8 @@ static bool stack_arg_safe(struct bpf_verifier_env *env, struct bpf_func_state *return true; } -static bool refsafe (struct bpf_verifier_state *old,struct bpf_verifier_state *cur, -struct bpf_idmap *idmap) +static bool refsafe (struct bpf_verifier_env *env,struct bpf_verifier_state *old, +struct bpf_verifier_state *cur,struct bpf_idmap *idmap) {int i;
@@ -590,6 +590,19 @@ static bool refsafe(struct bpf_verifier_state *old, struct bpf_verifier_state *cif (!check_ids (old->refs[i].id, cur->refs[i].id, idmap) || old->refs[i].type != cur->refs[i].type)return false; +/* +* The frames of a program that may throw keep the handle of a +* reference in the slot of the call that acquired it, in the frame +* that holds it, see frame_desc.c: a state doesn't stand for one +* that holds the reference as another class, in another frame, or +* from another call. What the callers of a function verified on its +* own hold has no slot in its frames. +*/ +if (env->describe_frames && + (old->refs[i].frameno != cur->refs[i].frameno || + old->refs[i].res != cur->refs[i].res || + (old->refs[i].frameno >=0 && old->refs[i].acq != cur->refs[i].acq))) +return false;switch (old->refs[i].type) {case REF_TYPE_PTR :if (!check_ids (old->refs[i].parent_id, cur->refs[i].parent_id, idmap))
@@ -695,6 +708,10 @@ static bool func_states_equal(struct bpf_verifier_env *env, struct bpf_func_stat old->entry_rcu_locks != cur->entry_rcu_locks || old->entry_preempt_locks != cur->entry_preempt_locks)return false; +/* a frame that left a section of its callers hands its count over, see frame_desc.c */ +if (env->describe_frames && (old->rcu_released_below != cur->rcu_released_below || + old->preempt_released_below != cur->preempt_released_below)) +return false;for (i =0 ; i <MAX_BPF_REG ; i++) if (((1 << i) & live_regs) &&
@@ -748,7 +765,7 @@ bool bpf_state_subsumes(struct bpf_verifier_env *env, struct bpf_verifier_stateif (old->unwound != cur->unwound || old->changed_pkt_data != cur->changed_pkt_data)return false; -if (!refsafe (old, cur, &env->idmap_scratch)) +if (!refsafe (env, old, cur, &env->idmap_scratch))return false;/* for states to be equal callsites have to be the same
kernel/bpf/verifier.c +262 -34
586 lines not on this page · raw message
tools/testing/selftests/bpf/progs/exceptions_fail.c +14 -2
@@ -133,6 +133,7 @@ __noinline static int subprog_lock(struct __sk_buff *ctx) } SEC("?tc") +__arch_s390x __failure __msg("function calls are not allowed while holding a lock")int reject_with_lock (void *ctx) {
@@ -142,6 +143,7 @@ int reject_with_lock(void *ctx) } SEC("?tc") +__arch_s390x __failure __msg("function calls are not allowed while holding a lock")int reject_subprog_with_lock (void *ctx) {
@@ -149,6 +151,7 @@ int reject_subprog_with_lock(void *ctx) } SEC("?tc") +__arch_s390x __failure __msg("bpf_throw cannot be used inside bpf_rcu_read_lock-ed region")int reject_with_rcu_read_lock (void *ctx) {
@@ -165,6 +168,7 @@ __noinline static int throwing_subprog(struct __sk_buff *ctx) } SEC("?tc") +__arch_s390x __failure __msg("bpf_throw cannot be used inside bpf_rcu_read_lock-ed region")int reject_subprog_with_rcu_read_lock (void *ctx) {
@@ -181,7 +185,8 @@ static bool rbless(struct bpf_rb_node *n1, const struct bpf_rb_node *n2) } SEC("?tc") -__failure __msg("function calls are not allowed while holding a lock") +__arch_x86_64 __arch_arm64 +__failure __msg("cannot be called from callback subprog")int reject_with_rbtree_add_throw (void *ctx) {struct foo *f;
@@ -196,6 +201,7 @@ int reject_with_rbtree_add_throw(void *ctx) } SEC("?tc") +__arch_s390x __failure __msg("Unreleased reference")int reject_with_reference (void *ctx) {
@@ -216,6 +222,7 @@ __noinline int global_subprog_may_throw(struct __sk_buff *ctx) } SEC("?tc") +__arch_s390x __failure __msg("Unreleased reference")int reject_global_subprog_throw_with_reference (struct __sk_buff *ctx) {
@@ -248,7 +255,8 @@ __noinline static int subprog_cb_ref(u32 i, void *ctx) } SEC("?tc") -__failure __msg("Unreleased reference") +__arch_x86_64 __arch_arm64 +__failure __msg("cannot be called from callback subprog")int reject_with_cb_reference (void *ctx) {struct foo *f;
@@ -270,6 +278,7 @@ int reject_with_cb(void *ctx) } SEC("?tc") +__arch_s390x __failure __msg("Unreleased reference")int reject_with_subprog_reference (void *ctx) {
@@ -417,6 +426,7 @@ __noinline static int rcu_lock_then_throw(void) } SEC("?tc") +__arch_s390x __failure __msg("bpf_throw cannot be used inside bpf_rcu_read_lock-ed region")int reject_subprog_rcu_lock_throw (void *ctx) {
@@ -425,6 +435,7 @@ int reject_subprog_rcu_lock_throw(void *ctx) } SEC("?tc") +__arch_s390x __failure __msg("bpf_throw cannot be used inside bpf_preempt_disable-ed region")int reject_subprog_throw_preempt_lock (void *ctx) {
@@ -435,6 +446,7 @@ int reject_subprog_throw_preempt_lock(void *ctx) } SEC("?tc") +__arch_s390x __failure __msg("bpf_throw cannot be used inside bpf_local_irq_save-ed region")int reject_subprog_throw_irq_lock (void *ctx) {
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 14/29 bpf: Describe held resources at throw sites
In reply to Kumar Kartikeya DwivediCurrently every call that acquires a resource in a program that throws keeps the handle in a hidden slot of its frame. This costs a stack slot, a store on acquire, and a compare and a store on release, whether or not a throw can find the resource held.
A frame can only be unwound at a call it has not returned from:bpf_throw(), or a call to a subprog that may throw. Such a call is a site. Often the frame already keeps its resources on its own stack at a site, in the same location on every path:
- an iterator and saved IRQ flags are objects on the stack;
- a reference is spilled, or is the first word of a dynptr;
- the frame is in the same number of RCU read and preemption disabled sections.
Add a descriptor per site for these. On every visit to a site, the main pass records where each frame of the state keeps its resources:
- at bpf_throw() and at a call to a global subprog that may throw: for the frame that makes the call and for each of its callers, at the call it has not returned from;
- at a call to a subprog that is verified on its own and may throw: for the frames of the caller. Such a subprog has no pointer into their stack, so its throw finds them as they are at the call.
After the pass, each acquisition is one of three cases:
- No site finds the resource held: nothing is kept for it.
- Every site that finds it held does so on every visit, with the handle in the same location: the site descriptors give that location.
- Otherwise the handle is kept in a hidden slot, as before.
Sections work the same way. A subprog counts its sections in a hidden slot only if the visits to one of its sites disagree on the number of sections. Otherwise the site descriptor gives the count, which is negative for a callee that left a section of its caller.
A referenced argument of a struct_ops program is described like any other reference. Its hidden slot, and the load and the store at program entry that fill it, remain only for an argument that some site cannot describe. Locks always take a hidden slot when a site finds them held.
Two things limit which stack slots a site can describe:
- The verifier forgets the contents of a dead slot where paths join, see bpf_clean_verifier_state(). A reference spilled in a slot that is not read again would then be unknown at the site, and nothing is read after bpf_throw(). So a slot that holds a pointer to a resource held by its frame is kept even when dead: it stays in the state, and a state can stand for another only if both have the same contents in it. Stack liveness itself does not change.
- The slots that the spills of a bpf_fastcall pattern go to hold nothing at runtime once bpf_remove_fastcall_spills_fills() removes the spills. They are never described.
At runtime, each JITed function has a table of its sites that have a descriptor, sorted by native offset. The verifier builds the table from the instruction index of each call when the function is split off for the JIT, after constant blinding has moved the instructions. The x86-64 and arm64 JITs fill in the native range of each call. The unwinder looks up the return address of a frame, minus one, and releases the resources listed in the site descriptor together with those in the hidden slots of the function.
A descriptor entry now says what kind of location it describes: a slot or a spill that holds a pointer, possibly into the object, a stack object, a slot that counts sections, or a constant count. Iterators and file readers are flagged to be released before the references of their frame, in both descriptors of the frame.
The rejections for a program that throws do not change, with one
exception: an instruction that acquires a resource only on some paths is rejected only if the resource needs a hidden slot.
A subprog that calls through a function pointer it was passed can throw without containing a throw or taking the address of a function. Mark every subprog that contains a callx as one that may throw if any function whose address is taken may throw, so that the frames of its callers are visited at the call.
--- arch/arm64/net/bpf_jit_comp.c | 1 + arch/x86/net/bpf_jit_comp.c | 4 +- include/linux/bpf.h | 64 +++- include/linux/bpf_verifier.h | 61 +++- kernel/bpf/cfg.c | 23 +- kernel/bpf/core.c | 1 + kernel/bpf/fixups.c | 8 +- kernel/bpf/frame_desc.c | 603 +++++++++++++++++++++++++++------- kernel/bpf/join.c | 3 +- kernel/bpf/states.c | 8 +- kernel/bpf/unwind.c | 156 ++++++--- kernel/bpf/verifier.c | 22 +- 12 files changed, 780 insertions(+), 174 deletions(-)
arch/arm64/net/bpf_jit_comp.c +1 -0
@@ -2902,6 +2902,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr* reasons, expects to point to the next instruction) */ bpf_prog_update_insn_ptrs (prog, ctx.offset, ctx.ro_image); +bpf_prog_fill_frame_desc_sites (prog, ctx.offset, ctx.ro_image); out_off:if (!ro_header && priv_stack_ptr) {free_percpu (priv_stack_ptr);
arch/x86/net/bpf_jit_comp.c +3 -1
@@ -4932,8 +4932,10 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr }if (!image || !prog->is_func || extra_pass) { -if (image) +if (image) {bpf_prog_fill_jited_linfo (prog, addrs +1 ); +bpf_prog_fill_frame_desc_sites (prog, addrs, image); + } out_addrs:kvfree (addrs);if (!image && priv_stack_ptr) {
include/linux/bpf.h +54 -10
101 lines not on this page · raw message
include/linux/bpf_verifier.h +58 -3
104 lines not on this page · raw message
kernel/bpf/cfg.c +21 -2
@@ -535,22 +535,41 @@ static int visit_func_ptrs_insn(int t, struct bpf_verifier_env *env,static void close_callee_effects (struct bpf_verifier_env *env) {struct bpf_insn *insn = env->prog->insnsi; +bool changed, taken_throws = false, throws; +struct bpf_subprog_info *sub;struct bpf_func_ptr *ptrs; -bool changed;u32 cnt, j;int t;do { changed = false; + throws = taken_throws;for (t =0 ; t < env->prog->len; t++) {if (bpf_pseudo_call (&insn[t]) ||bpf_pseudo_func (&insn[t])) changed |=merge_callee_effects (env, t, t + insn[t].imm +1 ); +if (bpf_pseudo_func (&insn[t])) + taken_throws |=bpf_find_containing_subprog (env, t + insn[t].imm +1 )->might_throw; ptrs =insn_func_ptrs (env, t, &cnt); -for (j =0 ; j < cnt; j++) +for (j =0 ; j < cnt; j++) { changed |=merge_callee_effects (env, t, ptrs[j].xlated_off); + taken_throws |=bpf_find_containing_subprog (env, ptrs[j].xlated_off)->might_throw; + } +/* +* It is not known which function a callx calls: the subprog may +* throw when a function whose address is taken may. The marks +* above are for the subprog that takes the address, which need +* not be the one that calls through it, and the frames of the +* callers of the one that does are unwound, see frame_desc.c. +*/ +if (taken_throws &&bpf_is_callx (&insn[t])) { + sub =bpf_find_containing_subprog (env, t); + changed |= !sub->might_throw; + sub->might_throw = true; + }if (bpf_is_ldimm64 (&insn[t])) t++; } + changed |= throws != taken_throws; }while (changed); }
kernel/bpf/core.c +1 -0
@@ -295,6 +295,7 @@ void __bpf_prog_free(struct bpf_prog *fp)mutex_destroy (&fp->aux->dst_mutex);kfree (fp->aux->poke_tab);free_percpu (fp->aux->timed_may_goto); +kvfree (fp->aux->frame_desc_sites);kfree (fp->aux); }free_percpu (fp->stats);
kernel/bpf/fixups.c +7 -1
@@ -2047,9 +2047,15 @@ static int jit_subprogs(struct bpf_verifier_env *env) func[i]->aux->nr_linfo = prog->aux->nr_linfo; func[i]->aux->jited_linfo = prog->aux->jited_linfo; func[i]->aux->linfo_idx = env->subprog_info[i].linfo_idx; -/* The pool is freed with the main prog. */ +/* The pool is freed with the main prog, the sites with func[i]. */ func[i]->aux->frame_desc = env->subprog_info[i].frame_desc; func[i]->aux->frame_desc_pool = env->frame_desc_pool; + func[i]->aux->frame_desc_sites =bpf_frame_desc_sites (env, i); +if (IS_ERR (func[i]->aux->frame_desc_sites)) { + err =PTR_ERR (func[i]->aux->frame_desc_sites); + func[i]->aux->frame_desc_sites =NULL ; + goto out_free; + } func[i]->aux->arena = prog->aux->arena; func[i]->aux->arena_stack_size = prog->aux->arena_stack_size; func[i]->aux->arena_stack_entry = env->subprog_info[i].arena_stack_entry;
kernel/bpf/frame_desc.c +488 -115
733 lines not on this page · raw message
kernel/bpf/join.c +2 -1
@@ -16,7 +16,8 @@* * Inputs have to be at the same insn and cleaned with * bpf_clean_verifier_state(): dead registers and stack slots are the same on -* both sides then, but for dynptrs, iterators and irq flags: they stay. +* both sides then, but for dynptrs, iterators and irq flags: they stay. So +* does what a program that may throw keeps for it, see bpf_frame_desc_keeps(). */ #include <linux/bpf.h> #include <linux/bpf_verifier.h>
kernel/bpf/states.c +7 -1
@@ -129,6 +129,7 @@ static bool check_scalar_ids(u32 old_id, u32 cur_id, struct bpf_idmap *idmap) }static void __clean_func_state (struct bpf_verifier_env *env, +const struct bpf_verifier_state *vst,struct bpf_func_state *st,u16 live_regs,int frame) {
@@ -168,6 +169,11 @@ static void __clean_func_state(struct bpf_verifier_env *env, stype == STACK_IRQ_FLAG)continue ; +/* a throw finds what the frame holds through it, see frame_desc.c */ +if (stype ==STACK_SPILL && +bpf_frame_desc_keeps (env, vst, frame, &bpf_stack_slot (st, i)->spilled_ptr)) +continue ; +/* * Only scalar spills can be degraded to raw stack bytes * when their high half is dead. Pointer spills need the
@@ -209,7 +215,7 @@ int bpf_clean_verifier_state(struct bpf_verifier_env *env, struct bpf_verifier_su32 ip =bpf_frame_insn_idx (st, i);u16 live_regs = env->insn_aux_data[ip].live_regs_before; -__clean_func_state (env, st->frame[i], live_regs, i); +__clean_func_state (env, st, st->frame[i], live_regs, i); }return 0 ; }
kernel/bpf/unwind.c +119 -37
189 lines not on this page · raw message
kernel/bpf/verifier.c +19 -3
@@ -3727,6 +3727,7 @@ static void check_fastcall_stack_contract(struct bpf_verifier_env *env,* fastcall_stack_off to a value smaller than any possible offset. */ subprog->fastcall_stack_off =S16_MIN ; + subprog->fastcall_stack_end =S16_MIN ;/* reset fastcall aux flags within subprogram, * happens at most once per subprogram */
@@ -11847,6 +11848,18 @@ static int call_func_on_its_own(struct bpf_verifier_env *env, int subprog, int iinherited_id (state, callee->stack_arg_regs[i].map_uid); } +/* +* A throw of the function finds the frames of the caller as they are +* now: it has nothing that points to their stack. +*/ +if (env->subprog_info[subprog].might_throw) { + err =bpf_frame_desc_record_site (env, state, insn_idx); +if (err) { +bpf_free_verifier_state (&entry, false); +return err; + } + } +account_current_path (env); env->subprog_info[subprog].on_its_own = true; err =bpf_flow_call (env, state, &entry, subprog, insn_idx);
@@ -20836,6 +20849,7 @@ static void mark_fastcall_pattern_for_call(struct bpf_verifier_env *env,else subprog->keep_fastcall_stack =1 ; subprog->fastcall_stack_off =min (subprog->fastcall_stack_off, off); + subprog->fastcall_stack_end =max (subprog->fastcall_stack_end, off); }static int mark_fastcall_patterns (struct bpf_verifier_env *env)
@@ -20846,6 +20860,7 @@ static int mark_fastcall_patterns(struct bpf_verifier_env *env)int s, i;for (s =0 ; s < env->subprog_cnt; ++s, ++subprog) { + subprog->fastcall_stack_end =S16_MIN ;/* find lowest stack spill offset used in this subprog */ lowest_off =0 ;for (i = subprog->start; i < (subprog +1 )->start; ++i) {
@@ -21072,7 +21087,8 @@ static int process_bpf_exit_full(struct bpf_verifier_env *env,* for exceptional exits. */ if (exception_exit) -return PROCESS_BPF_EXIT ; +return bpf_frame_desc_record_site (env, env->cur_state, env->insn_idx) ?: +PROCESS_BPF_EXIT ;if (env->cur_state->curframe) {/* exit from nested function */
@@ -23742,8 +23758,8 @@ static void print_verification_stats(struct bpf_verifier_env *env) env->max_states_per_insn, env->total_states, env->peak_states);if (env->log.level &BPF_LOG_STATS ) -verbose (env,"frame descriptors: slots %u insns %u classes %u bytes %u\n" , - env->frame_desc_slots, env->frame_desc_insns, +verbose (env,"frame descriptors: slots %u insns %u sites %u classes %u bytes %u\n" , + env->frame_desc_slots, env->frame_desc_insns, env->frame_desc_sites, env->frame_desc_pool ? env->frame_desc_pool->resource_cnt : env->resource_cnt, env->frame_desc_bytes); }
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 15/29 bpf: Describe handles in registers at throw sites
In reply to Kumar Kartikeya DwivediCurrently a site descriptor can only give locations on the stack. But a compiler keeps a reference that lives across calls in R6-R9 unless it runs out of them, and leaves it in whichever register it was in at a bpf_throw(), which does not return. Such a reference takes a hidden slot, with a store on acquire and a compare on release.
The unwinder has the registers of the throw site from the native entry of bpf_throw(), and recovers the callee-saved registers of each frame for the exception callback, see bpf_throw_caller_regs(). Do the same in the walks that release resources, so that they have the registers of the frame being unwound:
- In the frame that called bpf_throw(), R0-R9 are the values that the entry captured.
- When the walk moves from a frame to its caller, R6-R9 keep their values, except for those that the prologue of the frame saved, which are loaded from where it saved them. R0-R5 are unknown from then on.
Add BPF_FRAME_ENT_REG for a descriptor entry that gives a register, and have the verifier record registers at a site along with stack slots:
- R6-R9 of every frame in the state;
- R0 and R2-R5 of the frame that calls bpf_throw(). R1 holds the cookie. For this, the site is recorded before the call clobbers the caller-saved registers.
A register is described like a stack slot: when every visit to the site finds the handle in it. If both hold the handle, the stack slot is preferred. Locks and stack objects are handled as before.
The verifier forgets the contents of a dead register where paths join, as it does for stack slots. So a register that points to a resource held by its frame is kept like such a slot: it is not cleaned, a state can stand for another only if both have the same contents in it, and a join keeps it only if both sides have it. Register liveness does not change.
The program no longer uses such a register, but inserted code must not reuse it either. The expansion of a timed may_goto keeps the address of its count in a dead register: make it pick one that no site describes.
Registers are described only on architectures with a native entry, x86-64 and arm64.
--- include/linux/bpf.h | 11 +++- include/linux/bpf_verifier.h | 14 +++-- kernel/bpf/fixups.c | 3 +- kernel/bpf/frame_desc.c | 103 ++++++++++++++++++++++++++++------- kernel/bpf/helpers.c | 2 +- kernel/bpf/join.c | 3 + kernel/bpf/states.c | 10 +++- kernel/bpf/unwind.c | 29 ++++++++-- kernel/bpf/verifier.c | 13 ++++- 9 files changed, 151 insertions(+), 37 deletions(-)
include/linux/bpf.h +9 -2
@@ -1873,6 +1873,11 @@ struct bpf_resource {* slot of the frame, or a slot of the program's own stack that holds the * pointer whenever the frame is at the site of the descriptor; the pointer * may then point adj bytes into the object; +* - with BPF_FRAME_ENT_REG a register instead of a location, which holds +* such a pointer whenever the frame is at the site: off is its number. The +* unwinder has R6 to R9 of every frame, and R0 to R5 of the one that +* called bpf_throw(), where the architecture captures them, see +* bpf_arch_throw_entry(); * - with BPF_FRAME_ENT_OBJ the resource itself, an iterator or saved IRQ * flags the frame has on its stack whenever it is at the site; * - with BPF_FRAME_ENT_COUNTER a hidden slot that holds a count of sections,
@@ -1885,12 +1890,14 @@ struct bpf_resource {#define BPF_FRAME_ENT_COUNT BIT(2)/* Released before the other entries of the frame, which it may be using. */ #define BPF_FRAME_ENT_FIRST BIT(3) +#define BPF_FRAME_ENT_REG BIT(4)struct bpf_frame_desc_entry {u16 res;/* index into the program's resource classes */ u16 flags;/* BPF_FRAME_ENT_* */ union {s16 off;/* the location, relative to the frame pointer */ +s16 reg;/* BPF_FRAME_ENT_REG */ s16 count;/* BPF_FRAME_ENT_COUNT */ };s16 adj;
@@ -4631,7 +4638,7 @@ void bpf_prog_update_insn_ptrs(struct bpf_prog *prog, u32 *offsets, void *image)void bpf_prog_fill_frame_desc_sites (struct bpf_prog *prog,const u32 *offsets,void *image);void bpf_unwind_release (const struct bpf_resource *res,void *handle);void bpf_dynptr_file_free (void *reader); -void bpf_unwind_release_frames (void ); +void bpf_unwind_release_frames (const struct bpf_throw_regs *regs);void bpf_frame_desc_pool_free (struct bpf_frame_desc_pool *pool);#else static inline void
@@ -4645,7 +4652,7 @@ bpf_prog_fill_frame_desc_sites(struct bpf_prog *prog, const u32 *offsets, void *static inline void bpf_frame_desc_pool_free (struct bpf_frame_desc_pool *pool) { } -static inline void bpf_unwind_release_frames (void ) +static inline void bpf_unwind_release_frames (const struct bpf_throw_regs *regs) { }#endif
include/linux/bpf_verifier.h +10 -4
@@ -1273,8 +1273,8 @@ struct bpf_res_acq {bool slot;/* and some site cannot say where: the handle is kept in a slot */ }; -/* How many places of its stack a site remembers a frame keeping one handle in. */ -#define BPF_RES_LOC_MAX 3 +/* How many places a site remembers a frame keeping one handle in. */ +#define BPF_RES_LOC_MAX 4/* * A site: a call a frame can be unwound at, bpf_throw() or the call of a
@@ -1292,7 +1292,8 @@ struct bpf_res_site {/* * Where the frame at a site keeps the handle of what an acquisition acquired: -* the slots of its stack that every visit holding it found the handle in. +* the slots of its stack and the registers that every visit holding it found +* the handle in. */ struct bpf_res_loc {u32 site;/* index of the site */
@@ -1301,8 +1302,9 @@ struct bpf_res_loc {u8 cnt;/* the places left */ bool obj;/* they are the resource itself, an object on the stack */ struct { -s16 off;/* relative to R10 */ +s16 off;/* relative to R10, or the number of the register */ s16 adj;/* the pointer there points that many bytes into the object */ +bool reg; } at[BPF_RES_LOC_MAX ]; };
@@ -1374,6 +1376,8 @@ struct bpf_verifier_env {u32 frame_desc_insns;/* instructions keeping them up to date */ u32 frame_desc_bytes;/* descriptors and resource classes */ bool describe_frames;/* frame descriptors are generated: the program may throw */ +bool describe_regs;/* and may name registers, see bpf_arch_throw_entry() */ +u16 frame_desc_regs;/* the registers that some site names */ int exception_callback_subprog;bool explore_alu_limits;bool allow_ptr_leaks;
@@ -2332,6 +2336,8 @@ int bpf_frame_desc_record_section(struct bpf_verifier_env *env, u8 kind, s8 deltint bpf_frame_desc_record_exit (struct bpf_verifier_env *env);bool bpf_frame_desc_keeps (const struct bpf_verifier_env *env,const struct bpf_verifier_state *st,int f,const struct bpf_reg_state *reg); +u16 bpf_frame_desc_kept_regs (const struct bpf_verifier_env *env,const struct bpf_verifier_state *st, +int f);int bpf_frame_desc_record_site (struct bpf_verifier_env *env,struct bpf_verifier_state *st,int insn_idx); int bpf_frame_desc_record_return(struct bpf_verifier_env *env, struct bpf_verifier_state *st,
kernel/bpf/fixups.c +2 -1
@@ -2985,7 +2985,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) }if (bpf_is_may_goto_insn (insn) && timed_may_goto) { -u16 live = env->insn_aux_data[i].live_regs_before; +/* a site of a program that throws reads a register the program is done with */ +u16 live = env->insn_aux_data[i].live_regs_before | env->frame_desc_regs;int reg, save_off =0 ;/* a register that is dead here holds the address of the count */
kernel/bpf/frame_desc.c +84 -19
176 lines not on this page · raw message
kernel/bpf/helpers.c +1 -1
@@ -3493,7 +3493,7 @@ notrace void bpf_throw_unwind(struct bpf_throw_regs *regs, u64 cookie)if (regs)memcpy (ctx.saved, ®s->r[BPF_REG_6 ],sizeof (ctx.saved));/* What the frames about to be discarded hold is released first. */ -bpf_unwind_release_frames (); +bpf_unwind_release_frames (regs);arch_bpf_stack_walk (bpf_stack_walker, &ctx);WARN_ON_ONCE (!ctx.aux);if (ctx.aux)
kernel/bpf/join.c +3 -0
@@ -663,6 +663,9 @@ int bpf_states_join(struct bpf_verifier_env *env, struct bpf_verifier_state *dstu16 live_regs = env->insn_aux_data[insn_idx].live_regs_before;u32 cnt; +/* the registers both keep for a throw are joined, see bpf_frame_desc_keeps() */ + live_regs |=bpf_frame_desc_kept_regs (env, a, fr) & +bpf_frame_desc_kept_regs (env, b, fr);/* not the states that get to the head through a call of the function */ frame = a->frame[fr]; cnt = frame->loop_stack_cnt;
kernel/bpf/states.c +7 -3
@@ -215,6 +215,8 @@ int bpf_clean_verifier_state(struct bpf_verifier_env *env, struct bpf_verifier_su32 ip =bpf_frame_insn_idx (st, i);u16 live_regs = env->insn_aux_data[ip].live_regs_before; +/* a throw finds what the frame holds through them, see frame_desc.c */ + live_regs |=bpf_frame_desc_kept_regs (env, st, i);__clean_func_state (env, st, st->frame[i], live_regs, i); }return 0 ;
@@ -691,9 +693,10 @@ static bool loop_stack_safe(struct bpf_verifier_env *env, struct bpf_func_state* the type in the current state is meaningful. */ static bool func_states_equal (struct bpf_verifier_env *env,struct bpf_func_state *old, -struct bpf_func_state *cur,u32 insn_idx) +struct bpf_func_state *cur,u32 insn_idx,u16 kept) { -u16 live_regs = env->insn_aux_data[insn_idx].live_regs_before; +/* and the registers that @old keeps for a throw, see bpf_frame_desc_keeps() */ +u16 live_regs = env->insn_aux_data[insn_idx].live_regs_before | kept;u16 i;if (old->callback_depth > cur->callback_depth)
@@ -781,7 +784,8 @@ bool bpf_state_subsumes(struct bpf_verifier_env *env, struct bpf_verifier_state insn_idx =bpf_frame_insn_idx (old, i);if (old->frame[i]->callsite != cur->frame[i]->callsite)return false; -if (!func_states_equal (env, old->frame[i], cur->frame[i], insn_idx)) +if (!func_states_equal (env, old->frame[i], cur->frame[i], insn_idx, +bpf_frame_desc_kept_regs (env, old, i)))return false; }
kernel/bpf/unwind.c +24 -5
@@ -179,6 +179,13 @@ struct bpf_unwind_ctx {/* The sections the frames hold, per kind, summed by the first walk. */ const struct bpf_resource *section[BPF_UNWIND_NR_SECTIONS ];s64 nr_sections[BPF_UNWIND_NR_SECTIONS ]; +/* +* The registers of the frame the walk is at, when the entry of +* bpf_throw() captured those of the throw site: the callee-saved ones, +* and R0 to R5 while the walk is at the frame that threw. +*/ +const struct bpf_throw_regs *entry; +struct bpf_throw_regs regs; };/*
@@ -212,6 +219,7 @@ static void bpf_unwind_release_desc(struct bpf_unwind_ctx *ctx, const struct bpfconst struct bpf_frame_desc_entry *ent = &d->ent[i];const struct bpf_resource *res = &pool->resources[ent->res];u64 *loc = (u64 *)(long )(fp + ent->off); +u64 val;if (ent->flags & (BPF_FRAME_ENT_COUNTER |BPF_FRAME_ENT_COUNT )) {if (!locks || first || late)
@@ -226,9 +234,13 @@ static void bpf_unwind_release_desc(struct bpf_unwind_ctx *ctx, const struct bpfif (ent->flags &BPF_FRAME_ENT_OBJ ) { handle = loc; }else { -if (!*loc) +/* no descriptor names a register that was not captured */ +if ((ent->flags &BPF_FRAME_ENT_REG ) &&WARN_ON_ONCE (!ctx->entry)) +continue ; + val = ent->flags &BPF_FRAME_ENT_REG ? ctx->regs.r[ent->reg] : *loc; +if (!val)continue ; - handle = (void *)(long )(*loc - ent->adj); + handle = (void *)(long )(val - ent->adj); }if (locks && late != (res->kind ==BPF_RESOURCE_IRQ_FLAG && !arch_irqs_disabled_flags (*(unsigned long *)handle)))
@@ -290,6 +302,8 @@ static bool bpf_unwind_walker(void *cookie, u64 ip, u64 sp, u64 bp)return !ctx->cnt; ctx->cnt++;bpf_unwind_release_frame (ctx, prog, ip, bp); +/* the walk leaves the frame for its caller, which has R6 to R9 as it left them */ +bpf_throw_caller_regs (bpf_prog_frame_aux (prog), bp, &ctx->regs.r[BPF_REG_6 ]);/* the main program is where the exception callback takes over */ return bpf_is_subprog (prog); }
@@ -302,16 +316,21 @@ static bool bpf_unwind_walker(void *cookie, u64 ip, u64 sp, u64 bp)* each frame's entries of the phase in the order of its descriptor; the * frames are released as the walk passes them, so their number is not * bounded. The sections need no walk of their own: the first one summed -* their counts, and sections of one kind are all the same. +* their counts, and sections of one kind are all the same. @regs are the +* registers of the frame that called bpf_throw(), or NULL when the +* architecture does not capture them. */ -void bpf_unwind_release_frames (void ) +void bpf_unwind_release_frames (const struct bpf_throw_regs *regs) { -struct bpf_unwind_ctx ctx = {}; +struct bpf_unwind_ctx ctx = { .entry = regs };int s;for (ctx.phase =0 ; ctx.phase <BPF_UNWIND_NR_PHASES ; ctx.phase++) {if (ctx.phase !=BPF_UNWIND_PHASE_SECTIONS ) { ctx.cnt =0 ; +/* every walk starts at the throw site */ +if (regs) + ctx.regs = *regs;arch_bpf_stack_walk (bpf_unwind_walker, &ctx);continue ; }
kernel/bpf/verifier.c +11 -2
@@ -12217,6 +12217,10 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,verbose (env,"failed to push state for global subprog exception path\n" );return PTR_ERR (branch); } +/* a throw of the subprog finds the frames as they are at the call */ + err =bpf_frame_desc_record_site (env, env->cur_state, *insn_idx); +if (err) +return err;return process_bpf_exit_full (env,NULL , true); }
@@ -16477,6 +16481,10 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,if (err <0 )return err; } +/* with the registers the frame calls it with, which the call clobbers */ + err =bpf_frame_desc_record_site (env, env->cur_state, insn_idx); +if (err) +return err; }bpf_diag_record_caller_saved (env, regs);
@@ -21087,8 +21095,7 @@ static int process_bpf_exit_full(struct bpf_verifier_env *env,* for exceptional exits. */ if (exception_exit) -return bpf_frame_desc_record_site (env, env->cur_state, env->insn_idx) ?: -PROCESS_BPF_EXIT ; +return PROCESS_BPF_EXIT ;if (env->cur_state->curframe) {/* exit from nested function */
@@ -25710,6 +25717,8 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,*/ for (i =0 ;bpf_jit_supports_exception_cleanup () && i < env->subprog_cnt; i++) env->describe_frames |= env->subprog_info[i].might_throw; +/* the unwinder has the registers of the frames where bpf_throw() has a native entry */ + env->describe_regs = env->describe_frames &&bpf_arch_throw_entry (); ret =check_unwind_insns (env);if (ret <0 )
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 16/29 bpf: Describe locks at throw sites
In reply to Kumar Kartikeya DwivediCurrently no site describes a lock, so every lock that a throw finds held is kept in a hidden slot. Its address is stored before the call that takes the lock, and cleared before the unlock.
A lock does not move, so a site can describe it:
- The lock of a global variable, or of the only element of any other array map that the program refers to, is at an address known at verification time. Its resource class carries the address. The site entry is marked BPF_FRAME_ENT_FIXED and reads nothing from the frame. This does not apply to an inner map: the map that stands for it during verification has no values.
- Any other lock is found through a pointer to the map value or allocated object that contains it. Such a pointer has the same id as the lock. The acquisition records the offset of the lock in the object, and the site gives a stack slot or a register that holds such a pointer, with the difference as its adjustment. A lock whose offset does not fit in an adjustment is not found through a pointer.
A lock at a fixed address then costs the program nothing when every visit to a site finds it held. Any other lock costs nothing when, in addition, the frame has a pointer to the containing object in the same location on every visit. A lock that some visits to a site find held and others do not still takes a hidden slot.
--- include/linux/bpf.h | 4 ++ include/linux/bpf_verifier.h | 2 + kernel/bpf/frame_desc.c | 77 +++++++++++++++++++++++++++++------- kernel/bpf/unwind.c | 2 + 4 files changed, 70 insertions(+), 15 deletions(-)
include/linux/bpf.h +4 -0
@@ -1860,6 +1860,7 @@ struct bpf_resource {union {void (*dtor)(void *);struct btf_struct_meta *meta; +void *addr;/* a lock at a fixed address, in the only element of an array map */ }; };
@@ -1880,6 +1881,8 @@ struct bpf_resource {* bpf_arch_throw_entry(); * - with BPF_FRAME_ENT_OBJ the resource itself, an iterator or saved IRQ * flags the frame has on its stack whenever it is at the site; +* - with BPF_FRAME_ENT_FIXED no location: a lock at the address its class +* gives, which the frame holds whenever it is at the site; * - with BPF_FRAME_ENT_COUNTER a hidden slot that holds a count of sections, * signed: the sections the frame entered less those of its callers it left; * - with BPF_FRAME_ENT_COUNT no location: the count of the sections the frame
@@ -1891,6 +1894,7 @@ struct bpf_resource {/* Released before the other entries of the frame, which it may be using. */ #define BPF_FRAME_ENT_FIRST BIT(3)#define BPF_FRAME_ENT_REG BIT(4) +#define BPF_FRAME_ENT_FIXED BIT(5)struct bpf_frame_desc_entry {u16 res;/* index into the program's resource classes */
include/linux/bpf_verifier.h +2 -0
@@ -1253,6 +1253,7 @@ void bpf_patch_image_install(struct bpf_prog *prog, const struct bpf_patch_image#define BPF_RES_F_MAY_FAIL BIT(1)/* acquired only when the call returns 0 */ #define BPF_RES_F_DEREF BIT(2)/* the handle is the first word of a dynptr on the stack */ #define BPF_RES_F_CTX BIT(3)/* a __ref argument the kernel passes the main program */ +#define BPF_RES_F_NO_PTR BIT(5)/* a lock that no pointer to what it is in leads to */ /* * A call that acquires a resource, see frame_desc.c. When a frame can be
@@ -1301,6 +1302,7 @@ struct bpf_res_loc {u16 acq;/* index of the acquisition */ u8 cnt;/* the places left */ bool obj;/* they are the resource itself, an object on the stack */ +bool fixed;/* it is a lock at the address of its class, and needs no place */ struct {s16 off;/* relative to R10, or the number of the register */ s16 adj;/* the pointer there points that many bytes into the object */
kernel/bpf/frame_desc.c +62 -15
147 lines not on this page · raw message
kernel/bpf/unwind.c +2 -0
@@ -233,6 +233,8 @@ static void bpf_unwind_release_desc(struct bpf_unwind_ctx *ctx, const struct bpfcontinue ;if (ent->flags &BPF_FRAME_ENT_OBJ ) { handle = loc; + }else if (ent->flags &BPF_FRAME_ENT_FIXED ) { + handle = res->addr; }else {/* no descriptor names a register that was not captured */ if ((ent->flags &BPF_FRAME_ENT_REG ) &&WARN_ON_ONCE (!ctx->entry))
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 17/29 bpf: Apply hidden slot limits only when a slot is needed
In reply to Kumar Kartikeya DwivediThe instructions that maintain a hidden slot are emitted once per call, and a slot holds one handle. Currently a program that throws is rejected for anything that these instructions cannot handle, as soon as the main pass sees it:
- an instruction that acquires resources of different types, or takes their handle in different ways, on different paths;
- an acquisition whose handle is not in a register;
- a release whose handle is not in an argument register, is in a register that does not point to the released resource, or is taken in different ways on different paths;
- a frame that holds two resources acquired by the same instruction.
None of this matters for a resource that gets no hidden slot, either because its sites describe it or because no throw finds it held. Such a program loads if it contains no throw, and a throw elsewhere in the program is no reason to reject it.
Record these cases during the pass, and reject them after it, only for the resources that need a hidden slot:
- An instruction gets one acquisition for each way it acquires.
- A release records why its handle cannot be cleared from a slot.
- An acquisition records that a frame held two of its resources.
Two resources of one instruction that a throw finds held have no location that all visits agree on, so they always need a slot. That case loads only if no throw finds them held.
The messages do not change.
--- include/linux/bpf_verifier.h | 9 ++ kernel/bpf/frame_desc.c | 182 ++++++++++++++++++++++++----------- 2 files changed, 137 insertions(+), 54 deletions(-)
include/linux/bpf_verifier.h +9 -0
@@ -1272,6 +1272,7 @@ struct bpf_res_acq {s16 off;/* the slot, relative to R10 */ bool held;/* some frame holds what it acquired at a site */ bool slot;/* and some site cannot say where: the handle is kept in a slot */ +bool multi;/* a frame held two of what it acquired at once: one slot cannot */ };/* How many places a site remembers a frame keeping one handle in. */
@@ -1317,8 +1318,16 @@ struct bpf_res_rel {u8 reg;/* the argument the call releases */ u8 flags;/* BPF_RES_F_DEREF */ s16 adj;/* the argument points that many bytes into the object */ +u8 bad;/* BPF_RES_REL_*: why no insns can clear a slot here */ }; +/* The handle is in no argument register. */ +#define BPF_RES_REL_NO_REG 1 +/* The register does not point to what is released. */ +#define BPF_RES_REL_NOT_PTR 2 +/* The call takes the handle in different ways on different paths. */ +#define BPF_RES_REL_DIFFER 3 +/* A call that enters or leaves a section, for the count of its frame, see frame_desc.c. */ struct bpf_section_insn {u32 insn;
kernel/bpf/frame_desc.c +128 -54
267 lines not on this page · raw message
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 18/29 bpf: Allow a callee to leave resources with its caller
In reply to Kumar Kartikeya DwivediA hidden slot belongs to the frame that made the acquiring call. Currently a program that throws is rejected whenever a resource ends up held by another frame:
- a subprog returns holding anything other than a reference in R0, for example a lock that it took for its caller;
- a subprog returns in more or fewer RCU read or preemption disabled sections than it was called in;
- a callee creates an iterator or saves IRQ flags on its caller's stack.
These programs are rejected even if no throw can find the resource held, although they load today. An example is one subprog that takes a lock and another that drops it, with an assertion elsewhere in the program.
Reject them only if a hidden slot is needed:
- A resource that a subprog returns holding belongs to its caller from then on, under the acquisition that the subprog made. That acquisition cannot have a hidden slot. The program loads if the caller's sites describe the resource, or if no throw finds it held.
- A stack object belongs to the frame whose stack contains it, whichever frame created it. Its sites find it there, so it needs no slot unless some visits to a site find it held and others do not.
- A callee that returns in different sections than it was called in leaves the difference to its caller, whose sites give the number of sections. The caller then cannot count its sections in a hidden slot. A function verified on its own reports this to its caller in bpf_flow_return().
A lock at a fixed address, an iterator and saved IRQ flags are always where a site finds them. Subprogs that take and drop these for their callers therefore load if every visit to a throw finds them held, or none does.
--- include/linux/bpf_verifier.h | 15 ++++ kernel/bpf/frame_desc.c | 160 +++++++++++++++++++++++------------ kernel/bpf/join.c | 2 +- kernel/bpf/states.c | 2 +- kernel/bpf/verifier.c | 4 + 5 files changed, 125 insertions(+), 58 deletions(-)
include/linux/bpf_verifier.h +15 -0
@@ -1112,6 +1112,12 @@ struct bpf_subprog_info { bool stack_args_from_code: 1;/* it releases what its caller holds, so it's verified in the context of such caller */ bool releases_inherited: 1; +/* +* The kinds of sections, a bit each, that a callee returned to it in +* more or fewer of than it called the callee in: a count it keeps in a +* slot does not follow that, see frame_desc.c. +*/ +u8 section_delta;/* it loads the end of the packet from the context */ bool loads_pkt_end: 1;/* a call of it was verified on its own: its insns are not in insns_total of the caller */
@@ -1272,6 +1278,13 @@ struct bpf_res_acq {s16 off;/* the slot, relative to R10 */ bool held;/* some frame holds what it acquired at a site */ bool slot;/* and some site cannot say where: the handle is kept in a slot */ +/* +* An insn, plus one, that leaves what it acquired with another frame +* than the one that ran the acquisition: the call a subprog returns +* holding it from, or the acquisition itself when it creates a stack +* object in the stack of a caller. A slot cannot follow it there. +*/ +u32 elsewhere;bool multi;/* a frame held two of what it acquired at once: one slot cannot */ };
@@ -2345,6 +2358,8 @@ int bpf_frame_desc_record_release(struct bpf_verifier_env *env, int idx);int bpf_frame_desc_record_ctx_arg (struct bpf_verifier_env *env,int idx,int argno);int bpf_frame_desc_record_section (struct bpf_verifier_env *env,u8 kind,s8 delta);int bpf_frame_desc_record_exit (struct bpf_verifier_env *env); +void bpf_frame_desc_record_sections (struct bpf_verifier_env *env,int subprog,bool rcu, +bool preempt);bool bpf_frame_desc_keeps (const struct bpf_verifier_env *env,const struct bpf_verifier_state *st,int f,const struct bpf_reg_state *reg); u16 bpf_frame_desc_kept_regs(const struct bpf_verifier_env *env, const struct bpf_verifier_state *st,
kernel/bpf/frame_desc.c +104 -56
263 lines not on this page · raw message
kernel/bpf/join.c +1 -1
@@ -463,7 +463,7 @@ static bool join_func(struct bpf_join_state *js, struct bpf_func_state *fd, fa->entry_rcu_locks != fb->entry_rcu_locks || fa->entry_preempt_locks != fb->entry_preempt_locks)return false; -/* a frame that left a section of its callers hands its count over, see frame_desc.c */ +/* the sections a frame left of those it was entered in matter to a throw, see frame_desc.c */ if (env->describe_frames && (fa->rcu_released_below != fb->rcu_released_below || fa->preempt_released_below != fb->preempt_released_below))return false;
kernel/bpf/states.c +1 -1
@@ -717,7 +717,7 @@ static bool func_states_equal(struct bpf_verifier_env *env, struct bpf_func_stat old->entry_rcu_locks != cur->entry_rcu_locks || old->entry_preempt_locks != cur->entry_preempt_locks)return false; -/* a frame that left a section of its callers hands its count over, see frame_desc.c */ +/* the sections a frame left of those it was entered in matter to a throw, see frame_desc.c */ if (env->describe_frames && (old->rcu_released_below != cur->rcu_released_below || old->preempt_released_below != cur->preempt_released_below))return false;
kernel/bpf/verifier.c +4 -0
@@ -12028,6 +12028,10 @@ int bpf_flow_return(struct bpf_verifier_env *env, struct bpf_verifier_state *cal } caller->active_irq_id =fresh_id (env, idmap, caller->active_irq_id); caller->active_lock_id =fresh_id (env, idmap, caller->active_lock_id); +/* the sections the function entered and did not leave are the caller's, see frame_desc.c */ +bpf_frame_desc_record_sections (env, frame->subprogno, + caller->active_rcu_locks != caller_rcu, + caller->active_preempt_locks != caller_preempt);/* fresh_id() had no room for an id, or there are no ids left */ if (idmap->cnt ==BPF_ID_MAP_SIZE || env->id_gen >=BPF_MAX_ID ) {verbose (env,"too many ids at the call at insn %d\n" , call_idx);
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 19/29 bpf: Allow a callee to release its caller's resources
In reply to Kumar Kartikeya DwivediCurrently a program that throws is rejected when a callee releases a resource held by its caller. The handle could be in a hidden slot of the caller, and the callee does not know where that slot is.
Handle the two cases separately.
If the resource has no hidden slot, nothing needs to be cleared. A site of the caller describes the resource if every visit finds it held, and omits it if none does.
If the resource has a hidden slot, the callee cannot clear it, but the caller can, before the call. This is correct when the slot is not needed from the call on:
- the resource is released on every return from the call, and
- no throw finds the caller holding it at the call. That is, the callee does not throw before it releases the resource.
For each call to a subprog and each acquisition of the caller, record whether a frame above the caller released the resource during the call, and whether any return from the call found the caller still holding it. After the pass, a resource with a hidden slot that a callee releases has the slot cleared before every such call. The program is rejected if one of these calls does not allow it.
A subprog that releases resources of its callers must be verified in the context of each caller, so that a throw sees the frames below it as they are. The verifier normally learns this when the subprog returns, but a subprog that throws after the release never returns. So mark the functions between the owner and the releasing frame at the release itself.
Setting the mark invalidates what the current round of the main pass has recorded so far: the frames of the callers at the call, still holding their resources. So the round is run again when a mark is set, see do_check_all().
This allows a subprog that unlocks a lock of its caller, and one that releases a resource that the caller's sites describe.
Still rejected, for a resource that the caller keeps in a hidden slot:
- a callee that may throw before it releases the resource;
- a callee that releases the resource only on some paths;
- a callback that releases the resource, since its helper may never call it;
- a callx to a subprog that releases the resource.
--- include/linux/bpf_verifier.h | 23 +++++ kernel/bpf/frame_desc.c | 158 ++++++++++++++++++++++++++++++++--- kernel/bpf/verifier.c | 6 +- 3 files changed, 173 insertions(+), 14 deletions(-)
include/linux/bpf_verifier.h +23 -0
@@ -1278,6 +1278,7 @@ struct bpf_res_acq {s16 off;/* the slot, relative to R10 */ bool held;/* some frame holds what it acquired at a site */ bool slot;/* and some site cannot say where: the handle is kept in a slot */ +u32 foreign;/* an insn of another frame that releases what it acquired, plus one */ /* * An insn, plus one, that leaves what it acquired with another frame * than the one that ran the acquisition: the call a subprog returns
@@ -1341,6 +1342,20 @@ struct bpf_res_rel {/* The call takes the handle in different ways on different paths. */ #define BPF_RES_REL_DIFFER 3 +/* +* A call of a subprog during which a frame above the caller releases what an +* acquisition of the caller acquired. The caller clears its slot in front of +* the call when no throw finds it holding the resource there and no return +* from the call does, see frame_desc.c. +*/ +struct bpf_res_call { +u32 insn;/* the call; U32_MAX when the caller is not in the state */ +u16 acq;/* index of the acquisition */ +bool released; +bool held_after;/* some return from the call finds the caller holding it */ +bool clear;/* decided once the pass is over */ +}; +/* A call that enters or leaves a section, for the count of its frame, see frame_desc.c. */ struct bpf_section_insn {u32 insn;
@@ -1390,6 +1405,8 @@ struct bpf_verifier_env {u32 res_rel_cnt;struct bpf_section_insn *section_insns;u32 section_insn_cnt; +struct bpf_res_call *res_calls; +u32 res_call_cnt;struct bpf_res_site *res_sites;u32 res_site_cnt;struct bpf_res_loc *res_locs;
@@ -1402,6 +1419,12 @@ struct bpf_verifier_env {bool describe_frames;/* frame descriptors are generated: the program may throw */ bool describe_regs;/* and may name registers, see bpf_arch_throw_entry() */ u16 frame_desc_regs;/* the registers that some site names */ +/* +* The round of the main pass found out that a subprog releases what +* its callers hold: they are recorded differently then, so the round +* is done again, see do_check_all(). +*/ +bool frame_desc_redo;int exception_callback_subprog;bool explore_alu_limits;bool allow_ptr_leaks;
kernel/bpf/frame_desc.c +145 -13
258 lines not on this page · raw message
kernel/bpf/verifier.c +5 -1
@@ -11998,6 +11998,9 @@ int bpf_flow_return(struct bpf_verifier_env *env, struct bpf_verifier_state *calif (env->log.level &BPF_LOG_LEVEL2 )verbose (env,"func#%d releases what its caller holds at insn %d\n" , callee->subprogno, call_idx); +/* what the round recorded of the function so far does not hold, see frame_desc.c */ +if (env->describe_frames && !env->subprog_info[callee->subprogno].releases_inherited) + env->frame_desc_redo = true; env->subprog_info[callee->subprogno].releases_inherited = true;return 1 ; }
@@ -22273,7 +22276,8 @@ static int do_check_block(struct bpf_verifier_env *env, int prev_insn_idx)/* All is done again, whatever came out, see do_check_all() */ static bool round_is_dropped (struct bpf_verifier_env *env) { -return (env->restart &&bpf_flow_loops_lost_bound (env)) || env->guards_late; +return (env->restart &&bpf_flow_loops_lost_bound (env)) || env->guards_late || + env->frame_desc_redo; }static int do_check (struct bpf_verifier_env *env)
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 20/29 bpf: Transfer a reference to the callee that releases it
In reply to Kumar Kartikeya DwivediCurrently a callee can release a reference that its caller keeps in a hidden slot only if it does not throw before the release, since the caller clears the slot before the call. A subprog that is passed an object, may throw, and then drops the object is rejected if its caller keeps the object in a hidden slot.
Transfer the reference to the callee at the call:
- When the verifier sees a subprog release a reference of its caller, it records which argument register the reference was passed in.
- A direct call to the subprog then transfers the reference that the caller holds and passes in that register. For the caller, the call is the release: the handle is cleared from its hidden slot, if it has one, before the call. For the callee, its entry is the acquisition, with the handle in the argument register. The callee holds the reference like one it acquired itself: its sites describe it, or it gets a hidden slot that the entry of the callee fills from the argument.
- A callee that returns without releasing the reference gives it back to the caller, under the caller's original acquisition.
Recording the argument changes what the current round of the main pass has recorded so far, so the round is run again when one is found.
Only references are transferred. Other resources, such as a lock, stay with the caller even if a pointer to them is passed.
This allows a subprog that drops an object or discards a record that it is passed, and may throw both before and after.
Still rejected:
- a reference that a callee is passed and returns without releasing on some paths, if the caller keeps it in a hidden slot;
- a subprog that keeps a transferred reference in a hidden slot, if another call passes something else in that argument;
- a callx to a subprog that releases a reference that its caller keeps in a hidden slot.
--- include/linux/bpf_verifier.h | 30 +++++- kernel/bpf/frame_desc.c | 192 ++++++++++++++++++++++++++++++----- kernel/bpf/join.c | 6 +- kernel/bpf/states.c | 6 +- kernel/bpf/verifier.c | 12 +++ 5 files changed, 212 insertions(+), 34 deletions(-)
include/linux/bpf_verifier.h +26 -4
@@ -524,6 +524,13 @@ struct bpf_func_state {*/ u32 rcu_released_below;u32 preempt_released_below; +/* +* The ids of what the frame was called with in R1 to R5, and the +* acquisition, plus one, each was of in the caller when the call +* handed it to the frame, see bpf_frame_desc_record_call(). +*/ +u32 entry_arg_id[5 ]; +u16 entry_arg_acq[5 ];/* The following fields should be last. See copy_func_state() */ /* The state of the stack. Each element of the array describes BPF_REG_SIZE
@@ -1112,6 +1119,13 @@ struct bpf_subprog_info { bool stack_args_from_code: 1;/* it releases what its caller holds, so it's verified in the context of such caller */ bool releases_inherited: 1; +/* +* The arguments, a bit per register, that it releases what its caller +* holds through: the call hands that to it, see frame_desc.c. And those +* of them that some call passes something else in. +*/ +u8 consumes; +u8 entry_none;/* * The kinds of sections, a bit each, that a callee returned to it in * more or fewer of than it called the callee in: a count it keeps in a
@@ -1260,6 +1274,7 @@ void bpf_patch_image_install(struct bpf_prog *prog, const struct bpf_patch_image#define BPF_RES_F_DEREF BIT(2)/* the handle is the first word of a dynptr on the stack */ #define BPF_RES_F_CTX BIT(3)/* a __ref argument the kernel passes the main program */ #define BPF_RES_F_NO_PTR BIT(5)/* a lock that no pointer to what it is in leads to */ +#define BPF_RES_F_ENTRY BIT(4)/* what its caller hands a subprog in the register */ /* * A call that acquires a resource, see frame_desc.c. When a frame can be
@@ -1269,7 +1284,11 @@ void bpf_patch_image_install(struct bpf_prog *prog, const struct bpf_patch_image* per acquiring call. */ struct bpf_res_acq { -u32 insn;/* the insn as the main pass named it; the argument with BPF_RES_F_CTX */ +/* +* The insn as the main pass named it; the argument with BPF_RES_F_CTX, +* the first insn of the subprog with BPF_RES_F_ENTRY. +*/ +u32 insn;u16 res;/* the class of what it acquires */ u8 reg;/* R0 for what the call returns, else the argument that is the handle */ u8 flags;/* BPF_RES_F_* */
@@ -1279,6 +1298,7 @@ struct bpf_res_acq {bool held;/* some frame holds what it acquired at a site */ bool slot;/* and some site cannot say where: the handle is kept in a slot */ u32 foreign;/* an insn of another frame that releases what it acquired, plus one */ +bool handed_back;/* a callee it was handed to returned holding it */ /* * An insn, plus one, that leaves what it acquired with another frame * than the one that ran the acquisition: the call a subprog returns
@@ -1344,9 +1364,9 @@ struct bpf_res_rel {/* * A call of a subprog during which a frame above the caller releases what an -* acquisition of the caller acquired. The caller clears its slot in front of -* the call when no throw finds it holding the resource there and no return -* from the call does, see frame_desc.c. +* acquisition of the caller acquired, without being handed it. The caller +* clears its slot in front of the call when no throw finds it holding the +* resource there and no return from the call does, see frame_desc.c. */ struct bpf_res_call {u32 insn;/* the call; U32_MAX when the caller is not in the state */
@@ -2383,6 +2403,8 @@ int bpf_frame_desc_record_section(struct bpf_verifier_env *env, u8 kind, s8 deltint bpf_frame_desc_record_exit (struct bpf_verifier_env *env);void bpf_frame_desc_record_sections (struct bpf_verifier_env *env,int subprog,bool rcu,bool preempt); +int bpf_frame_desc_record_call (struct bpf_verifier_env *env,struct bpf_verifier_state *st, +struct bpf_func_state *callee,int callsite);bool bpf_frame_desc_keeps (const struct bpf_verifier_env *env,const struct bpf_verifier_state *st,int f,const struct bpf_reg_state *reg); u16 bpf_frame_desc_kept_regs(const struct bpf_verifier_env *env, const struct bpf_verifier_state *st,
kernel/bpf/frame_desc.c +166 -26
301 lines not on this page · raw message
kernel/bpf/join.c +4 -2
@@ -463,9 +463,11 @@ static bool join_func(struct bpf_join_state *js, struct bpf_func_state *fd, fa->entry_rcu_locks != fb->entry_rcu_locks || fa->entry_preempt_locks != fb->entry_preempt_locks)return false; -/* the sections a frame left of those it was entered in matter to a throw, see frame_desc.c */ +/* the sections a frame left and what it was handed matter to a throw, see frame_desc.c */ if (env->describe_frames && (fa->rcu_released_below != fb->rcu_released_below || - fa->preempt_released_below != fb->preempt_released_below)) + fa->preempt_released_below != fb->preempt_released_below || +memcmp (fa->entry_arg_acq, fb->entry_arg_acq, +sizeof (fa->entry_arg_acq))))return false;if (!join_loop_stack (fa, fb))return false;
kernel/bpf/states.c +4 -2
@@ -717,9 +717,11 @@ static bool func_states_equal(struct bpf_verifier_env *env, struct bpf_func_stat old->entry_rcu_locks != cur->entry_rcu_locks || old->entry_preempt_locks != cur->entry_preempt_locks)return false; -/* the sections a frame left of those it was entered in matter to a throw, see frame_desc.c */ +/* the sections a frame left and what it was handed matter to a throw, see frame_desc.c */ if (env->describe_frames && (old->rcu_released_below != cur->rcu_released_below || - old->preempt_released_below != cur->preempt_released_below)) + old->preempt_released_below != cur->preempt_released_below || +memcmp (old->entry_arg_acq, cur->entry_arg_acq, +sizeof (old->entry_arg_acq))))return false;for (i =0 ; i <MAX_BPF_REG ; i++)
kernel/bpf/verifier.c +12 -0
@@ -11451,6 +11451,12 @@ static int setup_func_entry(struct bpf_verifier_env *env, int subprog, int calls err =set_callee_state_cb (env, caller, callee, callsite);if (err) goto err_out; +/* a plain call hands the callee what it releases of the caller's, see frame_desc.c */ + err =bpf_frame_desc_record_call (env, set_callee_state_cb == set_callee_state && +bpf_pseudo_call (&env->prog->insnsi[callsite]) ? state :NULL , + callee, callsite); +if (err) + goto err_out;/* only increment it after check_reg_arg() finished */ state->curframe++;
@@ -11859,6 +11865,12 @@ static int call_func_on_its_own(struct bpf_verifier_env *env, int subprog, int ireturn err; } } +/* what it is called with, should it release some of what it inherits */ + err =bpf_frame_desc_record_call (env,NULL , callee, insn_idx); +if (err) { +bpf_free_verifier_state (&entry, false); +return err; + }account_current_path (env); env->subprog_info[subprog].on_its_own = true;
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 21/29 bpf: Support callx when a callee releases resources
In reply to Kumar Kartikeya DwivediCurrently a callx transfers no reference, and its caller does not clear a hidden slot before it. A subprog that is called through a function pointer can release a resource of its caller only if the caller keeps the resource without a hidden slot.
A callx can call one of several subprogs, but the instructions inserted before it run whichever subprog is called. These are the instructions that clear the caller's hidden slot, both for a transferred reference and for a resource that the callee releases without a transfer. They are correct only if all the subprogs agree on what they release.
Treat a callx like a direct call, and record for each subprog it can call which resources that subprog leaves with the caller:
- A reference that the caller passes in an argument through which the subprog does not release. If the reference has a hidden slot and the callx transfers it to another subprog in the same argument, the program is rejected.
- Every resource of the caller, if the subprog releases no resources of its callers. Such a subprog is not verified in the context of its callers, so its return does not report that the caller still holds the resource. Recording it at the call stops the caller from clearing its hidden slot before the callx.
--- include/linux/bpf_verifier.h | 10 ++++ kernel/bpf/frame_desc.c | 94 +++++++++++++++++++++++++++++++++--- kernel/bpf/verifier.c | 10 +++- 3 files changed, 104 insertions(+), 10 deletions(-)
include/linux/bpf_verifier.h +10 -0
@@ -1367,10 +1367,19 @@ struct bpf_res_rel {* acquisition of the caller acquired, without being handed it. The caller * clears its slot in front of the call when no throw finds it holding the * resource there and no return from the call does, see frame_desc.c. +* +* A callx calls one of several subprogs, and what is put in front of it runs +* whichever it calls: it has a record as well for what a subprog it calls +* leaves with the caller, see bpf_frame_desc_record_callx(). */ struct bpf_res_call {u32 insn;/* the call; U32_MAX when the caller is not in the state */ u16 acq;/* index of the acquisition */ +/* +* The arguments, a bit per register, that a callx passes it in to a +* subprog that does not release through them. +*/ +u8 lent;bool released;bool held_after;/* some return from the call finds the caller holding it */ bool clear;/* decided once the pass is over */
@@ -2405,6 +2414,7 @@ void bpf_frame_desc_record_sections(struct bpf_verifier_env *env, int subprog, b bool preempt);int bpf_frame_desc_record_call (struct bpf_verifier_env *env,struct bpf_verifier_state *st,struct bpf_func_state *callee,int callsite); +int bpf_frame_desc_record_callx (struct bpf_verifier_env *env,int subprog,int callsite);bool bpf_frame_desc_keeps (const struct bpf_verifier_env *env,const struct bpf_verifier_state *st,int f,const struct bpf_reg_state *reg); u16 bpf_frame_desc_kept_regs(const struct bpf_verifier_env *env, const struct bpf_verifier_state *st,
kernel/bpf/frame_desc.c +86 -8
149 lines not on this page · raw message
kernel/bpf/verifier.c +8 -2
@@ -11451,9 +11451,10 @@ static int setup_func_entry(struct bpf_verifier_env *env, int subprog, int calls err =set_callee_state_cb (env, caller, callee, callsite);if (err) goto err_out; -/* a plain call hands the callee what it releases of the caller's, see frame_desc.c */ +/* the call of a subprog hands it what it releases of the caller's, see frame_desc.c */ err =bpf_frame_desc_record_call (env, set_callee_state_cb == set_callee_state && -bpf_pseudo_call (&env->prog->insnsi[callsite]) ? state :NULL , + (bpf_pseudo_call (&env->prog->insnsi[callsite]) || +bpf_is_callx (&env->prog->insnsi[callsite])) ? state :NULL , callee, callsite);if (err) goto err_out;
@@ -12280,6 +12281,11 @@ static int check_func_callx_one(struct bpf_verifier_env *env, int subprog, int *return err; env->subprog_info[subprog].via_callx = true; +/* what the insns in front of the callx do goes for every subprog it calls, see frame_desc.c */ + err =bpf_frame_desc_record_callx (env, subprog, *insn_idx); +if (err) +return err; +return check_static_func_call (env, subprog, insn_idx); }
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 22/29 selftests/bpf: Test rejected throws that hold resources
In reply to Kumar Kartikeya DwivediAdd a program to exceptions_fail for each rule that a program must follow when it throws while holding resources. The programs are rejected because:
- a callee may throw while the lock of its caller is held, and again after it has released the lock;
- a callee is passed a record that its caller keeps in a hidden slot, and returns without discarding it on some paths;
- a callee leaves an RCU section of a caller that counts its sections in a hidden slot;
- an object is released while the lock inside it is held;
- an iterator or saved IRQ flags on the stack are overwritten;
- an RCU protected iterator outlives its RCU section.
The programs run on x86-64 and arm64 only, where these rules apply.
--- .../selftests/bpf/progs/exceptions_fail.c | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+)
tools/testing/selftests/bpf/progs/exceptions_fail.c +214 -0
@@ -458,4 +458,218 @@ int reject_subprog_throw_irq_lock(void *ctx)return 0 ; } +/* +* What a program that throws may not do yet: the frames keep what they hold +* in hidden slots of their own, see kernel/bpf/frame_desc.c. +*/ + +struct locked_obj { +struct bpf_spin_lock lock; +int data; +}; + +private(L) struct bpf_spin_lock held_lock; + +static __noinline void throw_around_unlock_held(int before, int after) +{ +if (before) +bpf_throw (0 ); +bpf_spin_unlock (&held_lock); +if (after) +bpf_throw (0 ); +} + +/* +* The callee may throw with the lock of its caller held and after it dropped +* it, so the call of the caller says neither. The lock is in a slot of the +* caller then, which the callee cannot reach to empty. +*/ +SEC("?tc") +__arch_x86_64 __arch_arm64 +__failure __msg("releases what another frame holds in a hidden slot") +int reject_callee_releases (struct __sk_buff *ctx) +{ +bpf_spin_lock (&held_lock); +throw_around_unlock_held (ctx->len, ctx->mark); +return 0 ; +} + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 4096); +} fail_ringbuf SEC(".maps"); + +/* Discards the record when told. */ +__used static __noinline int discard_if(void *rec, int drop) +{ +if (drop) +bpf_ringbuf_discard (rec,0 ); +return 0 ; +} + +/* +* The two paths to the throw keep the record in different registers, so it is +* in a slot, which the caller empties for the callee it hands the record to. +* The callee returns holding the record when it is not told to discard it. +*/ +SEC("?tc") +__arch_x86_64 __arch_arm64 +__failure __msg("releases it on some paths only") +__naked int reject_callee_releases_sometimes (void ) +{ + asmvolatile ( +"r7 = *(u32 *)(r1 + %[len]);" +"r8 = *(u32 *)(r1 + %[mark]);" +"r2 = *(u32 *)(r1 + %[priority]);" +"*(u64 *)(r10 - 8) = r2;" +"r1 = %[fail_ringbuf] ll;" +"r2 = 8;" +"r3 = 0;" +"call %[bpf_ringbuf_reserve];" +"if r0 == 0 goto 4f;" +"r6 = r0;" +"r9 = 0;" +"r0 = 0;" +"if r7 == 0 goto 1f;" +"r9 = r6;" +"r6 = 0;" +"1: r1 = *(u64 *)(r10 - 8);" +"if r1 == 0 goto 2f;" +"r1 = 0;" +"call %[bpf_throw];" +"2: r1 = r6;" +"if r1 != 0 goto 3f;" +"r1 = r9;" +"3: r2 = r8;" +"call discard_if;" +"if r8 != 0 goto 4f;" +"r1 = r6;" +"if r1 != 0 goto +1;" +"r1 = r9;" +"r2 = 0;" +"call %[bpf_ringbuf_discard];" +"4: r0 = 0;" +"exit;" + : + : __imm_addr(fail_ringbuf), +__imm (bpf_ringbuf_reserve), +__imm (bpf_ringbuf_discard), + __imm(bpf_throw), + [len]"i"(offsetof(struct __sk_buff, len)), + [mark]"i"(offsetof(struct __sk_buff, mark)), + [priority]"i"(offsetof(struct __sk_buff, priority)) + : __clobber_all); +} + +__used static __noinline void leave_rcu(void) +{ +bpf_rcu_read_unlock (); +} + +/* +* The paths to the throw are in an RCU read section or not, so the frame +* counts its sections in a slot. The count does not follow what a callee +* does: the callee leaves the section it was called in. +*/ +SEC("?tc") +__arch_x86_64 __arch_arm64 +__failure __msg("a subprog returns in other sections than it was called in") +__naked int reject_callee_leaves_rcu (void ) +{ + asmvolatile ( +"r6 = *(u32 *)(r1 + %[len]);" +"r7 = *(u32 *)(r1 + %[mark]);" +"if r6 == 0 goto 1f;" +"call %[bpf_rcu_read_lock];" +"1: if r7 == 0 goto 2f;" +"r1 = 0;" +"call %[bpf_throw];" +"2: if r6 != 0 goto 3f;" +"call %[bpf_rcu_read_lock];" +"3: call leave_rcu;" +"r0 = 0;" +"exit;" + : + : __imm(bpf_rcu_read_lock), + __imm(bpf_throw), + [len]"i"(offsetof(struct __sk_buff, len)), + [mark]"i"(offsetof(struct __sk_buff, mark)) + : __clobber_all); +} + +struct obj_map_value { + struct locked_obj __kptr *obj; +}; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, int); + __type(value, struct obj_map_value); +} obj_map SEC(".maps"); + +/* The unwinder would unlock a lock in an object that may be freed by then. */ +SEC("?tc") +__arch_x86_64 __arch_arm64 +__failure __msg("releases an object whose lock is held") +int reject_release_locked_obj (struct __sk_buff *ctx) +{ +struct obj_map_value *v; +struct locked_obj *o; + + v =bpf_map_lookup_elem (&obj_map, &(int ){0 }); +if (!v) +return 0 ; + o =bpf_obj_new (typeof (*o)); +if (!o) +return 0 ; +bpf_spin_lock (&o->lock); + o =bpf_kptr_xchg (&v->obj, o); +bpf_throw (0 ); +return 0 ; +} + +/* The unwinder restores what is at the address of the flags. */ +SEC("?tc") +__arch_x86_64 __arch_arm64 +__failure __msg("cannot write over saved irq flags in a program that throws") +int reject_write_over_irq_flags (struct __sk_buff *ctx) +{ +unsigned long flags; + +bpf_local_irq_save (&flags); + *(volatile unsigned long *)&flags =0 ; +bpf_throw (0 ); +return 0 ; +} + +/* The unwinder destroys what is at the address of the iterator. */ +SEC("?tc") +__arch_x86_64 __arch_arm64 +__failure __msg("cannot write over an iterator in a program that throws") +int reject_write_over_iter (struct __sk_buff *ctx) +{ +struct bpf_iter_num it; + +bpf_iter_num_new (&it,0 ,10 ); + *(volatile __u64 *)&it =0x1234 ; +bpf_throw (0 ); +return 0 ; +} + +/* Nothing may destroy the iterator once the section it is protected by ended. */ +SEC("?syscall") +__arch_x86_64 __arch_arm64 +__failure __msg("an RCU protected iterator may not outlive its section in a program that throws") +int reject_rcu_iter_outlives_section (void *ctx) +{ +struct bpf_iter_task it; + +bpf_rcu_read_lock (); +bpf_iter_task_new (&it,NULL ,BPF_TASK_ITER_ALL_PROCS ); +bpf_rcu_read_unlock (); +bpf_throw (0 ); +return 0 ; +} + char _license[] SEC("license") = "GPL";
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 23/29 selftests/bpf: Test resource cleanup on bpf_throw()
In reply to Kumar Kartikeya DwivediAdd exceptions_cleanup. It runs programs that throw while holding resources, and checks which resources the unwinder released and in which order.
Each program records the handle of every resource it acquires. Tracing programs attached to bpf_unwind_release() record every release during an unwind. The test compares the two.
The programs cover:
- the kinds of resources: local objects, ringbuf records, tasks, sockets and their casts, iterators, a file dynptr, spin locks, saved IRQ flags, RCU read and preemption disabled sections, and the referenced argument of a struct_ops program;
- throws from the main program, from static and global subprogs, through an indirect call, and out of a recursion, with every frame holding resources;
- a reference that a subprog returns to its caller;
- registers that hold the handle at the throw only on some paths, or on none;
- one releasing instruction shared by two acquisitions;
- acquisitions that can fail and leave the slot empty;
- the release order: locks and IRQ flags first, then sections, then a destructor that sleeps.
For the struct_ops program, bpf_testmod calls test_refcounted() with a reference to current when the struct_ops is registered.
The suite runs a second time with bpf_jit_harden set, so the test is serial.
Add verifier_frame_desc. It checks the number of hidden slots and inserted instructions that the verifier reports for a few programs, and that two records reserved by the same instruction and held at once are rejected.
Both tests run on x86-64 and arm64 only. Elsewhere a program still cannot throw while it holds resources.
--- .../bpf/prog_tests/exceptions_cleanup.c | 257 ++++ .../selftests/bpf/prog_tests/verifier.c | 2 + .../selftests/bpf/progs/exceptions_cleanup.c | 1200 +++++++++++++++++ .../selftests/bpf/progs/verifier_frame_desc.c | 200 +++ .../selftests/bpf/test_kmods/bpf_testmod.c | 8 + 5 files changed, 1667 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/exceptions_cleanup.c create mode 100644 tools/testing/selftests/bpf/progs/exceptions_cleanup.c create mode 100644 tools/testing/selftests/bpf/progs/verifier_frame_desc.c
tools/testing/selftests/bpf/prog_tests/exceptions_cleanup.c new file +257 -0
@@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <test_progs.h> +#include <network_helpers.h> +#include "sysctl_helpers.h" + +#include "exceptions_cleanup.skel.h" + +/* The cookie the programs throw. */ +#define THROWN 0xeB9F + +/* +* What a run of a program has to show: its return value, the number of +* resources the program recorded acquiring, and the releases observed, in +* order, as a string: a digit names the acquired resource released at that +* point, 's' an RCU or preemption section, '?' any acquired resource not +* named elsewhere. @param is a choice the program reads. +*/ +struct expect { +int retval; +int acquired; +const char *released; +__u32 param; +}; + +static void check_releases (struct exceptions_cleanup *skel,const struct expect *e) +{ +bool matched[ARRAY_SIZE (skel->bss->acquired)] = {}; +int n =strlen (e->released), i, j; + +ASSERT_EQ (skel->bss->acquired_cnt, e->acquired,"acquired" ); +if (!ASSERT_EQ (skel->bss->released_cnt, n,"released" )) +return ; +for (i =0 ; i < n; i++) { +__u64 handle = skel->bss->released[i]; +char c = e->released[i]; + +if (c =='s' ) { +ASSERT_TRUE (skel->bss->released_section[i],"section" ); +continue ; + } +if (!ASSERT_FALSE (skel->bss->released_section[i],"not a section" )) +continue ; +if (c =='?' ) { +for (j =0 ; j < e->acquired; j++) +if (!matched[j] && handle == skel->bss->acquired[j]) +break ; +if (ASSERT_LT (j, e->acquired,"released an acquired resource" )) + matched[j] = true; +continue ; + } + j = c -'0' ; +if (ASSERT_EQ (handle, skel->bss->acquired[j],"release order" )) + matched[j] = true; + } +} + +static void reset (struct exceptions_cleanup *skel,__u32 param) +{ + skel->bss->acquired_cnt =0 ; + skel->bss->released_cnt =0 ; +memset (skel->bss->acquired,0 ,sizeof (skel->bss->acquired)); +memset (skel->bss->released,0 ,sizeof (skel->bss->released)); +memset (skel->bss->released_section,0 ,sizeof (skel->bss->released_section)); + skel->bss->param = param; + skel->bss->test_pid =getpid (); +} + +/* Run @prog once and check what it returned and what was released against @e. */ +static void run_prog (struct exceptions_cleanup *skel,struct bpf_program *prog, +const struct expect *e) +{ +LIBBPF_OPTS (bpf_test_run_opts, ropts, + .data_in = &pkt_v4, + .data_size_in = sizeof(pkt_v4), + .repeat = 1, + ); +int ret; + +reset (skel, e->param); +/* A syscall program runs on nothing: no input, and once, without a repeat count. */ +if (bpf_program__type (prog) ==BPF_PROG_TYPE_SYSCALL ) { + ropts.data_in =NULL ; + ropts.data_size_in =0 ; + ropts.repeat =0 ; + } + ret =bpf_prog_test_run_opts (bpf_program__fd (prog), &ropts); +if (!ASSERT_OK (ret,"test_run" )) +return ; +ASSERT_EQ (ropts.retval, e->retval,"retval" ); +check_releases (skel, e); +} + +/* +* Register the struct_ops whose program throws: the kernel calls it with a +* task reference in a __ref argument as it registers, see bpf_dummy_reg(). +*/ +static void run_struct_ops (struct exceptions_cleanup *skel,const struct expect *e) +{ +struct bpf_link *link; + +reset (skel, e->param); + link =bpf_map__attach_struct_ops (skel->maps.throwing_ops); +if (!ASSERT_OK_PTR (link,"attach_struct_ops" )) +return ; +check_releases (skel, e); +bpf_link__destroy (link); +} + +/* Open a file, which runs the LSM program that throws with a file and a dynptr that reads it. */ +static void run_file (struct exceptions_cleanup *skel,const struct expect *e) +{ +int fd; + +reset (skel, e->param); + skel->bss->file_armed =1 ; + fd =open ("/proc/self/exe" ,O_RDONLY ); +if (!ASSERT_OK_FD (fd,"open" )) +return ; +close (fd); +ASSERT_EQ (skel->bss->file_armed,0 ,"program ran" ); +check_releases (skel, e); +} + +/* Run every program; @prefix names the pass in the subtest names. */ +static void run_suite (const char *prefix) +{ +struct exceptions_cleanup *skel; +int server =-1 ; +char name[64 ]; + + skel =exceptions_cleanup__open (); +if (!ASSERT_OK_PTR (skel,"exceptions_cleanup__open" )) +return ; +/* registered by the subtests that run its program */ +bpf_map__set_autoattach (skel->maps.throwing_ops, false); +if (!ASSERT_OK (exceptions_cleanup__load (skel),"exceptions_cleanup__load" )) + goto out; +if (!ASSERT_OK (exceptions_cleanup__attach (skel),"attach" )) + goto out; +/* The listener throw_sock_cast looks up, on an ephemeral port. */ + server =start_server (AF_INET ,SOCK_STREAM ,"127.0.0.1" ,0 ,0 ); +if (!ASSERT_OK_FD (server,"start_server" )) + goto out; + skel->bss->server_port =get_socket_local_port (server); + +/* Run program @pname as subtest @sname, expecting @ret, @n acquisitions and the releases @rel. */ +#define RUN_AS (pname, sname, ret, n, rel, ...) do { \ + struct expect e = { .retval = ret, .acquired = n, .released = rel, ##__VA_ARGS__ }; \ + \ + snprintf(name, sizeof(name), "%s%s", prefix, sname); \ + if (test__start_subtest(name)) \ + run_prog(skel, skel->progs.pname, &e); \ +} while (0) +#define RUN (pname, ret, n, rel, ...) RUN_AS(pname, #pname, ret, n, rel, ##__VA_ARGS__) +#define THROW (pname, n, rel, ...) RUN(pname, THROWN, n, rel, ##__VA_ARGS__) +THROW (throw_obj_reg,1 ,"0" ); +THROW (throw_obj_two,2 ,"??" ); +THROW (throw_ringbuf,1 ,"0" ); +THROW (throw_task,1 ,"0" ); +THROW (throw_sock_cast,1 ,"0" ); +THROW (throw_iter,1 ,"0" ); +THROW (throw_obj_maybe,1 ,"0" ); +THROW (throw_obj_caller,1 ,"0" ); +THROW (throw_obj_callx_caller,1 ,"0" ); +THROW (throw_obj_origin_after_guard,1 ,"0" ); +THROW (throw_obj_unconditional,1 ,"0" ); +THROW (throw_obj_dead_caller,1 ,"0" ); +THROW (throw_obj_subprog,1 ,"0" ); +THROW (throw_obj_global_caller,1 ,"0" ); +/* Locks and IRQ flags go first, sections next, objects last. */ +THROW (throw_spin_lock,2 ,"10" ); +THROW (throw_rcu,1 ,"s0" ); +THROW (throw_irq,1 ,"0s" ); +THROW (throw_obj_after_caller_lock,2 ,"01" ); +THROW (throw_sleeping_dtor_after_section,1 ,"s0" ); +/* References the program keeps in different places on different paths, or nowhere. */ +THROW (throw_reg_conflict,1 ,"0" ); +THROW (throw_dead_reg,1 ,"0" ); +THROW (throw_lock_two_locations,2 ,"01" ); +RUN_AS (throw_shared_release,"throw_shared_release_first" ,THROWN ,2 ,"1" , + .param = 0); +RUN_AS (throw_shared_release,"throw_shared_release_second" ,THROWN ,2 ,"0" , + .param = 1); +RUN_AS (throw_refcount_shared_release,"throw_refcount_shared_release_first" ,THROWN ,1 ,"0" , + .param = 0); +RUN_AS (throw_refcount_shared_release,"throw_refcount_shared_release_second" ,THROWN ,1 ,"0" , + .param = 1); +/* A callee overwrites the caller's copy of the pointer: the object is released, once. */ +THROW (throw_callee_clobbers_slot,1 ,"0" ); +/* What is released through one call or on one path and not through another. */ + RUN_AS(throw_irq_order, "throw_irq_order_enabled_first", THROWN, 2, "10s", .param = 1); + RUN_AS(throw_irq_order, "throw_irq_order_enabled_last", THROWN, 2, "01", .param = 0); +/* A recursive subprog holding an object per frame: each frame owns its own. */ +THROW (throw_recursion_holding,4 ,"3210" ); +/* what the frames hold at call depth two */ +RUN_AS (throw_ringbuf_depth2_split,"throw_ringbuf_depth2_split_held" ,THROWN ,1 ,"0" , + .param = 3); +RUN_AS (throw_ringbuf_depth2_split,"throw_ringbuf_depth2_split_free" ,THROWN ,1 ,"" , + .param = 1); +/* a throw reached by paths of different stack depth */ + RUN_AS(throw_slot_unallocated, "throw_slot_unallocated_held", THROWN, 1, "0", .param = 0); + RUN_AS(throw_slot_unallocated, "throw_slot_unallocated_free", THROWN, 0, "", .param = 1); +/* a reference that may be NULL when a callee throws */ + RUN_AS(throw_task_maybe_null, "throw_task_maybe_null_null", THROWN, 1, "", .param = 1); + RUN_AS(throw_task_maybe_null, "throw_task_maybe_null_held", THROWN, 1, "0", .param = 3); +/* a reference held as a pointer into the object */ + RUN_AS(throw_pop_null, "throw_pop_null_popped", THROWN, 1, "0", .param = 0); + RUN_AS(throw_pop_null, "throw_pop_null_empty", THROWN, 0, "", .param = 1); +/* A spill the verifier removes is not where the unwinder looks. */ + RUN_AS(throw_fastcall_spill, "throw_fastcall_spill_throw", THROWN, 1, "0", .param = 0); + RUN_AS(throw_fastcall_spill, "throw_fastcall_spill_discard", 0, 1, "", .param = 1); +/* A reference a subprog returns is the caller's from the call on. */ +THROW (throw_obj_returned,1 ,"0" ); +/* One call that takes the object on one path and NULL on the other. */ + RUN_AS(throw_xchg_maybe, "throw_xchg_maybe_kept", THROWN, 1, "0", .param = 0); + RUN_AS(throw_xchg_maybe, "throw_xchg_maybe_given", THROWN, 1, "", .param = 1); +/* A file and the reader of its dynptr, which goes first. */ +snprintf (name,sizeof (name),"%sthrow_file_dynptr_held" , prefix); +if (test__start_subtest (name)) +run_file (skel, &(struct expect ){ .acquired =1 , .released ="s0" , .param =0 }); +snprintf (name,sizeof (name),"%sthrow_file_dynptr_released" , prefix); +if (test__start_subtest (name)) +run_file (skel, &(struct expect ){ .acquired =1 , .released ="" , .param =1 }); +/* A reference the kernel passed in a __ref argument, held at the throw or released before it. */ +snprintf (name,sizeof (name),"%sthrow_ops_ref_held" , prefix); +if (test__start_subtest (name)) +run_struct_ops (skel, &(struct expect ){ .acquired =1 , .released ="0" , .param =0 }); +snprintf (name,sizeof (name),"%sthrow_ops_ref_released" , prefix); +if (test__start_subtest (name)) +run_struct_ops (skel, &(struct expect ){ .acquired =1 , .released ="" , .param =1 }); +#undef THROW +#undef RUN +#undef RUN_AS +out: +if (server >=0 ) +close (server); +exceptions_cleanup__destroy (skel); +} + +/* Serial, because it sets bpf_jit_harden. */ +void serial_test_exceptions_cleanup (void ) +{ +const char *harden ="/proc/sys/net/core/bpf_jit_harden" ; +char old[16 ] = {}; + +#if !defined(__x86_64__) && !defined(__aarch64__) +/* elsewhere a program may not throw while it holds resources */ +test__skip (); +return ; +#endif +run_suite ("" ); +/* Constant blinding rewrites the program, with the insns that keep the slots in it. */ +if (sysctl_set_or_fail (harden, old,"2" )) +return ; +run_suite ("harden_" ); +sysctl_set_or_fail (harden,NULL , old); +}
tools/testing/selftests/bpf/prog_tests/verifier.c +2 -0
@@ -58,6 +58,7 @@#include "verifier_div_const.skel.h" #include "verifier_div_mod_bounds.skel.h" #include "verifier_div_overflow.skel.h" +#include "verifier_frame_desc.skel.h" #include "verifier_flow_keys.skel.h" #include "verifier_global_subprogs.skel.h" #include "verifier_global_ptr_args.skel.h"
@@ -267,6 +268,7 @@ void test_verifier_div0(void) { RUN(verifier_div0); }void test_verifier_div_const (void ) {RUN (verifier_div_const); }void test_verifier_div_mod_bounds (void ) {RUN (verifier_div_mod_bounds); }void test_verifier_div_overflow (void ) {RUN (verifier_div_overflow); } +void test_verifier_frame_desc (void ) {RUN (verifier_frame_desc); }void test_verifier_flow_keys (void ) {RUN (verifier_flow_keys); }void test_verifier_global_subprogs (void ) {RUN (verifier_global_subprogs); }void test_verifier_global_ptr_args (void ) {RUN (verifier_global_ptr_args); }
tools/testing/selftests/bpf/progs/exceptions_cleanup.c new file +1200 -0
1,200 lines not on this page · raw message
tools/testing/selftests/bpf/progs/verifier_frame_desc.c new file +200 -0
200 lines not on this page · raw message
tools/testing/selftests/bpf/test_kmods/bpf_testmod.c +8 -0
@@ -2093,6 +2093,14 @@ static int bpf_dummy_reg(void *kdata, struct bpf_link *link) ops->test_2 (4 , ops->data);if (ops->test_trampoline_stack_args) ops->test_trampoline_stack_args (1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9999 ); +/* +* The program releases the task, or the unwinder does when it throws: +* with bpf_task_release(), which puts what bpf_task_acquire() gets. +*/ +if (ops->test_refcounted) { +refcount_inc (¤t->rcu_users); + ops->test_refcounted (0 , current); + }return 0 ; }
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 24/29 selftests/bpf: Test cleanup of handles on the stack
In reply to Kumar Kartikeya DwivediAdd programs to exceptions_cleanup that keep their resources on their own stack when they throw, where the unwinder reads them:
- a record spilled in the frame that throws, and in its caller;
- a popped list node, a pointer into its object, that is spilled, or NULL;
- a record that is spilled on one path to the throw and in a register on the other, which takes a hidden slot;
- a callee that leaves the RCU read section of its caller and throws;
- a section entered on only one path to the throw, which is counted.
Check in verifier_frame_desc how many hidden slots, inserted instructions and site descriptors the verifier reports:
- none for a socket released before the throw;
- a site descriptor and no slot for a spilled record, in the frame that throws and in its caller, for an iterator, and for an RCU read section;
- the same for a record spilled in a slot that is not read again, where two paths join before a call to a subprog that may throw;
- a slot for a record or a section that differs between the paths to the throw.
--- .../bpf/prog_tests/exceptions_cleanup.c | 18 +- .../selftests/bpf/progs/exceptions_cleanup.c | 202 +++++++++++++++ .../selftests/bpf/progs/verifier_frame_desc.c | 240 ++++++++++++++++++ 3 files changed, 459 insertions(+), 1 deletion(-)
tools/testing/selftests/bpf/prog_tests/exceptions_cleanup.c +17 -1
@@ -214,6 +214,19 @@ static void run_suite(const char *prefix)/* One call that takes the object on one path and NULL on the other. */ RUN_AS(throw_xchg_maybe, "throw_xchg_maybe_kept", THROWN, 1, "0", .param = 0); RUN_AS(throw_xchg_maybe, "throw_xchg_maybe_given", THROWN, 1, "", .param = 1); +/* What the frames keep in their own stack at the throw is released from there. */ +THROW (throw_spilled,1 ,"0" ); + RUN_AS(throw_spilled_caller, "throw_spilled_caller_throw", THROWN, 1, "0", .param = 0); + RUN_AS(throw_spilled_caller, "throw_spilled_caller_return", 0, 1, "", .param = 1); + RUN_AS(throw_spilled_node, "throw_spilled_node_popped", THROWN, 1, "0", .param = 0); + RUN_AS(throw_spilled_node, "throw_spilled_node_empty", THROWN, 0, "", .param = 1); + RUN_AS(throw_spilled_some_paths, "throw_spilled_some_paths_reg", THROWN, 1, "0", .param = 0); +RUN_AS (throw_spilled_some_paths,"throw_spilled_some_paths_stack" ,THROWN ,1 ,"0" , + .param = 1); +/* The sections the frames are in, counted where the paths to a throw differ. */ +THROW (throw_callee_left_rcu,1 ,"0" ); + RUN_AS(throw_rcu_some_paths, "throw_rcu_some_paths_in", THROWN, 0, "s", .param = 1); + RUN_AS(throw_rcu_some_paths, "throw_rcu_some_paths_out", THROWN, 0, "", .param = 0);/* A file and the reader of its dynptr, which goes first. */ snprintf (name,sizeof (name),"%sthrow_file_dynptr_held" , prefix);if (test__start_subtest (name))
@@ -249,7 +262,10 @@ void serial_test_exceptions_cleanup(void)return ;#endif run_suite ("" ); -/* Constant blinding rewrites the program, with the insns that keep the slots in it. */ +/* +* Constant blinding rewrites the program, with the insns that keep the +* slots in it, and moves the calls the sites are at. +*/ if (sysctl_set_or_fail (harden, old,"2" ))return ;run_suite ("harden_" );
tools/testing/selftests/bpf/progs/exceptions_cleanup.c +202 -0
206 lines not on this page · raw message
tools/testing/selftests/bpf/progs/verifier_frame_desc.c +240 -0
246 lines not on this page · raw message
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 25/29 selftests/bpf: Test cleanup of handles in registers
In reply to Kumar Kartikeya DwivediAdd programs to exceptions_cleanup that keep their resources only in registers when they throw:
- three records, two frames up from the throw: one in a register that the callee saves and overwrites, one in a register that no frame touches, and one of the callee's in a register that the throwing frame does not save;
- a record in R3 in the frame that throws;
- a popped list node, a pointer into its object, in R6, or NULL.
Check in verifier_frame_desc that the verifier reports a site descriptor and no slot for a record in R6 or R0 at the throw, and for a record in R6 in the caller of a subprog that may throw. A record in R2 there takes a slot.
Also check that a register that is not read again is still found at the throw when two paths join before it.
--- .../bpf/prog_tests/exceptions_cleanup.c | 5 + .../selftests/bpf/progs/exceptions_cleanup.c | 164 ++++++++++++++++++ .../selftests/bpf/progs/verifier_frame_desc.c | 155 +++++++++++++++++ 3 files changed, 324 insertions(+)
tools/testing/selftests/bpf/prog_tests/exceptions_cleanup.c +5 -0
@@ -227,6 +227,11 @@ static void run_suite(const char *prefix)THROW (throw_callee_left_rcu,1 ,"0" ); RUN_AS(throw_rcu_some_paths, "throw_rcu_some_paths_in", THROWN, 0, "s", .param = 1); RUN_AS(throw_rcu_some_paths, "throw_rcu_some_paths_out", THROWN, 0, "", .param = 0); +/* What the frames keep in registers at the throw is released from them. */ +THROW (throw_regs_callers,3 ,"201" ); +THROW (throw_reg_caller_saved,1 ,"0" ); + RUN_AS(throw_reg_node, "throw_reg_node_popped", THROWN, 1, "0", .param = 0); + RUN_AS(throw_reg_node, "throw_reg_node_empty", THROWN, 0, "", .param = 1);/* A file and the reader of its dynptr, which goes first. */ snprintf (name,sizeof (name),"%sthrow_file_dynptr_held" , prefix);if (test__start_subtest (name))
tools/testing/selftests/bpf/progs/exceptions_cleanup.c +164 -0
168 lines not on this page · raw message
tools/testing/selftests/bpf/progs/verifier_frame_desc.c +155 -0
161 lines not on this page · raw message
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 26/29 selftests/bpf: Test callees releasing caller resources
In reply to Kumar Kartikeya DwivediAdd programs to exceptions_cleanup where a callee releases a resource of its caller:
- the callee discards a record that the caller keeps in R6, and the caller throws before or after the call;
- the caller passes the record without keeping a copy, and the callee throws before or after its discard, with the record in R6;
- the same, with the record in a hidden slot of the caller, which the call clears;
- the callee has the record in no register or stack slot when it throws, and keeps it in a hidden slot of its own;
- the callee unlocks the lock of a map element that the caller keeps in a hidden slot, which the caller clears before the call.
Check in verifier_frame_desc how many hidden slots, inserted instructions and site descriptors each case takes. Also check that a lock at a fixed address that a callee unlocks takes a site descriptor and no slot.
--- .../bpf/prog_tests/exceptions_cleanup.c | 30 ++ .../selftests/bpf/progs/exceptions_cleanup.c | 281 ++++++++++++++++++ .../selftests/bpf/progs/verifier_frame_desc.c | 253 ++++++++++++++++ 3 files changed, 564 insertions(+)
tools/testing/selftests/bpf/prog_tests/exceptions_cleanup.c +30 -0
@@ -232,6 +232,36 @@ static void run_suite(const char *prefix)THROW (throw_reg_caller_saved,1 ,"0" ); RUN_AS(throw_reg_node, "throw_reg_node_popped", THROWN, 1, "0", .param = 0); RUN_AS(throw_reg_node, "throw_reg_node_empty", THROWN, 0, "", .param = 1); +/* A callee releases what its caller holds: a throw finds it held, or released. */ + RUN_AS(throw_callee_releases, "throw_callee_releases_before", THROWN, 1, "0", .param = 1); + RUN_AS(throw_callee_releases, "throw_callee_releases_after", THROWN, 1, "", .param = 2); + RUN_AS(throw_callee_releases, "throw_callee_releases_return", 0, 1, "", .param = 0); +/* A callee that is handed what it releases holds it from the call on. */ + RUN_AS(throw_callee_consumes, "throw_callee_consumes_before", THROWN, 1, "0", .param = 1); + RUN_AS(throw_callee_consumes, "throw_callee_consumes_after", THROWN, 1, "", .param = 2); + RUN_AS(throw_callee_consumes, "throw_callee_consumes_return", 0, 1, "", .param = 0); +/* the caller keeps it in a slot, which the call empties */ +RUN_AS (throw_callee_consumes_from_slot,"throw_callee_consumes_from_slot_caller_r6" ,THROWN , + 1, "0", .param = 3); +RUN_AS (throw_callee_consumes_from_slot,"throw_callee_consumes_from_slot_caller_r9" ,THROWN , + 1, "0", .param = 11); +RUN_AS (throw_callee_consumes_from_slot,"throw_callee_consumes_from_slot_callee_r6" ,THROWN , + 1, "0", .param = 1); +RUN_AS (throw_callee_consumes_from_slot,"throw_callee_consumes_from_slot_callee_r9" ,THROWN , + 1, "0", .param = 9); +RUN_AS (throw_callee_consumes_from_slot,"throw_callee_consumes_from_slot_after" ,THROWN , + 1, "", .param = 10); +RUN_AS (throw_callee_consumes_from_slot,"throw_callee_consumes_from_slot_return" ,0 , + 1, "", .param = 8); +/* the callee has it nowhere at its throw and keeps it in a slot of its own */ +RUN_AS (throw_callee_consumes_slot,"throw_callee_consumes_slot_throw" ,THROWN ,1 ,"0" , + .param = 1); +RUN_AS (throw_callee_consumes_slot,"throw_callee_consumes_slot_return" ,0 ,1 ,"" , + .param = 0); +/* A lock a callee unlocks leaves the slot of the caller in front of the call. */ + RUN_AS(throw_callee_unlocks, "throw_callee_unlocks_before", THROWN, 1, "0", .param = 1); + RUN_AS(throw_callee_unlocks, "throw_callee_unlocks_after", THROWN, 1, "", .param = 2); + RUN_AS(throw_callee_unlocks, "throw_callee_unlocks_return", 0, 1, "", .param = 0);/* A file and the reader of its dynptr, which goes first. */ snprintf (name,sizeof (name),"%sthrow_file_dynptr_held" , prefix);if (test__start_subtest (name))
tools/testing/selftests/bpf/progs/exceptions_cleanup.c +281 -0
285 lines not on this page · raw message
tools/testing/selftests/bpf/progs/verifier_frame_desc.c +253 -0
259 lines not on this page · raw message
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 27/29 selftests/bpf: Test resources a callee leaves with its caller
In reply to Kumar Kartikeya DwivediAdd programs to exceptions_cleanup where the caller throws while holding a resource that a callee acquired for it: an iterator that the callee created on the caller's stack, and an RCU read section that the callee returned in.
Check in verifier_frame_desc that nothing is kept for resources that no throw finds held: a lock that one subprog takes and another drops, a section that a subprog returns in, and two records that one instruction reserves in a loop. An iterator that a callee creates on its caller's stack takes a site descriptor and no slot.
--- .../bpf/prog_tests/exceptions_cleanup.c | 3 + .../selftests/bpf/progs/exceptions_cleanup.c | 43 ++++++ .../selftests/bpf/progs/verifier_frame_desc.c | 133 +++++++++++++++++- 3 files changed, 177 insertions(+), 2 deletions(-)
tools/testing/selftests/bpf/prog_tests/exceptions_cleanup.c +3 -0
@@ -262,6 +262,9 @@ static void run_suite(const char *prefix) RUN_AS(throw_callee_unlocks, "throw_callee_unlocks_before", THROWN, 1, "0", .param = 1); RUN_AS(throw_callee_unlocks, "throw_callee_unlocks_after", THROWN, 1, "", .param = 2); RUN_AS(throw_callee_unlocks, "throw_callee_unlocks_return", 0, 1, "", .param = 0); +/* What a frame holds that a callee acquired for it. */ +THROW (throw_iter_from_callee,1 ,"0" ); +THROW (throw_rcu_from_callee,0 ,"s" );/* A file and the reader of its dynptr, which goes first. */ snprintf (name,sizeof (name),"%sthrow_file_dynptr_held" , prefix);if (test__start_subtest (name))
tools/testing/selftests/bpf/progs/exceptions_cleanup.c +43 -0
47 lines not on this page · raw message
tools/testing/selftests/bpf/progs/verifier_frame_desc.c +131 -2
141 lines not on this page · raw message
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 28/29 selftests/bpf: Test cleanup of locks described at sites
In reply to Kumar Kartikeya DwivediAdd programs to exceptions_cleanup that throw while holding a lock that has no hidden slot:
- the lock of a global variable, taken by a subprog for its caller, which is at a fixed address;
- the lock of a map element, found through the pointer to the element that the frame keeps in R7.
Check in verifier_frame_desc that each takes a site descriptor, no slot, and no inserted instruction.
--- .../bpf/prog_tests/exceptions_cleanup.c | 3 + .../selftests/bpf/progs/exceptions_cleanup.c | 63 +++++++++++++++++++ .../selftests/bpf/progs/verifier_frame_desc.c | 61 ++++++++++++++++++ 3 files changed, 127 insertions(+)
tools/testing/selftests/bpf/prog_tests/exceptions_cleanup.c +3 -0
@@ -265,6 +265,9 @@ static void run_suite(const char *prefix)/* What a frame holds that a callee acquired for it. */ THROW (throw_iter_from_callee,1 ,"0" );THROW (throw_rcu_from_callee,0 ,"s" ); +/* Locks the sites name: at a fixed address, or through a pointer the frame keeps. */ +THROW (throw_lock_from_callee,1 ,"0" ); +THROW (throw_lock_in_value,1 ,"0" );/* A file and the reader of its dynptr, which goes first. */ snprintf (name,sizeof (name),"%sthrow_file_dynptr_held" , prefix);if (test__start_subtest (name))
tools/testing/selftests/bpf/progs/exceptions_cleanup.c +63 -0
67 lines not on this page · raw message
tools/testing/selftests/bpf/progs/verifier_frame_desc.c +61 -0
67 lines not on this page · raw message
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 29/29 selftests/bpf: Test callee releases through callx
In reply to Kumar Kartikeya DwivediAdd a program to exceptions_cleanup that keeps a record in a hidden slot and passes it to a callee through a function pointer. The caller throws with the record in its slot, and the callee throws before and after its discard, when the slot of the caller is already cleared.
Check in verifier_frame_desc the slots and instructions that this takes, and that the caller clears the hidden slot of a lock before a callx to a subprog that unlocks.
Add a program to exceptions_fail in which a callx can call one subprog that discards the record and another that leaves it with the caller, which keeps it in a hidden slot.
--- .../bpf/prog_tests/exceptions_cleanup.c | 9 ++ .../selftests/bpf/progs/exceptions_cleanup.c | 44 ++++++++++ .../selftests/bpf/progs/exceptions_fail.c | 70 ++++++++++++++++ .../selftests/bpf/progs/verifier_frame_desc.c | 83 +++++++++++++++++++ 4 files changed, 206 insertions(+)
tools/testing/selftests/bpf/prog_tests/exceptions_cleanup.c +9 -0
@@ -253,6 +253,15 @@ static void run_suite(const char *prefix) 1, "", .param = 10);RUN_AS (throw_callee_consumes_from_slot,"throw_callee_consumes_from_slot_return" ,0 , 1, "", .param = 8); +/* a callx hands over like a call, and its caller empties the slot in front of it */ +RUN_AS (throw_callx_consumes_from_slot,"throw_callx_consumes_from_slot_caller" ,THROWN , + 1, "0", .param = 3); +RUN_AS (throw_callx_consumes_from_slot,"throw_callx_consumes_from_slot_callee" ,THROWN , + 1, "0", .param = 1); +RUN_AS (throw_callx_consumes_from_slot,"throw_callx_consumes_from_slot_after" ,THROWN , + 1, "", .param = 2); +RUN_AS (throw_callx_consumes_from_slot,"throw_callx_consumes_from_slot_return" ,0 , + 1, "", .param = 0);/* the callee has it nowhere at its throw and keeps it in a slot of its own */ RUN_AS (throw_callee_consumes_slot,"throw_callee_consumes_slot_throw" ,THROWN ,1 ,"0" , .param = 1);
tools/testing/selftests/bpf/progs/exceptions_cleanup.c +44 -0
50 lines not on this page · raw message
tools/testing/selftests/bpf/progs/exceptions_fail.c +70 -0
76 lines not on this page · raw message
tools/testing/selftests/bpf/progs/verifier_frame_desc.c +83 -0
89 lines not on this page · raw message
Signature · 1 line
2.53.0