Skip to content

Commit a7658e1

Browse files
Alexei Starovoitovborkmann
Alexei Starovoitov
authored andcommitted
bpf: Check types of arguments passed into helpers
Introduce new helper that reuses existing skb perf_event output implementation, but can be called from raw_tracepoint programs that receive 'struct sk_buff *' as tracepoint argument or can walk other kernel data structures to skb pointer. In order to do that teach verifier to resolve true C types of bpf helpers into in-kernel BTF ids. The type of kernel pointer passed by raw tracepoint into bpf program will be tracked by the verifier all the way until it's passed into helper function. For example: kfree_skb() kernel function calls trace_kfree_skb(skb, loc); bpf programs receives that skb pointer and may eventually pass it into bpf_skb_output() bpf helper which in-kernel is implemented via bpf_skb_event_output() kernel function. Its first argument in the kernel is 'struct sk_buff *'. The verifier makes sure that types match all the way. Signed-off-by: Alexei Starovoitov <[email protected]> Signed-off-by: Daniel Borkmann <[email protected]> Acked-by: Andrii Nakryiko <[email protected]> Acked-by: Martin KaFai Lau <[email protected]> Link: https://lore.kernel.org/bpf/[email protected]
1 parent 3dec541 commit a7658e1

File tree

7 files changed

+180
-23
lines changed

7 files changed

+180
-23
lines changed

Diff for: include/linux/bpf.h

+13-5
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ enum bpf_arg_type {
213213
ARG_PTR_TO_INT, /* pointer to int */
214214
ARG_PTR_TO_LONG, /* pointer to long */
215215
ARG_PTR_TO_SOCKET, /* pointer to bpf_sock (fullsock) */
216+
ARG_PTR_TO_BTF_ID, /* pointer to in-kernel struct */
216217
};
217218

218219
/* type of values returned from helper functions */
@@ -235,11 +236,17 @@ struct bpf_func_proto {
235236
bool gpl_only;
236237
bool pkt_access;
237238
enum bpf_return_type ret_type;
238-
enum bpf_arg_type arg1_type;
239-
enum bpf_arg_type arg2_type;
240-
enum bpf_arg_type arg3_type;
241-
enum bpf_arg_type arg4_type;
242-
enum bpf_arg_type arg5_type;
239+
union {
240+
struct {
241+
enum bpf_arg_type arg1_type;
242+
enum bpf_arg_type arg2_type;
243+
enum bpf_arg_type arg3_type;
244+
enum bpf_arg_type arg4_type;
245+
enum bpf_arg_type arg5_type;
246+
};
247+
enum bpf_arg_type arg_type[5];
248+
};
249+
u32 *btf_id; /* BTF ids of arguments */
243250
};
244251

245252
/* bpf_context is intentionally undefined structure. Pointer to bpf_context is
@@ -765,6 +772,7 @@ int btf_struct_access(struct bpf_verifier_log *log,
765772
const struct btf_type *t, int off, int size,
766773
enum bpf_access_type atype,
767774
u32 *next_btf_id);
775+
u32 btf_resolve_helper_id(struct bpf_verifier_log *log, void *, int);
768776

769777
#else /* !CONFIG_BPF_SYSCALL */
770778
static inline struct bpf_prog *bpf_prog_get(u32 ufd)

Diff for: include/uapi/linux/bpf.h

+26-1
Original file line numberDiff line numberDiff line change
@@ -2751,6 +2751,30 @@ union bpf_attr {
27512751
* **-EOPNOTSUPP** kernel configuration does not enable SYN cookies
27522752
*
27532753
* **-EPROTONOSUPPORT** IP packet version is not 4 or 6
2754+
*
2755+
* int bpf_skb_output(void *ctx, struct bpf_map *map, u64 flags, void *data, u64 size)
2756+
* Description
2757+
* Write raw *data* blob into a special BPF perf event held by
2758+
* *map* of type **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. This perf
2759+
* event must have the following attributes: **PERF_SAMPLE_RAW**
2760+
* as **sample_type**, **PERF_TYPE_SOFTWARE** as **type**, and
2761+
* **PERF_COUNT_SW_BPF_OUTPUT** as **config**.
2762+
*
2763+
* The *flags* are used to indicate the index in *map* for which
2764+
* the value must be put, masked with **BPF_F_INDEX_MASK**.
2765+
* Alternatively, *flags* can be set to **BPF_F_CURRENT_CPU**
2766+
* to indicate that the index of the current CPU core should be
2767+
* used.
2768+
*
2769+
* The value to write, of *size*, is passed through eBPF stack and
2770+
* pointed by *data*.
2771+
*
2772+
* *ctx* is a pointer to in-kernel struct sk_buff.
2773+
*
2774+
* This helper is similar to **bpf_perf_event_output**\ () but
2775+
* restricted to raw_tracepoint bpf programs.
2776+
* Return
2777+
* 0 on success, or a negative error in case of failure.
27542778
*/
27552779
#define __BPF_FUNC_MAPPER(FN) \
27562780
FN(unspec), \
@@ -2863,7 +2887,8 @@ union bpf_attr {
28632887
FN(sk_storage_get), \
28642888
FN(sk_storage_delete), \
28652889
FN(send_signal), \
2866-
FN(tcp_gen_syncookie),
2890+
FN(tcp_gen_syncookie), \
2891+
FN(skb_output),
28672892

28682893
/* integer value in 'imm' field of BPF_CALL instruction selects which helper
28692894
* function eBPF program intends to call

Diff for: kernel/bpf/btf.c

+68
Original file line numberDiff line numberDiff line change
@@ -3626,6 +3626,74 @@ int btf_struct_access(struct bpf_verifier_log *log,
36263626
return -EINVAL;
36273627
}
36283628

3629+
u32 btf_resolve_helper_id(struct bpf_verifier_log *log, void *fn, int arg)
3630+
{
3631+
char fnname[KSYM_SYMBOL_LEN + 4] = "btf_";
3632+
const struct btf_param *args;
3633+
const struct btf_type *t;
3634+
const char *tname, *sym;
3635+
u32 btf_id, i;
3636+
3637+
if (IS_ERR(btf_vmlinux)) {
3638+
bpf_log(log, "btf_vmlinux is malformed\n");
3639+
return -EINVAL;
3640+
}
3641+
3642+
sym = kallsyms_lookup((long)fn, NULL, NULL, NULL, fnname + 4);
3643+
if (!sym) {
3644+
bpf_log(log, "kernel doesn't have kallsyms\n");
3645+
return -EFAULT;
3646+
}
3647+
3648+
for (i = 1; i <= btf_vmlinux->nr_types; i++) {
3649+
t = btf_type_by_id(btf_vmlinux, i);
3650+
if (BTF_INFO_KIND(t->info) != BTF_KIND_TYPEDEF)
3651+
continue;
3652+
tname = __btf_name_by_offset(btf_vmlinux, t->name_off);
3653+
if (!strcmp(tname, fnname))
3654+
break;
3655+
}
3656+
if (i > btf_vmlinux->nr_types) {
3657+
bpf_log(log, "helper %s type is not found\n", fnname);
3658+
return -ENOENT;
3659+
}
3660+
3661+
t = btf_type_by_id(btf_vmlinux, t->type);
3662+
if (!btf_type_is_ptr(t))
3663+
return -EFAULT;
3664+
t = btf_type_by_id(btf_vmlinux, t->type);
3665+
if (!btf_type_is_func_proto(t))
3666+
return -EFAULT;
3667+
3668+
args = (const struct btf_param *)(t + 1);
3669+
if (arg >= btf_type_vlen(t)) {
3670+
bpf_log(log, "bpf helper %s doesn't have %d-th argument\n",
3671+
fnname, arg);
3672+
return -EINVAL;
3673+
}
3674+
3675+
t = btf_type_by_id(btf_vmlinux, args[arg].type);
3676+
if (!btf_type_is_ptr(t) || !t->type) {
3677+
/* anything but the pointer to struct is a helper config bug */
3678+
bpf_log(log, "ARG_PTR_TO_BTF is misconfigured\n");
3679+
return -EFAULT;
3680+
}
3681+
btf_id = t->type;
3682+
t = btf_type_by_id(btf_vmlinux, t->type);
3683+
/* skip modifiers */
3684+
while (btf_type_is_modifier(t)) {
3685+
btf_id = t->type;
3686+
t = btf_type_by_id(btf_vmlinux, t->type);
3687+
}
3688+
if (!btf_type_is_struct(t)) {
3689+
bpf_log(log, "ARG_PTR_TO_BTF is not a struct\n");
3690+
return -EFAULT;
3691+
}
3692+
bpf_log(log, "helper %s arg%d has btf_id %d struct %s\n", fnname + 4,
3693+
arg, btf_id, __btf_name_by_offset(btf_vmlinux, t->name_off));
3694+
return btf_id;
3695+
}
3696+
36293697
void btf_type_seq_show(const struct btf *btf, u32 type_id, void *obj,
36303698
struct seq_file *m)
36313699
{

Diff for: kernel/bpf/verifier.c

+29-15
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ struct bpf_call_arg_meta {
205205
u64 msize_umax_value;
206206
int ref_obj_id;
207207
int func_id;
208+
u32 btf_id;
208209
};
209210

210211
struct btf *btf_vmlinux;
@@ -3439,6 +3440,22 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 regno,
34393440
expected_type = PTR_TO_SOCKET;
34403441
if (type != expected_type)
34413442
goto err_type;
3443+
} else if (arg_type == ARG_PTR_TO_BTF_ID) {
3444+
expected_type = PTR_TO_BTF_ID;
3445+
if (type != expected_type)
3446+
goto err_type;
3447+
if (reg->btf_id != meta->btf_id) {
3448+
verbose(env, "Helper has type %s got %s in R%d\n",
3449+
kernel_type_name(meta->btf_id),
3450+
kernel_type_name(reg->btf_id), regno);
3451+
3452+
return -EACCES;
3453+
}
3454+
if (!tnum_is_const(reg->var_off) || reg->var_off.value || reg->off) {
3455+
verbose(env, "R%d is a pointer to in-kernel struct with non-zero offset\n",
3456+
regno);
3457+
return -EACCES;
3458+
}
34423459
} else if (arg_type == ARG_PTR_TO_SPIN_LOCK) {
34433460
if (meta->func_id == BPF_FUNC_spin_lock) {
34443461
if (process_spin_lock(env, regno, true))
@@ -3586,6 +3603,7 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env,
35863603
case BPF_MAP_TYPE_PERF_EVENT_ARRAY:
35873604
if (func_id != BPF_FUNC_perf_event_read &&
35883605
func_id != BPF_FUNC_perf_event_output &&
3606+
func_id != BPF_FUNC_skb_output &&
35893607
func_id != BPF_FUNC_perf_event_read_value)
35903608
goto error;
35913609
break;
@@ -3673,6 +3691,7 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env,
36733691
case BPF_FUNC_perf_event_read:
36743692
case BPF_FUNC_perf_event_output:
36753693
case BPF_FUNC_perf_event_read_value:
3694+
case BPF_FUNC_skb_output:
36763695
if (map->map_type != BPF_MAP_TYPE_PERF_EVENT_ARRAY)
36773696
goto error;
36783697
break;
@@ -4127,21 +4146,16 @@ static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
41274146

41284147
meta.func_id = func_id;
41294148
/* check args */
4130-
err = check_func_arg(env, BPF_REG_1, fn->arg1_type, &meta);
4131-
if (err)
4132-
return err;
4133-
err = check_func_arg(env, BPF_REG_2, fn->arg2_type, &meta);
4134-
if (err)
4135-
return err;
4136-
err = check_func_arg(env, BPF_REG_3, fn->arg3_type, &meta);
4137-
if (err)
4138-
return err;
4139-
err = check_func_arg(env, BPF_REG_4, fn->arg4_type, &meta);
4140-
if (err)
4141-
return err;
4142-
err = check_func_arg(env, BPF_REG_5, fn->arg5_type, &meta);
4143-
if (err)
4144-
return err;
4149+
for (i = 0; i < 5; i++) {
4150+
if (fn->arg_type[i] == ARG_PTR_TO_BTF_ID) {
4151+
if (!fn->btf_id[i])
4152+
fn->btf_id[i] = btf_resolve_helper_id(&env->log, fn->func, i);
4153+
meta.btf_id = fn->btf_id[i];
4154+
}
4155+
err = check_func_arg(env, BPF_REG_1 + i, fn->arg_type[i], &meta);
4156+
if (err)
4157+
return err;
4158+
}
41454159

41464160
err = record_func_map(env, &meta, func_id, insn_idx);
41474161
if (err)

Diff for: kernel/trace/bpf_trace.c

+4
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,8 @@ static const struct bpf_func_proto bpf_perf_event_output_proto_raw_tp = {
995995
.arg5_type = ARG_CONST_SIZE_OR_ZERO,
996996
};
997997

998+
extern const struct bpf_func_proto bpf_skb_output_proto;
999+
9981000
BPF_CALL_3(bpf_get_stackid_raw_tp, struct bpf_raw_tracepoint_args *, args,
9991001
struct bpf_map *, map, u64, flags)
10001002
{
@@ -1053,6 +1055,8 @@ raw_tp_prog_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
10531055
switch (func_id) {
10541056
case BPF_FUNC_perf_event_output:
10551057
return &bpf_perf_event_output_proto_raw_tp;
1058+
case BPF_FUNC_skb_output:
1059+
return &bpf_skb_output_proto;
10561060
case BPF_FUNC_get_stackid:
10571061
return &bpf_get_stackid_proto_raw_tp;
10581062
case BPF_FUNC_get_stack:

Diff for: net/core/filter.c

+14-1
Original file line numberDiff line numberDiff line change
@@ -3798,7 +3798,7 @@ BPF_CALL_5(bpf_skb_event_output, struct sk_buff *, skb, struct bpf_map *, map,
37983798

37993799
if (unlikely(flags & ~(BPF_F_CTXLEN_MASK | BPF_F_INDEX_MASK)))
38003800
return -EINVAL;
3801-
if (unlikely(skb_size > skb->len))
3801+
if (unlikely(!skb || skb_size > skb->len))
38023802
return -EFAULT;
38033803

38043804
return bpf_event_output(map, flags, meta, meta_size, skb, skb_size,
@@ -3816,6 +3816,19 @@ static const struct bpf_func_proto bpf_skb_event_output_proto = {
38163816
.arg5_type = ARG_CONST_SIZE_OR_ZERO,
38173817
};
38183818

3819+
static u32 bpf_skb_output_btf_ids[5];
3820+
const struct bpf_func_proto bpf_skb_output_proto = {
3821+
.func = bpf_skb_event_output,
3822+
.gpl_only = true,
3823+
.ret_type = RET_INTEGER,
3824+
.arg1_type = ARG_PTR_TO_BTF_ID,
3825+
.arg2_type = ARG_CONST_MAP_PTR,
3826+
.arg3_type = ARG_ANYTHING,
3827+
.arg4_type = ARG_PTR_TO_MEM,
3828+
.arg5_type = ARG_CONST_SIZE_OR_ZERO,
3829+
.btf_id = bpf_skb_output_btf_ids,
3830+
};
3831+
38193832
static unsigned short bpf_tunnel_key_af(u64 flags)
38203833
{
38213834
return flags & BPF_F_TUNINFO_IPV6 ? AF_INET6 : AF_INET;

Diff for: tools/include/uapi/linux/bpf.h

+26-1
Original file line numberDiff line numberDiff line change
@@ -2751,6 +2751,30 @@ union bpf_attr {
27512751
* **-EOPNOTSUPP** kernel configuration does not enable SYN cookies
27522752
*
27532753
* **-EPROTONOSUPPORT** IP packet version is not 4 or 6
2754+
*
2755+
* int bpf_skb_output(void *ctx, struct bpf_map *map, u64 flags, void *data, u64 size)
2756+
* Description
2757+
* Write raw *data* blob into a special BPF perf event held by
2758+
* *map* of type **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. This perf
2759+
* event must have the following attributes: **PERF_SAMPLE_RAW**
2760+
* as **sample_type**, **PERF_TYPE_SOFTWARE** as **type**, and
2761+
* **PERF_COUNT_SW_BPF_OUTPUT** as **config**.
2762+
*
2763+
* The *flags* are used to indicate the index in *map* for which
2764+
* the value must be put, masked with **BPF_F_INDEX_MASK**.
2765+
* Alternatively, *flags* can be set to **BPF_F_CURRENT_CPU**
2766+
* to indicate that the index of the current CPU core should be
2767+
* used.
2768+
*
2769+
* The value to write, of *size*, is passed through eBPF stack and
2770+
* pointed by *data*.
2771+
*
2772+
* *ctx* is a pointer to in-kernel struct sk_buff.
2773+
*
2774+
* This helper is similar to **bpf_perf_event_output**\ () but
2775+
* restricted to raw_tracepoint bpf programs.
2776+
* Return
2777+
* 0 on success, or a negative error in case of failure.
27542778
*/
27552779
#define __BPF_FUNC_MAPPER(FN) \
27562780
FN(unspec), \
@@ -2863,7 +2887,8 @@ union bpf_attr {
28632887
FN(sk_storage_get), \
28642888
FN(sk_storage_delete), \
28652889
FN(send_signal), \
2866-
FN(tcp_gen_syncookie),
2890+
FN(tcp_gen_syncookie), \
2891+
FN(skb_output),
28672892

28682893
/* integer value in 'imm' field of BPF_CALL instruction selects which helper
28692894
* function eBPF program intends to call

0 commit comments

Comments
 (0)