// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2022 Facebook */ #include #include #include #include #include "bpf_misc.h" char _license[] SEC("license") = "GPL"; #define ITER_HELPERS \ __imm(bpf_iter_num_new), \ __imm(bpf_iter_num_next), \ __imm(bpf_iter_num_destroy) SEC("?raw_tp") __success int force_clang_to_emit_btf_for_externs(void *ctx) { /* we need this as a workaround to enforce compiler emitting BTF * information for bpf_iter_num_{new,next,destroy}() kfuncs, * as, apparently, it doesn't emit it for symbols only referenced from * assembly (or cleanup attribute, for that matter, as well) */ bpf_repeat(0); return 0; } SEC("?raw_tp") __success __log_level(2) __msg("fp-8_w=iter_num(ref_id=1,state=active,depth=0)") int create_and_destroy(void *ctx) { struct bpf_iter_num iter; asm volatile ( /* create iterator */ "r1 = %[iter];" "r2 = 0;" "r3 = 1000;" "call %[bpf_iter_num_new];" /* destroy iterator */ "r1 = %[iter];" "call %[bpf_iter_num_destroy];" : : __imm_ptr(iter), ITER_HELPERS : __clobber_common ); return 0; } SEC("?raw_tp") __failure __msg("Unreleased reference id=1") int create_and_forget_to_destroy_fail(void *ctx) { struct bpf_iter_num iter; asm volatile ( /* create iterator */ "r1 = %[iter];" "r2 = 0;" "r3 = 1000;" "call %[bpf_iter_num_new];" : : __imm_ptr(iter), ITER_HELPERS : __clobber_common ); return 0; } SEC("?raw_tp") __failure __msg("expected an initialized iter_num as arg #1") int destroy_without_creating_fail(void *ctx) { /* init with zeros to stop verifier complaining about uninit stack */ struct bpf_iter_num iter; asm volatile ( "r1 = %[iter];" "call %[bpf_iter_num_destroy];" : : __imm_ptr(iter), ITER_HELPERS : __clobber_common ); return 0; } SEC("?raw_tp") __failure __msg("expected an initialized iter_num as arg #1") int compromise_iter_w_direct_write_fail(void *ctx) { struct bpf_iter_num iter; asm volatile ( /* create iterator */ "r1 = %[iter];" "r2 = 0;" "r3 = 1000;" "call %[bpf_iter_num_new];" /* directly write over first half of iter state */ "*(u64 *)(%[iter] + 0) = r0;" /* (attempt to) destroy iterator */ "r1 = %[iter];" "call %[bpf_iter_num_destroy];" : : __imm_ptr(iter), ITER_HELPERS : __clobber_common ); return 0; } SEC("?raw_tp") __failure __msg("Unreleased reference id=1") int compromise_iter_w_direct_write_and_skip_destroy_fail(void *ctx) { struct bpf_iter_num iter; asm volatile ( /* create iterator */ "r1 = %[iter];" "r2 = 0;" "r3 = 1000;" "call %[bpf_iter_num_new];" /* directly write over first half of iter state */ "*(u64 *)(%[iter] + 0) = r0;" /* don't destroy iter, leaking ref, which should fail */ : : __imm_ptr(iter), ITER_HELPERS : __clobber_common ); return 0; } SEC("?raw_tp") __failure __msg("expected an initialized iter_num as arg #1") int compromise_iter_w_helper_write_fail(void *ctx) { struct bpf_iter_num iter; asm volatile ( /* create iterator */ "r1 = %[iter];" "r2 = 0;" "r3 = 1000;" "call %[bpf_iter_num_new];" /* overwrite 8th byte with bpf_probe_read_kernel() */ "r1 = %[iter];" "r1 += 7;" "r2 = 1;" "r3 = 0;" /* NULL */ "call %[bpf_probe_read_kernel];" /* (attempt to) destroy iterator */ "r1 = %[iter];" "call %[bpf_iter_num_destroy];" : : __imm_ptr(iter), ITER_HELPERS, __imm(bpf_probe_read_kernel) : __clobber_common ); return 0; } static __noinline void subprog_with_iter(void) { struct bpf_iter_num iter; bpf_iter_num_new(&iter, 0, 1); return; } SEC("?raw_tp") __failure /* ensure there was a call to subprog, which might happen without __noinline */ __msg("returning from callee:") __msg("Unreleased reference id=1") int leak_iter_from_subprog_fail(void *ctx) { subprog_with_iter(); return 0; } SEC("?raw_tp") __success __log_level(2) __msg("fp-8_w=iter_num(ref_id=1,state=active,depth=0)") int valid_stack_reuse(void *ctx) { struct bpf_iter_num iter; asm volatile ( /* create iterator */ "r1 = %[iter];" "r2 = 0;" "r3 = 1000;" "call %[bpf_iter_num_new];" /* destroy iterator */ "r1 = %[iter];" "call %[bpf_iter_num_destroy];" /* now reuse same stack slots */ /* create iterator */ "r1 = %[iter];" "r2 = 0;" "r3 = 1000;" "call %[bpf_iter_num_new];" /* destroy iterator */ "r1 = %[iter];" "call %[bpf_iter_num_destroy];" : : __imm_ptr(iter), ITER_HELPERS : __clobber_common ); return 0; } SEC("?raw_tp") __failure __msg("expected uninitialized iter_num as arg #1") int double_create_fail(void *ctx) { struct bpf_iter_num iter; asm volatile ( /* create iterator */ "r1 = %[iter];" "r2 = 0;" "r3 = 1000;" "call %[bpf_iter_num_new];" /* (attempt to) create iterator again */ "r1 = %[iter];" "r2 = 0;" "r3 = 1000;" "call %[bpf_iter_num_new];" /* destroy iterator */ "r1 = %[iter];" "call %[bpf_iter_num_destroy];" : : __imm_ptr(iter), ITER_HELPERS : __clobber_common ); return 0; } SEC("?raw_tp") __failure __msg("expected an initialized iter_num as arg #1") int double_destroy_fail(void *ctx) { struct bpf_iter_num iter; asm volatile ( /* create iterator */ "r1 = %[iter];" "r2 = 0;" "r3 = 1000;" "call %[bpf_iter_num_new];" /* destroy iterator */ "r1 = %[iter];" "call %[bpf_iter_num_destroy];" /* (attempt to) destroy iterator again */ "r1 = %[iter];" "call %[bpf_iter_num_destroy];" : : __imm_ptr(iter), ITER_HELPERS : __clobber_common ); return 0; } SEC("?raw_tp") __failure __msg("expected an initialized iter_num as arg #1") int next_without_new_fail(void *ctx) { struct bpf_iter_num iter; asm volatile ( /* don't create iterator and try to iterate*/ "r1 = %[iter];" "call %[bpf_iter_num_next];" /* destroy iterator */ "r1 = %[iter];" "call %[bpf_iter_num_destroy];" : : __imm_ptr(iter), ITER_HELPERS : __clobber_common ); return 0; } SEC("?raw_tp") __failure __msg("expected an initialized iter_num as arg #1") int next_after_destroy_fail(void *ctx) { struct bpf_iter_num iter; asm volatile ( /* create iterator */ "r1 = %[iter];" "r2 = 0;" "r3 = 1000;" "call %[bpf_iter_num_new];" /* destroy iterator */ "r1 = %[iter];" "call %[bpf_iter_num_destroy];" /* don't create iterator and try to iterate*/ "r1 = %[iter];" "call %[bpf_iter_num_next];" : : __imm_ptr(iter), ITER_HELPERS : __clobber_common ); return 0; } SEC("?raw_tp") __failure __msg("invalid read from stack") int __naked read_from_iter_slot_fail(void) { asm volatile ( /* r6 points to struct bpf_iter_num on the stack */ "r6 = r10;" "r6 += -24;" /* create iterator */ "r1 = r6;" "r2 = 0;" "r3 = 1000;" "call %[bpf_iter_num_new];" /* attemp to leak bpf_iter_num state */ "r7 = *(u64 *)(r6 + 0);" "r8 = *(u64 *)(r6 + 8);" /* destroy iterator */ "r1 = r6;" "call %[bpf_iter_num_destroy];" /* leak bpf_iter_num state */ "r0 = r7;" "if r7 > r8 goto +1;" "r0 = r8;" "exit;" : : ITER_HELPERS : __clobber_common, "r6", "r7", "r8" ); } int zero; SEC("?raw_tp") __failure __flag(BPF_F_TEST_STATE_FREQ) __msg("Unreleased reference") int stacksafe_should_not_conflate_stack_spill_and_iter(void *ctx) { struct bpf_iter_num iter; asm volatile ( /* Create a fork in logic, with general setup as follows: * - fallthrough (first) path is valid; * - branch (second) path is invalid. * Then depending on what we do in fallthrough vs branch path, * we try to detect bugs in func_states_equal(), regsafe(), * refsafe(), stack_safe(), and similar by tricking verifier * into believing that branch state is a valid subset of * a fallthrough state. Verifier should reject overall * validation, unless there is a bug somewhere in verifier * logic. */ "call %[bpf_get_prandom_u32];" "r6 = r0;" "call %[bpf_get_prandom_u32];" "r7 = r0;" "if r6 > r7 goto bad;" /* fork */ /* spill r6 into stack slot of bpf_iter_num var */ "*(u64 *)(%[iter] + 0) = r6;" "goto skip_bad;" "bad:" /* create iterator in the same stack slot */ "r1 = %[iter];" "r2 = 0;" "r3 = 1000;" "call %[bpf_iter_num_new];" /* but then forget about it and overwrite it back to r6 spill */ "*(u64 *)(%[iter] + 0) = r6;" "skip_bad:" "goto +0;" /* force checkpoint */ /* corrupt stack slots, if they are really dynptr */ "*(u64 *)(%[iter] + 0) = r6;" : : __imm_ptr(iter), __imm_addr(zero), __imm(bpf_get_prandom_u32), __imm(bpf_dynptr_from_mem), ITER_HELPERS : __clobber_common, "r6", "r7" ); return 0; }