Skip to content

Commit cf8b876

Browse files
author
Alexei Starovoitov
committed
Merge branch 'bpf-track-changes_pkt_data-property-for-global-functions'
Eduard Zingerman says: ==================== bpf: track changes_pkt_data property for global functions Nick Zavaritsky reported [0] a bug in verifier, where the following unsafe program is not rejected: __attribute__((__noinline__)) long skb_pull_data(struct __sk_buff *sk, __u32 len) { return bpf_skb_pull_data(sk, len); } SEC("tc") int test_invalidate_checks(struct __sk_buff *sk) { int *p = (void *)(long)sk->data; if ((void *)(p + 1) > (void *)(long)sk->data_end) return TCX_DROP; skb_pull_data(sk, 0); /* not safe, p is invalid after bpf_skb_pull_data call */ *p = 42; return TCX_PASS; } This happens because verifier does not track package invalidation effect of global sub-programs. This patch-set fixes the issue by modifying check_cfg() to compute whether or not each sub-program calls (directly or indirectly) helper invalidating packet pointers. As global functions could be replaced with extension programs, a new field 'changes_pkt_data' is added to struct bpf_prog_aux. Verifier only allows replacing functions that do not change packet data with functions that do not change packet data. In case if there is a need to a have a global function that does not change packet data, but allow replacing it with function that does, the recommendation is to add a noop call to a helper, e.g.: - for skb do 'bpf_skb_change_proto(skb, 0, 0)'; - for xdp do 'bpf_xdp_adjust_meta(xdp, 0)'. Functions also can do tail calls. Effects of the tail call cannot be analyzed before-hand, thus verifier assumes that tail calls always change packet data. Changes v1 [1] -> v2: - added handling of extension programs and tail calls (thanks, Alexei, for all the input). [0] https://lore.kernel.org/bpf/0498CA22-5779-4767-9C0C-A9515CEA711F@gmail.com/ [1] https://lore.kernel.org/bpf/20241206040307.568065-1-eddyz87@gmail.com/ ==================== Link: https://patch.msgid.link/20241210041100.1898468-1-eddyz87@gmail.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2 parents 978c448 + d9706b5 commit cf8b876

File tree

11 files changed

+280
-47
lines changed

11 files changed

+280
-47
lines changed

include/linux/bpf.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,7 @@ struct bpf_prog_aux {
15271527
bool is_extended; /* true if extended by freplace program */
15281528
bool jits_use_priv_stack;
15291529
bool priv_stack_requested;
1530+
bool changes_pkt_data;
15301531
u64 prog_array_member_cnt; /* counts how many times as member of prog_array */
15311532
struct mutex ext_mutex; /* mutex for is_extended and prog_array_member_cnt */
15321533
struct bpf_arena *arena;

include/linux/bpf_verifier.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,7 @@ struct bpf_subprog_info {
659659
bool args_cached: 1;
660660
/* true if bpf_fastcall stack region is used by functions that can't be inlined */
661661
bool keep_fastcall_stack: 1;
662+
bool changes_pkt_data: 1;
662663

663664
enum priv_stack_mode priv_stack_mode;
664665
u8 arg_cnt;

include/linux/filter.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1122,7 +1122,7 @@ bool bpf_jit_supports_insn(struct bpf_insn *insn, bool in_arena);
11221122
bool bpf_jit_supports_private_stack(void);
11231123
u64 bpf_arch_uaddress_limit(void);
11241124
void arch_bpf_stack_walk(bool (*consume_fn)(void *cookie, u64 ip, u64 sp, u64 bp), void *cookie);
1125-
bool bpf_helper_changes_pkt_data(void *func);
1125+
bool bpf_helper_changes_pkt_data(enum bpf_func_id func_id);
11261126

11271127
static inline bool bpf_dump_raw_ok(const struct cred *cred)
11281128
{

kernel/bpf/core.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2936,7 +2936,7 @@ void __weak bpf_jit_compile(struct bpf_prog *prog)
29362936
{
29372937
}
29382938

2939-
bool __weak bpf_helper_changes_pkt_data(void *func)
2939+
bool __weak bpf_helper_changes_pkt_data(enum bpf_func_id func_id)
29402940
{
29412941
return false;
29422942
}

kernel/bpf/verifier.c

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2597,16 +2597,36 @@ static int cmp_subprogs(const void *a, const void *b)
25972597
((struct bpf_subprog_info *)b)->start;
25982598
}
25992599

2600+
/* Find subprogram that contains instruction at 'off' */
2601+
static struct bpf_subprog_info *find_containing_subprog(struct bpf_verifier_env *env, int off)
2602+
{
2603+
struct bpf_subprog_info *vals = env->subprog_info;
2604+
int l, r, m;
2605+
2606+
if (off >= env->prog->len || off < 0 || env->subprog_cnt == 0)
2607+
return NULL;
2608+
2609+
l = 0;
2610+
r = env->subprog_cnt - 1;
2611+
while (l < r) {
2612+
m = l + (r - l + 1) / 2;
2613+
if (vals[m].start <= off)
2614+
l = m;
2615+
else
2616+
r = m - 1;
2617+
}
2618+
return &vals[l];
2619+
}
2620+
2621+
/* Find subprogram that starts exactly at 'off' */
26002622
static int find_subprog(struct bpf_verifier_env *env, int off)
26012623
{
26022624
struct bpf_subprog_info *p;
26032625

2604-
p = bsearch(&off, env->subprog_info, env->subprog_cnt,
2605-
sizeof(env->subprog_info[0]), cmp_subprogs);
2606-
if (!p)
2626+
p = find_containing_subprog(env, off);
2627+
if (!p || p->start != off)
26072628
return -ENOENT;
26082629
return p - env->subprog_info;
2609-
26102630
}
26112631

26122632
static int add_subprog(struct bpf_verifier_env *env, int off)
@@ -10022,6 +10042,8 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
1002210042

1002310043
verbose(env, "Func#%d ('%s') is global and assumed valid.\n",
1002410044
subprog, sub_name);
10045+
if (env->subprog_info[subprog].changes_pkt_data)
10046+
clear_all_pkt_pointers(env);
1002510047
/* mark global subprog for verifying after main prog */
1002610048
subprog_aux(env, subprog)->called = true;
1002710049
clear_caller_saved_regs(env, caller->regs);
@@ -10708,7 +10730,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
1070810730
}
1070910731

1071010732
/* With LD_ABS/IND some JITs save/restore skb from r1. */
10711-
changes_data = bpf_helper_changes_pkt_data(fn->func);
10733+
changes_data = bpf_helper_changes_pkt_data(func_id);
1071210734
if (changes_data && fn->arg1_type != ARG_PTR_TO_CTX) {
1071310735
verbose(env, "kernel subsystem misconfigured func %s#%d: r1 != ctx\n",
1071410736
func_id_name(func_id), func_id);
@@ -16226,6 +16248,29 @@ static int check_return_code(struct bpf_verifier_env *env, int regno, const char
1622616248
return 0;
1622716249
}
1622816250

16251+
static void mark_subprog_changes_pkt_data(struct bpf_verifier_env *env, int off)
16252+
{
16253+
struct bpf_subprog_info *subprog;
16254+
16255+
subprog = find_containing_subprog(env, off);
16256+
subprog->changes_pkt_data = true;
16257+
}
16258+
16259+
/* 't' is an index of a call-site.
16260+
* 'w' is a callee entry point.
16261+
* Eventually this function would be called when env->cfg.insn_state[w] == EXPLORED.
16262+
* Rely on DFS traversal order and absence of recursive calls to guarantee that
16263+
* callee's change_pkt_data marks would be correct at that moment.
16264+
*/
16265+
static void merge_callee_effects(struct bpf_verifier_env *env, int t, int w)
16266+
{
16267+
struct bpf_subprog_info *caller, *callee;
16268+
16269+
caller = find_containing_subprog(env, t);
16270+
callee = find_containing_subprog(env, w);
16271+
caller->changes_pkt_data |= callee->changes_pkt_data;
16272+
}
16273+
1622916274
/* non-recursive DFS pseudo code
1623016275
* 1 procedure DFS-iterative(G,v):
1623116276
* 2 label v as discovered
@@ -16359,6 +16404,7 @@ static int visit_func_call_insn(int t, struct bpf_insn *insns,
1635916404
bool visit_callee)
1636016405
{
1636116406
int ret, insn_sz;
16407+
int w;
1636216408

1636316409
insn_sz = bpf_is_ldimm64(&insns[t]) ? 2 : 1;
1636416410
ret = push_insn(t, t + insn_sz, FALLTHROUGH, env);
@@ -16370,8 +16416,10 @@ static int visit_func_call_insn(int t, struct bpf_insn *insns,
1637016416
mark_jmp_point(env, t + insn_sz);
1637116417

1637216418
if (visit_callee) {
16419+
w = t + insns[t].imm + 1;
1637316420
mark_prune_point(env, t);
16374-
ret = push_insn(t, t + insns[t].imm + 1, BRANCH, env);
16421+
merge_callee_effects(env, t, w);
16422+
ret = push_insn(t, w, BRANCH, env);
1637516423
}
1637616424
return ret;
1637716425
}
@@ -16688,6 +16736,8 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
1668816736
mark_prune_point(env, t);
1668916737
mark_jmp_point(env, t);
1669016738
}
16739+
if (bpf_helper_call(insn) && bpf_helper_changes_pkt_data(insn->imm))
16740+
mark_subprog_changes_pkt_data(env, t);
1669116741
if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) {
1669216742
struct bpf_kfunc_call_arg_meta meta;
1669316743

@@ -16822,6 +16872,7 @@ static int check_cfg(struct bpf_verifier_env *env)
1682216872
}
1682316873
}
1682416874
ret = 0; /* cfg looks good */
16875+
env->prog->aux->changes_pkt_data = env->subprog_info[0].changes_pkt_data;
1682516876

1682616877
err_free:
1682716878
kvfree(insn_state);
@@ -20311,6 +20362,7 @@ static int jit_subprogs(struct bpf_verifier_env *env)
2031120362
func[i]->aux->num_exentries = num_exentries;
2031220363
func[i]->aux->tail_call_reachable = env->subprog_info[i].tail_call_reachable;
2031320364
func[i]->aux->exception_cb = env->subprog_info[i].is_exception_cb;
20365+
func[i]->aux->changes_pkt_data = env->subprog_info[i].changes_pkt_data;
2031420366
if (!i)
2031520367
func[i]->aux->exception_boundary = env->seen_exception;
2031620368
func[i] = bpf_int_jit_compile(func[i]);
@@ -22175,6 +22227,12 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
2217522227
"Extension programs should be JITed\n");
2217622228
return -EINVAL;
2217722229
}
22230+
if (prog->aux->changes_pkt_data &&
22231+
!aux->func[subprog]->aux->changes_pkt_data) {
22232+
bpf_log(log,
22233+
"Extension program changes packet data, while original does not\n");
22234+
return -EINVAL;
22235+
}
2217822236
}
2217922237
if (!tgt_prog->jited) {
2218022238
bpf_log(log, "Can attach to only JITed progs\n");
@@ -22640,10 +22698,6 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
2264022698
if (ret < 0)
2264122699
goto skip_full_check;
2264222700

22643-
ret = check_attach_btf_id(env);
22644-
if (ret)
22645-
goto skip_full_check;
22646-
2264722701
ret = resolve_pseudo_ldimm64(env);
2264822702
if (ret < 0)
2264922703
goto skip_full_check;
@@ -22658,6 +22712,10 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
2265822712
if (ret < 0)
2265922713
goto skip_full_check;
2266022714

22715+
ret = check_attach_btf_id(env);
22716+
if (ret)
22717+
goto skip_full_check;
22718+
2266122719
ret = mark_fastcall_patterns(env);
2266222720
if (ret < 0)
2266322721
goto skip_full_check;

net/core/filter.c

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7899,42 +7899,37 @@ static const struct bpf_func_proto bpf_tcp_raw_check_syncookie_ipv6_proto = {
78997899

79007900
#endif /* CONFIG_INET */
79017901

7902-
bool bpf_helper_changes_pkt_data(void *func)
7903-
{
7904-
if (func == bpf_skb_vlan_push ||
7905-
func == bpf_skb_vlan_pop ||
7906-
func == bpf_skb_store_bytes ||
7907-
func == bpf_skb_change_proto ||
7908-
func == bpf_skb_change_head ||
7909-
func == sk_skb_change_head ||
7910-
func == bpf_skb_change_tail ||
7911-
func == sk_skb_change_tail ||
7912-
func == bpf_skb_adjust_room ||
7913-
func == sk_skb_adjust_room ||
7914-
func == bpf_skb_pull_data ||
7915-
func == sk_skb_pull_data ||
7916-
func == bpf_clone_redirect ||
7917-
func == bpf_l3_csum_replace ||
7918-
func == bpf_l4_csum_replace ||
7919-
func == bpf_xdp_adjust_head ||
7920-
func == bpf_xdp_adjust_meta ||
7921-
func == bpf_msg_pull_data ||
7922-
func == bpf_msg_push_data ||
7923-
func == bpf_msg_pop_data ||
7924-
func == bpf_xdp_adjust_tail ||
7925-
#if IS_ENABLED(CONFIG_IPV6_SEG6_BPF)
7926-
func == bpf_lwt_seg6_store_bytes ||
7927-
func == bpf_lwt_seg6_adjust_srh ||
7928-
func == bpf_lwt_seg6_action ||
7929-
#endif
7930-
#ifdef CONFIG_INET
7931-
func == bpf_sock_ops_store_hdr_opt ||
7932-
#endif
7933-
func == bpf_lwt_in_push_encap ||
7934-
func == bpf_lwt_xmit_push_encap)
7902+
bool bpf_helper_changes_pkt_data(enum bpf_func_id func_id)
7903+
{
7904+
switch (func_id) {
7905+
case BPF_FUNC_clone_redirect:
7906+
case BPF_FUNC_l3_csum_replace:
7907+
case BPF_FUNC_l4_csum_replace:
7908+
case BPF_FUNC_lwt_push_encap:
7909+
case BPF_FUNC_lwt_seg6_action:
7910+
case BPF_FUNC_lwt_seg6_adjust_srh:
7911+
case BPF_FUNC_lwt_seg6_store_bytes:
7912+
case BPF_FUNC_msg_pop_data:
7913+
case BPF_FUNC_msg_pull_data:
7914+
case BPF_FUNC_msg_push_data:
7915+
case BPF_FUNC_skb_adjust_room:
7916+
case BPF_FUNC_skb_change_head:
7917+
case BPF_FUNC_skb_change_proto:
7918+
case BPF_FUNC_skb_change_tail:
7919+
case BPF_FUNC_skb_pull_data:
7920+
case BPF_FUNC_skb_store_bytes:
7921+
case BPF_FUNC_skb_vlan_pop:
7922+
case BPF_FUNC_skb_vlan_push:
7923+
case BPF_FUNC_store_hdr_opt:
7924+
case BPF_FUNC_xdp_adjust_head:
7925+
case BPF_FUNC_xdp_adjust_meta:
7926+
case BPF_FUNC_xdp_adjust_tail:
7927+
/* tail-called program could call any of the above */
7928+
case BPF_FUNC_tail_call:
79357929
return true;
7936-
7937-
return false;
7930+
default:
7931+
return false;
7932+
}
79387933
}
79397934

79407935
const struct bpf_func_proto bpf_event_output_data_proto __weak;
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
#include "bpf/libbpf.h"
3+
#include "changes_pkt_data_freplace.skel.h"
4+
#include "changes_pkt_data.skel.h"
5+
#include <test_progs.h>
6+
7+
static void print_verifier_log(const char *log)
8+
{
9+
if (env.verbosity >= VERBOSE_VERY)
10+
fprintf(stdout, "VERIFIER LOG:\n=============\n%s=============\n", log);
11+
}
12+
13+
static void test_aux(const char *main_prog_name, const char *freplace_prog_name, bool expect_load)
14+
{
15+
struct changes_pkt_data_freplace *freplace = NULL;
16+
struct bpf_program *freplace_prog = NULL;
17+
LIBBPF_OPTS(bpf_object_open_opts, opts);
18+
struct changes_pkt_data *main = NULL;
19+
char log[16*1024];
20+
int err;
21+
22+
opts.kernel_log_buf = log;
23+
opts.kernel_log_size = sizeof(log);
24+
if (env.verbosity >= VERBOSE_SUPER)
25+
opts.kernel_log_level = 1 | 2 | 4;
26+
main = changes_pkt_data__open_opts(&opts);
27+
if (!ASSERT_OK_PTR(main, "changes_pkt_data__open"))
28+
goto out;
29+
err = changes_pkt_data__load(main);
30+
print_verifier_log(log);
31+
if (!ASSERT_OK(err, "changes_pkt_data__load"))
32+
goto out;
33+
freplace = changes_pkt_data_freplace__open_opts(&opts);
34+
if (!ASSERT_OK_PTR(freplace, "changes_pkt_data_freplace__open"))
35+
goto out;
36+
freplace_prog = bpf_object__find_program_by_name(freplace->obj, freplace_prog_name);
37+
if (!ASSERT_OK_PTR(freplace_prog, "freplace_prog"))
38+
goto out;
39+
bpf_program__set_autoload(freplace_prog, true);
40+
bpf_program__set_autoattach(freplace_prog, true);
41+
bpf_program__set_attach_target(freplace_prog,
42+
bpf_program__fd(main->progs.dummy),
43+
main_prog_name);
44+
err = changes_pkt_data_freplace__load(freplace);
45+
print_verifier_log(log);
46+
if (expect_load) {
47+
ASSERT_OK(err, "changes_pkt_data_freplace__load");
48+
} else {
49+
ASSERT_ERR(err, "changes_pkt_data_freplace__load");
50+
ASSERT_HAS_SUBSTR(log, "Extension program changes packet data", "error log");
51+
}
52+
53+
out:
54+
changes_pkt_data_freplace__destroy(freplace);
55+
changes_pkt_data__destroy(main);
56+
}
57+
58+
/* There are two global subprograms in both changes_pkt_data.skel.h:
59+
* - one changes packet data;
60+
* - another does not.
61+
* It is ok to freplace subprograms that change packet data with those
62+
* that either do or do not. It is only ok to freplace subprograms
63+
* that do not change packet data with those that do not as well.
64+
* The below tests check outcomes for each combination of such freplace.
65+
*/
66+
void test_changes_pkt_data_freplace(void)
67+
{
68+
if (test__start_subtest("changes_with_changes"))
69+
test_aux("changes_pkt_data", "changes_pkt_data", true);
70+
if (test__start_subtest("changes_with_doesnt_change"))
71+
test_aux("changes_pkt_data", "does_not_change_pkt_data", true);
72+
if (test__start_subtest("doesnt_change_with_changes"))
73+
test_aux("does_not_change_pkt_data", "changes_pkt_data", false);
74+
if (test__start_subtest("doesnt_change_with_doesnt_change"))
75+
test_aux("does_not_change_pkt_data", "does_not_change_pkt_data", true);
76+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
#include <linux/bpf.h>
4+
#include <bpf/bpf_helpers.h>
5+
6+
__noinline
7+
long changes_pkt_data(struct __sk_buff *sk, __u32 len)
8+
{
9+
return bpf_skb_pull_data(sk, len);
10+
}
11+
12+
__noinline __weak
13+
long does_not_change_pkt_data(struct __sk_buff *sk, __u32 len)
14+
{
15+
return 0;
16+
}
17+
18+
SEC("tc")
19+
int dummy(struct __sk_buff *sk)
20+
{
21+
changes_pkt_data(sk, 0);
22+
does_not_change_pkt_data(sk, 0);
23+
return 0;
24+
}
25+
26+
char _license[] SEC("license") = "GPL";
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
#include <linux/bpf.h>
4+
#include <bpf/bpf_helpers.h>
5+
6+
SEC("?freplace")
7+
long changes_pkt_data(struct __sk_buff *sk, __u32 len)
8+
{
9+
return bpf_skb_pull_data(sk, len);
10+
}
11+
12+
SEC("?freplace")
13+
long does_not_change_pkt_data(struct __sk_buff *sk, __u32 len)
14+
{
15+
return 0;
16+
}
17+
18+
char _license[] SEC("license") = "GPL";

0 commit comments

Comments
 (0)