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

bpf: Support pointers in global func args

Add an ability to pass a pointer to a type with known size in arguments
of a global function. Such pointers may be used to overcome the limit on
the maximum number of arguments, avoid expensive and tricky workarounds
and to have multiple output arguments.

A referenced type may contain pointers but indirect access through them
isn't supported.

The implementation consists of two parts. If a global function has an
argument that is a pointer to a type with known size then:

1) In btf_check_func_arg_match(): check that the corresponding
register points to NULL or to a valid memory region that is large enough
to contain the expected argument's type.

2) In btf_prepare_func_args(): set the corresponding register type to
PTR_TO_MEM_OR_NULL and its size to the size of the expected type.

Only global functions are supported because allowance of pointers for
static functions might break validation. Consider the following
scenario. A static function has a pointer argument. A caller passes
pointer to its stack memory. Because the callee can change referenced
memory verifier cannot longer assume any particular slot type of the
caller's stack memory hence the slot type is changed to SLOT_MISC. If
there is an operation that relies on slot type other than SLOT_MISC then
verifier won't be able to infer safety of the operation.

When verifier sees a static function that has a pointer argument
different from PTR_TO_CTX then it skips arguments check and continues
with "inline" validation with more information available. The operation
that relies on the particular slot type now succeeds.

Because global functions were not allowed to have pointer arguments
different from PTR_TO_CTX it's not possible to break existing and valid
code.

Signed-off-by: Dmitrii Banshchikov <me@ubique.spb.ru>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20210212205642.620788-4-me@ubique.spb.ru

authored by

Dmitrii Banshchikov and committed by
Alexei Starovoitov
e5069b9c 4ddb7416

+77 -10
+2
include/linux/bpf_verifier.h
··· 471 471 472 472 int check_ctx_reg(struct bpf_verifier_env *env, 473 473 const struct bpf_reg_state *reg, int regno); 474 + int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg, 475 + u32 regno, u32 mem_size); 474 476 475 477 /* this lives here instead of in bpf.h because it needs to dereference tgt_prog */ 476 478 static inline u64 bpf_trampoline_compute_key(const struct bpf_prog *tgt_prog,
+45 -10
kernel/bpf/btf.c
··· 5297 5297 struct bpf_prog *prog = env->prog; 5298 5298 struct btf *btf = prog->aux->btf; 5299 5299 const struct btf_param *args; 5300 - const struct btf_type *t; 5301 - u32 i, nargs, btf_id; 5300 + const struct btf_type *t, *ref_t; 5301 + u32 i, nargs, btf_id, type_size; 5302 5302 const char *tname; 5303 + bool is_global; 5303 5304 5304 5305 if (!prog->aux->func_info) 5305 5306 return -EINVAL; ··· 5334 5333 bpf_log(log, "Function %s has %d > 5 args\n", tname, nargs); 5335 5334 goto out; 5336 5335 } 5336 + 5337 + is_global = prog->aux->func_info_aux[subprog].linkage == BTF_FUNC_GLOBAL; 5337 5338 /* check that BTF function arguments match actual types that the 5338 5339 * verifier sees. 5339 5340 */ ··· 5352 5349 goto out; 5353 5350 } 5354 5351 if (btf_type_is_ptr(t)) { 5355 - if (reg->type == SCALAR_VALUE) { 5356 - bpf_log(log, "R%d is not a pointer\n", i + 1); 5357 - goto out; 5358 - } 5359 5352 /* If function expects ctx type in BTF check that caller 5360 5353 * is passing PTR_TO_CTX. 5361 5354 */ ··· 5366 5367 goto out; 5367 5368 continue; 5368 5369 } 5370 + 5371 + if (!is_global) 5372 + goto out; 5373 + 5374 + t = btf_type_skip_modifiers(btf, t->type, NULL); 5375 + 5376 + ref_t = btf_resolve_size(btf, t, &type_size); 5377 + if (IS_ERR(ref_t)) { 5378 + bpf_log(log, 5379 + "arg#%d reference type('%s %s') size cannot be determined: %ld\n", 5380 + i, btf_type_str(t), btf_name_by_offset(btf, t->name_off), 5381 + PTR_ERR(ref_t)); 5382 + goto out; 5383 + } 5384 + 5385 + if (check_mem_reg(env, reg, i + 1, type_size)) 5386 + goto out; 5387 + 5388 + continue; 5369 5389 } 5370 5390 bpf_log(log, "Unrecognized arg#%d type %s\n", 5371 5391 i, btf_kind_str[BTF_INFO_KIND(t->info)]); ··· 5415 5397 enum bpf_prog_type prog_type = prog->type; 5416 5398 struct btf *btf = prog->aux->btf; 5417 5399 const struct btf_param *args; 5418 - const struct btf_type *t; 5400 + const struct btf_type *t, *ref_t; 5419 5401 u32 i, nargs, btf_id; 5420 5402 const char *tname; 5421 5403 ··· 5488 5470 reg->type = SCALAR_VALUE; 5489 5471 continue; 5490 5472 } 5491 - if (btf_type_is_ptr(t) && 5492 - btf_get_prog_ctx_type(log, btf, t, prog_type, i)) { 5493 - reg->type = PTR_TO_CTX; 5473 + if (btf_type_is_ptr(t)) { 5474 + if (btf_get_prog_ctx_type(log, btf, t, prog_type, i)) { 5475 + reg->type = PTR_TO_CTX; 5476 + continue; 5477 + } 5478 + 5479 + t = btf_type_skip_modifiers(btf, t->type, NULL); 5480 + 5481 + ref_t = btf_resolve_size(btf, t, &reg->mem_size); 5482 + if (IS_ERR(ref_t)) { 5483 + bpf_log(log, 5484 + "arg#%d reference type('%s %s') size cannot be determined: %ld\n", 5485 + i, btf_type_str(t), btf_name_by_offset(btf, t->name_off), 5486 + PTR_ERR(ref_t)); 5487 + return -EINVAL; 5488 + } 5489 + 5490 + reg->type = PTR_TO_MEM_OR_NULL; 5491 + reg->id = ++env->id_gen; 5492 + 5494 5493 continue; 5495 5494 } 5496 5495 bpf_log(log, "Arg#%d type %s in %s() is not supported yet.\n",
+30
kernel/bpf/verifier.c
··· 4272 4272 } 4273 4273 } 4274 4274 4275 + int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg, 4276 + u32 regno, u32 mem_size) 4277 + { 4278 + if (register_is_null(reg)) 4279 + return 0; 4280 + 4281 + if (reg_type_may_be_null(reg->type)) { 4282 + /* Assuming that the register contains a value check if the memory 4283 + * access is safe. Temporarily save and restore the register's state as 4284 + * the conversion shouldn't be visible to a caller. 4285 + */ 4286 + const struct bpf_reg_state saved_reg = *reg; 4287 + int rv; 4288 + 4289 + mark_ptr_not_null_reg(reg); 4290 + rv = check_helper_mem_access(env, regno, mem_size, true, NULL); 4291 + *reg = saved_reg; 4292 + return rv; 4293 + } 4294 + 4295 + return check_helper_mem_access(env, regno, mem_size, true, NULL); 4296 + } 4297 + 4275 4298 /* Implementation details: 4276 4299 * bpf_map_lookup returns PTR_TO_MAP_VALUE_OR_NULL 4277 4300 * Two bpf_map_lookups (even with the same key) will have different reg->id. ··· 11983 11960 mark_reg_known_zero(env, regs, i); 11984 11961 else if (regs[i].type == SCALAR_VALUE) 11985 11962 mark_reg_unknown(env, regs, i); 11963 + else if (regs[i].type == PTR_TO_MEM_OR_NULL) { 11964 + const u32 mem_size = regs[i].mem_size; 11965 + 11966 + mark_reg_known_zero(env, regs, i); 11967 + regs[i].mem_size = mem_size; 11968 + regs[i].id = ++env->id_gen; 11969 + } 11986 11970 } 11987 11971 } else { 11988 11972 /* 1st arg to a function */