Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

bpf: relax zero fixed offset constraint on KF_TRUSTED_ARGS/KF_RCU

Currently, BPF kfuncs which accept trusted pointer arguments
i.e. those flagged as KF_TRUSTED_ARGS, KF_RCU, or KF_RELEASE, all
require an original/unmodified trusted pointer argument to be supplied
to them. By original/unmodified, it means that the backing register
holding the trusted pointer argument that is to be supplied to the BPF
kfunc must have its fixed offset set to zero, or else the BPF verifier
will outright reject the BPF program load. However, this zero fixed
offset constraint that is currently enforced by the BPF verifier onto
BPF kfuncs specifically flagged to accept KF_TRUSTED_ARGS or KF_RCU
trusted pointer arguments is rather unnecessary, and can limit their
usability in practice. Specifically, it completely eliminates the
possibility of constructing a derived trusted pointer from an original
trusted pointer. To put it simply, a derived pointer is a pointer
which points to one of the nested member fields of the object being
pointed to by the original trusted pointer.

This patch relaxes the zero fixed offset constraint that is enforced
upon BPF kfuncs which specifically accept KF_TRUSTED_ARGS, or KF_RCU
arguments. Although, the zero fixed offset constraint technically also
applies to BPF kfuncs accepting KF_RELEASE arguments, relaxing this
constraint for such BPF kfuncs has subtle and unwanted
side-effects. This was discovered by experimenting a little further
with an initial version of this patch series [0]. The primary issue
with relaxing the zero fixed offset constraint on BPF kfuncs accepting
KF_RELEASE arguments is that it'd would open up the opportunity for
BPF programs to supply both trusted pointers and derived trusted
pointers to them. For KF_RELEASE BPF kfuncs specifically, this could
be problematic as resources associated with the backing pointer could
be released by the backing BPF kfunc and cause instabilities for the
rest of the kernel.

With this new fixed offset semantic in-place for BPF kfuncs accepting
KF_TRUSTED_ARGS and KF_RCU arguments, we now have more flexibility
when it comes to the BPF kfuncs that we're able to introduce moving
forward.

Early discussions covering the possibility of relaxing the zero fixed
offset constraint can be found using the link below. This will provide
more context on where all this has stemmed from [1].

Notably, pre-existing tests have been updated such that they provide
coverage for the updated zero fixed offset
functionality. Specifically, the nested offset test was converted from
a negative to positive test as it was already designed to assert zero
fixed offset semantics of a KF_TRUSTED_ARGS BPF kfunc.

[0] https://lore.kernel.org/bpf/ZnA9ndnXKtHOuYMe@google.com/
[1] https://lore.kernel.org/bpf/ZhkbrM55MKQ0KeIV@google.com/

Signed-off-by: Matt Bobrowski <mattbobrowski@google.com>
Acked-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20240709210939.1544011-1-mattbobrowski@google.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

Matt Bobrowski and committed by
Alexei Starovoitov
605c9699 02779af2

+12 -15
+3 -6
kernel/bpf/verifier.c
··· 11335 11335 btf_type_ids_nocast_alias(&env->log, reg_btf, reg_ref_id, meta->btf, ref_id)) 11336 11336 strict_type_match = true; 11337 11337 11338 - WARN_ON_ONCE(is_kfunc_trusted_args(meta) && reg->off); 11338 + WARN_ON_ONCE(is_kfunc_release(meta) && 11339 + (reg->off || !tnum_is_const(reg->var_off) || 11340 + reg->var_off.value)); 11339 11341 11340 11342 reg_ref_t = btf_type_skip_modifiers(reg_btf, reg_ref_id, &reg_ref_id); 11341 11343 reg_ref_tname = btf_name_by_offset(reg_btf, reg_ref_t->name_off); ··· 11919 11917 return -EINVAL; 11920 11918 } 11921 11919 } 11922 - 11923 11920 fallthrough; 11924 11921 case KF_ARG_PTR_TO_CTX: 11925 - /* Trusted arguments have the same offset checks as release arguments */ 11926 - arg_type |= OBJ_RELEASE; 11927 - break; 11928 11922 case KF_ARG_PTR_TO_DYNPTR: 11929 11923 case KF_ARG_PTR_TO_ITER: 11930 11924 case KF_ARG_PTR_TO_LIST_HEAD: ··· 11933 11935 case KF_ARG_PTR_TO_REFCOUNTED_KPTR: 11934 11936 case KF_ARG_PTR_TO_CONST_STR: 11935 11937 case KF_ARG_PTR_TO_WORKQUEUE: 11936 - /* Trusted by default */ 11937 11938 break; 11938 11939 default: 11939 11940 WARN_ON_ONCE(1);
-8
tools/testing/selftests/bpf/progs/nested_trust_failure.c
··· 31 31 return 0; 32 32 } 33 33 34 - SEC("tp_btf/task_newtask") 35 - __failure __msg("R1 must have zero offset when passed to release func or trusted arg to kfunc") 36 - int BPF_PROG(test_invalid_nested_offset, struct task_struct *task, u64 clone_flags) 37 - { 38 - bpf_cpumask_first_zero(&task->cpus_mask); 39 - return 0; 40 - } 41 - 42 34 /* Although R2 is of type sk_buff but sock_common is expected, we will hit untrusted ptr first. */ 43 35 SEC("tp_btf/tcp_probe") 44 36 __failure __msg("R2 type=untrusted_ptr_ expected=ptr_, trusted_ptr_, rcu_ptr_")
+8
tools/testing/selftests/bpf/progs/nested_trust_success.c
··· 32 32 bpf_sk_storage_get(&sk_storage_map, skb->sk, 0, 0); 33 33 return 0; 34 34 } 35 + 36 + SEC("tp_btf/task_newtask") 37 + __success 38 + int BPF_PROG(test_nested_offset, struct task_struct *task, u64 clone_flags) 39 + { 40 + bpf_cpumask_first_zero(&task->cpus_mask); 41 + return 0; 42 + }
+1 -1
tools/testing/selftests/bpf/verifier/calls.c
··· 76 76 }, 77 77 .prog_type = BPF_PROG_TYPE_SCHED_CLS, 78 78 .result = REJECT, 79 - .errstr = "R1 must have zero offset when passed to release func or trusted arg to kfunc", 79 + .errstr = "arg#0 expected pointer to ctx, but got PTR", 80 80 .fixup_kfunc_btf_id = { 81 81 { "bpf_kfunc_call_test_pass_ctx", 2 }, 82 82 },