Skip to content

Commit 0ba9715

Browse files
anakryikoAlexei Starovoitov
authored andcommitted
bpf: enforce types for __arg_ctx-tagged arguments in global subprogs
Add enforcement of expected types for context arguments tagged with arg:ctx (__arg_ctx) tag. First, any program type will accept generic `void *` context type when combined with __arg_ctx tag. Besides accepting "canonical" struct names and `void *`, for a bunch of program types for which program context is actually a named struct, we allows a bunch of pragmatic exceptions to match real-world and expected usage: - for both kprobes and perf_event we allow `bpf_user_pt_regs_t *` as canonical context argument type, where `bpf_user_pt_regs_t` is a *typedef*, not a struct; - for kprobes, we also always accept `struct pt_regs *`, as that's what actually is passed as a context to any kprobe program; - for perf_event, we resolve typedefs (unless it's `bpf_user_pt_regs_t`) down to actual struct type and accept `struct pt_regs *`, or `struct user_pt_regs *`, or `struct user_regs_struct *`, depending on the actual struct type kernel architecture points `bpf_user_pt_regs_t` typedef to; otherwise, canonical `struct bpf_perf_event_data *` is expected; - for raw_tp/raw_tp.w programs, `u64/long *` are accepted, as that's what's expected with BPF_PROG() usage; otherwise, canonical `struct bpf_raw_tracepoint_args *` is expected; - tp_btf supports both `struct bpf_raw_tracepoint_args *` and `u64 *` formats, both are coded as expections as tp_btf is actually a TRACING program type, which has no canonical context type; - iterator programs accept `struct bpf_iter__xxx *` structs, currently with no further iterator-type specific enforcement; - fentry/fexit/fmod_ret/lsm/struct_ops all accept `u64 *`; - classic tracepoint programs, as well as syscall and freplace programs allow any user-provided type. In all other cases kernel will enforce exact match of struct name to expected canonical type. And if user-provided type doesn't match that expectation, verifier will emit helpful message with expected type name. Note a bit unnatural way the check is done after processing all the arguments. This is done to avoid conflict between bpf and bpf-next trees. Once trees converge, a small follow up patch will place a simple btf_validate_prog_ctx_type() check into a proper ARG_PTR_TO_CTX branch (which bpf-next tree patch refactored already), removing duplicated arg:ctx detection logic. Suggested-by: Alexei Starovoitov <[email protected]> Signed-off-by: Andrii Nakryiko <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
1 parent 66967a3 commit 0ba9715

File tree

1 file changed

+160
-0
lines changed

1 file changed

+160
-0
lines changed

kernel/bpf/btf.c

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5709,6 +5709,149 @@ btf_get_prog_ctx_type(struct bpf_verifier_log *log, const struct btf *btf,
57095709
return ctx_type;
57105710
}
57115711

5712+
/* forward declarations for arch-specific underlying types of
5713+
* bpf_user_pt_regs_t; this avoids the need for arch-specific #ifdef
5714+
* compilation guards below for BPF_PROG_TYPE_PERF_EVENT checks, but still
5715+
* works correctly with __builtin_types_compatible_p() on respective
5716+
* architectures
5717+
*/
5718+
struct user_regs_struct;
5719+
struct user_pt_regs;
5720+
5721+
static int btf_validate_prog_ctx_type(struct bpf_verifier_log *log, const struct btf *btf,
5722+
const struct btf_type *t, int arg,
5723+
enum bpf_prog_type prog_type,
5724+
enum bpf_attach_type attach_type)
5725+
{
5726+
const struct btf_type *ctx_type;
5727+
const char *tname, *ctx_tname;
5728+
5729+
if (!btf_is_ptr(t)) {
5730+
bpf_log(log, "arg#%d type isn't a pointer\n", arg);
5731+
return -EINVAL;
5732+
}
5733+
t = btf_type_by_id(btf, t->type);
5734+
5735+
/* KPROBE and PERF_EVENT programs allow bpf_user_pt_regs_t typedef */
5736+
if (prog_type == BPF_PROG_TYPE_KPROBE || prog_type == BPF_PROG_TYPE_PERF_EVENT) {
5737+
while (btf_type_is_modifier(t) && !btf_type_is_typedef(t))
5738+
t = btf_type_by_id(btf, t->type);
5739+
5740+
if (btf_type_is_typedef(t)) {
5741+
tname = btf_name_by_offset(btf, t->name_off);
5742+
if (tname && strcmp(tname, "bpf_user_pt_regs_t") == 0)
5743+
return 0;
5744+
}
5745+
}
5746+
5747+
/* all other program types don't use typedefs for context type */
5748+
while (btf_type_is_modifier(t))
5749+
t = btf_type_by_id(btf, t->type);
5750+
5751+
/* `void *ctx __arg_ctx` is always valid */
5752+
if (btf_type_is_void(t))
5753+
return 0;
5754+
5755+
tname = btf_name_by_offset(btf, t->name_off);
5756+
if (str_is_empty(tname)) {
5757+
bpf_log(log, "arg#%d type doesn't have a name\n", arg);
5758+
return -EINVAL;
5759+
}
5760+
5761+
/* special cases */
5762+
switch (prog_type) {
5763+
case BPF_PROG_TYPE_KPROBE:
5764+
if (__btf_type_is_struct(t) && strcmp(tname, "pt_regs") == 0)
5765+
return 0;
5766+
break;
5767+
case BPF_PROG_TYPE_PERF_EVENT:
5768+
if (__builtin_types_compatible_p(bpf_user_pt_regs_t, struct pt_regs) &&
5769+
__btf_type_is_struct(t) && strcmp(tname, "pt_regs") == 0)
5770+
return 0;
5771+
if (__builtin_types_compatible_p(bpf_user_pt_regs_t, struct user_pt_regs) &&
5772+
__btf_type_is_struct(t) && strcmp(tname, "user_pt_regs") == 0)
5773+
return 0;
5774+
if (__builtin_types_compatible_p(bpf_user_pt_regs_t, struct user_regs_struct) &&
5775+
__btf_type_is_struct(t) && strcmp(tname, "user_regs_struct") == 0)
5776+
return 0;
5777+
break;
5778+
case BPF_PROG_TYPE_RAW_TRACEPOINT:
5779+
case BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE:
5780+
/* allow u64* as ctx */
5781+
if (btf_is_int(t) && t->size == 8)
5782+
return 0;
5783+
break;
5784+
case BPF_PROG_TYPE_TRACING:
5785+
switch (attach_type) {
5786+
case BPF_TRACE_RAW_TP:
5787+
/* tp_btf program is TRACING, so need special case here */
5788+
if (__btf_type_is_struct(t) &&
5789+
strcmp(tname, "bpf_raw_tracepoint_args") == 0)
5790+
return 0;
5791+
/* allow u64* as ctx */
5792+
if (btf_is_int(t) && t->size == 8)
5793+
return 0;
5794+
break;
5795+
case BPF_TRACE_ITER:
5796+
/* allow struct bpf_iter__xxx types only */
5797+
if (__btf_type_is_struct(t) &&
5798+
strncmp(tname, "bpf_iter__", sizeof("bpf_iter__") - 1) == 0)
5799+
return 0;
5800+
break;
5801+
case BPF_TRACE_FENTRY:
5802+
case BPF_TRACE_FEXIT:
5803+
case BPF_MODIFY_RETURN:
5804+
/* allow u64* as ctx */
5805+
if (btf_is_int(t) && t->size == 8)
5806+
return 0;
5807+
break;
5808+
default:
5809+
break;
5810+
}
5811+
break;
5812+
case BPF_PROG_TYPE_LSM:
5813+
case BPF_PROG_TYPE_STRUCT_OPS:
5814+
/* allow u64* as ctx */
5815+
if (btf_is_int(t) && t->size == 8)
5816+
return 0;
5817+
break;
5818+
case BPF_PROG_TYPE_TRACEPOINT:
5819+
case BPF_PROG_TYPE_SYSCALL:
5820+
case BPF_PROG_TYPE_EXT:
5821+
return 0; /* anything goes */
5822+
default:
5823+
break;
5824+
}
5825+
5826+
ctx_type = find_canonical_prog_ctx_type(prog_type);
5827+
if (!ctx_type) {
5828+
/* should not happen */
5829+
bpf_log(log, "btf_vmlinux is malformed\n");
5830+
return -EINVAL;
5831+
}
5832+
5833+
/* resolve typedefs and check that underlying structs are matching as well */
5834+
while (btf_type_is_modifier(ctx_type))
5835+
ctx_type = btf_type_by_id(btf_vmlinux, ctx_type->type);
5836+
5837+
/* if program type doesn't have distinctly named struct type for
5838+
* context, then __arg_ctx argument can only be `void *`, which we
5839+
* already checked above
5840+
*/
5841+
if (!__btf_type_is_struct(ctx_type)) {
5842+
bpf_log(log, "arg#%d should be void pointer\n", arg);
5843+
return -EINVAL;
5844+
}
5845+
5846+
ctx_tname = btf_name_by_offset(btf_vmlinux, ctx_type->name_off);
5847+
if (!__btf_type_is_struct(t) || strcmp(ctx_tname, tname) != 0) {
5848+
bpf_log(log, "arg#%d should be `struct %s *`\n", arg, ctx_tname);
5849+
return -EINVAL;
5850+
}
5851+
5852+
return 0;
5853+
}
5854+
57125855
static int btf_translate_to_vmlinux(struct bpf_verifier_log *log,
57135856
struct btf *btf,
57145857
const struct btf_type *t,
@@ -6953,6 +7096,23 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
69537096
return -EINVAL;
69547097
}
69557098

7099+
for (i = 0; i < nargs; i++) {
7100+
const char *tag;
7101+
7102+
if (sub->args[i].arg_type != ARG_PTR_TO_CTX)
7103+
continue;
7104+
7105+
/* check if arg has "arg:ctx" tag */
7106+
t = btf_type_by_id(btf, args[i].type);
7107+
tag = btf_find_decl_tag_value(btf, fn_t, i, "arg:");
7108+
if (IS_ERR_OR_NULL(tag) || strcmp(tag, "ctx") != 0)
7109+
continue;
7110+
7111+
if (btf_validate_prog_ctx_type(log, btf, t, i, prog_type,
7112+
prog->expected_attach_type))
7113+
return -EINVAL;
7114+
}
7115+
69567116
sub->arg_cnt = nargs;
69577117
sub->args_cached = true;
69587118

0 commit comments

Comments
 (0)