BPF fixes:

- Fix a bug in the BPF verifier to track changes to packet data
   property for global functions (Eduard Zingerman)
 
 - Fix a theoretical BPF prog_array use-after-free in RCU handling
   of __uprobe_perf_func (Jann Horn)
 
 - Fix BPF tracing to have an explicit list of tracepoints and
   their arguments which need to be annotated as PTR_MAYBE_NULL
   (Kumar Kartikeya Dwivedi)
 
 - Fix a logic bug in the bpf_remove_insns code where a potential
   error would have been wrongly propagated (Anton Protopopov)
 
 - Avoid deadlock scenarios caused by nested kprobe and fentry
   BPF programs (Priya Bala Govindasamy)
 
 - Fix a bug in BPF verifier which was missing a size check for
   BTF-based context access (Kumar Kartikeya Dwivedi)
 
 - Fix a crash found by syzbot through an invalid BPF prog_array
   access in perf_event_detach_bpf_prog (Jiri Olsa)
 
 - Fix several BPF sockmap bugs including a race causing a
   refcount imbalance upon element replace (Michal Luczaj)
 
 - Fix a use-after-free from mismatching BPF program/attachment
   RCU flavors (Jann Horn)
 
 Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
 -----BEGIN PGP SIGNATURE-----
 
 iIsEABYKADMWIQTFp0I1jqZrAX+hPRXbK58LschIgwUCZ13rdhUcZGFuaWVsQGlv
 Z2VhcmJveC5uZXQACgkQ2yufC7HISINfqAD7B2vX6EgTFrgy7QDepQnZsmu2qjdW
 fFUzPatFXXp2S3MA/16vOEoHJ4rRhBkcUK/vw3gyY5j5bYZNUTTaam5l4BcM
 =gkfb
 -----END PGP SIGNATURE-----

Merge tag 'bpf-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf

Pull bpf fixes from Daniel Borkmann:

 - Fix a bug in the BPF verifier to track changes to packet data
   property for global functions (Eduard Zingerman)

 - Fix a theoretical BPF prog_array use-after-free in RCU handling of
   __uprobe_perf_func (Jann Horn)

 - Fix BPF tracing to have an explicit list of tracepoints and their
   arguments which need to be annotated as PTR_MAYBE_NULL (Kumar
   Kartikeya Dwivedi)

 - Fix a logic bug in the bpf_remove_insns code where a potential error
   would have been wrongly propagated (Anton Protopopov)

 - Avoid deadlock scenarios caused by nested kprobe and fentry BPF
   programs (Priya Bala Govindasamy)

 - Fix a bug in BPF verifier which was missing a size check for
   BTF-based context access (Kumar Kartikeya Dwivedi)

 - Fix a crash found by syzbot through an invalid BPF prog_array access
   in perf_event_detach_bpf_prog (Jiri Olsa)

 - Fix several BPF sockmap bugs including a race causing a refcount
   imbalance upon element replace (Michal Luczaj)

 - Fix a use-after-free from mismatching BPF program/attachment RCU
   flavors (Jann Horn)

* tag 'bpf-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf: (23 commits)
  bpf: Avoid deadlock caused by nested kprobe and fentry bpf programs
  selftests/bpf: Add tests for raw_tp NULL args
  bpf: Augment raw_tp arguments with PTR_MAYBE_NULL
  bpf: Revert "bpf: Mark raw_tp arguments with PTR_MAYBE_NULL"
  selftests/bpf: Add test for narrow ctx load for pointer args
  bpf: Check size for BTF-based ctx access of pointer members
  selftests/bpf: extend changes_pkt_data with cases w/o subprograms
  bpf: fix null dereference when computing changes_pkt_data of prog w/o subprogs
  bpf: Fix theoretical prog_array UAF in __uprobe_perf_func()
  bpf: fix potential error return
  selftests/bpf: validate that tail call invalidates packet pointers
  bpf: consider that tail calls invalidate packet pointers
  selftests/bpf: freplace tests for tracking of changes_packet_data
  bpf: check changes_pkt_data property for extension programs
  selftests/bpf: test for changing packet data from global functions
  bpf: track changes_pkt_data property for global functions
  bpf: refactor bpf_helper_changes_pkt_data to use helper number
  bpf: add find_containing_subprog() utility function
  bpf,perf: Fix invalid prog_array access in perf_event_detach_bpf_prog
  bpf: Fix UAF via mismatching bpf_prog/attachment RCU flavors
  ...
This commit is contained in:
Linus Torvalds 2024-12-14 12:58:14 -08:00
commit 35f301dd45
23 changed files with 595 additions and 163 deletions

View File

@ -1527,6 +1527,7 @@ struct bpf_prog_aux {
bool is_extended; /* true if extended by freplace program */ bool is_extended; /* true if extended by freplace program */
bool jits_use_priv_stack; bool jits_use_priv_stack;
bool priv_stack_requested; bool priv_stack_requested;
bool changes_pkt_data;
u64 prog_array_member_cnt; /* counts how many times as member of prog_array */ u64 prog_array_member_cnt; /* counts how many times as member of prog_array */
struct mutex ext_mutex; /* mutex for is_extended and prog_array_member_cnt */ struct mutex ext_mutex; /* mutex for is_extended and prog_array_member_cnt */
struct bpf_arena *arena; struct bpf_arena *arena;
@ -2193,26 +2194,25 @@ bpf_prog_run_array(const struct bpf_prog_array *array,
* rcu-protected dynamically sized maps. * rcu-protected dynamically sized maps.
*/ */
static __always_inline u32 static __always_inline u32
bpf_prog_run_array_uprobe(const struct bpf_prog_array __rcu *array_rcu, bpf_prog_run_array_uprobe(const struct bpf_prog_array *array,
const void *ctx, bpf_prog_run_fn run_prog) const void *ctx, bpf_prog_run_fn run_prog)
{ {
const struct bpf_prog_array_item *item; const struct bpf_prog_array_item *item;
const struct bpf_prog *prog; const struct bpf_prog *prog;
const struct bpf_prog_array *array;
struct bpf_run_ctx *old_run_ctx; struct bpf_run_ctx *old_run_ctx;
struct bpf_trace_run_ctx run_ctx; struct bpf_trace_run_ctx run_ctx;
u32 ret = 1; u32 ret = 1;
might_fault(); might_fault();
RCU_LOCKDEP_WARN(!rcu_read_lock_trace_held(), "no rcu lock held");
if (unlikely(!array))
return ret;
rcu_read_lock_trace();
migrate_disable(); migrate_disable();
run_ctx.is_uprobe = true; run_ctx.is_uprobe = true;
array = rcu_dereference_check(array_rcu, rcu_read_lock_trace_held());
if (unlikely(!array))
goto out;
old_run_ctx = bpf_set_run_ctx(&run_ctx.run_ctx); old_run_ctx = bpf_set_run_ctx(&run_ctx.run_ctx);
item = &array->items[0]; item = &array->items[0];
while ((prog = READ_ONCE(item->prog))) { while ((prog = READ_ONCE(item->prog))) {
@ -2227,9 +2227,7 @@ bpf_prog_run_array_uprobe(const struct bpf_prog_array __rcu *array_rcu,
rcu_read_unlock(); rcu_read_unlock();
} }
bpf_reset_run_ctx(old_run_ctx); bpf_reset_run_ctx(old_run_ctx);
out:
migrate_enable(); migrate_enable();
rcu_read_unlock_trace();
return ret; return ret;
} }
@ -3516,10 +3514,4 @@ static inline bool bpf_is_subprog(const struct bpf_prog *prog)
return prog->aux->func_idx != 0; return prog->aux->func_idx != 0;
} }
static inline bool bpf_prog_is_raw_tp(const struct bpf_prog *prog)
{
return prog->type == BPF_PROG_TYPE_TRACING &&
prog->expected_attach_type == BPF_TRACE_RAW_TP;
}
#endif /* _LINUX_BPF_H */ #endif /* _LINUX_BPF_H */

View File

@ -659,6 +659,7 @@ struct bpf_subprog_info {
bool args_cached: 1; bool args_cached: 1;
/* true if bpf_fastcall stack region is used by functions that can't be inlined */ /* true if bpf_fastcall stack region is used by functions that can't be inlined */
bool keep_fastcall_stack: 1; bool keep_fastcall_stack: 1;
bool changes_pkt_data: 1;
enum priv_stack_mode priv_stack_mode; enum priv_stack_mode priv_stack_mode;
u8 arg_cnt; u8 arg_cnt;

View File

@ -1122,7 +1122,7 @@ bool bpf_jit_supports_insn(struct bpf_insn *insn, bool in_arena);
bool bpf_jit_supports_private_stack(void); bool bpf_jit_supports_private_stack(void);
u64 bpf_arch_uaddress_limit(void); u64 bpf_arch_uaddress_limit(void);
void arch_bpf_stack_walk(bool (*consume_fn)(void *cookie, u64 ip, u64 sp, u64 bp), void *cookie); void arch_bpf_stack_walk(bool (*consume_fn)(void *cookie, u64 ip, u64 sp, u64 bp), void *cookie);
bool bpf_helper_changes_pkt_data(void *func); bool bpf_helper_changes_pkt_data(enum bpf_func_id func_id);
static inline bool bpf_dump_raw_ok(const struct cred *cred) static inline bool bpf_dump_raw_ok(const struct cred *cred)
{ {

View File

@ -53,3 +53,9 @@ obj-$(CONFIG_BPF_SYSCALL) += relo_core.o
obj-$(CONFIG_BPF_SYSCALL) += btf_iter.o obj-$(CONFIG_BPF_SYSCALL) += btf_iter.o
obj-$(CONFIG_BPF_SYSCALL) += btf_relocate.o obj-$(CONFIG_BPF_SYSCALL) += btf_relocate.o
obj-$(CONFIG_BPF_SYSCALL) += kmem_cache_iter.o obj-$(CONFIG_BPF_SYSCALL) += kmem_cache_iter.o
CFLAGS_REMOVE_percpu_freelist.o = $(CC_FLAGS_FTRACE)
CFLAGS_REMOVE_bpf_lru_list.o = $(CC_FLAGS_FTRACE)
CFLAGS_REMOVE_queue_stack_maps.o = $(CC_FLAGS_FTRACE)
CFLAGS_REMOVE_lpm_trie.o = $(CC_FLAGS_FTRACE)
CFLAGS_REMOVE_ringbuf.o = $(CC_FLAGS_FTRACE)

View File

@ -6439,6 +6439,101 @@ int btf_ctx_arg_offset(const struct btf *btf, const struct btf_type *func_proto,
return off; return off;
} }
struct bpf_raw_tp_null_args {
const char *func;
u64 mask;
};
static const struct bpf_raw_tp_null_args raw_tp_null_args[] = {
/* sched */
{ "sched_pi_setprio", 0x10 },
/* ... from sched_numa_pair_template event class */
{ "sched_stick_numa", 0x100 },
{ "sched_swap_numa", 0x100 },
/* afs */
{ "afs_make_fs_call", 0x10 },
{ "afs_make_fs_calli", 0x10 },
{ "afs_make_fs_call1", 0x10 },
{ "afs_make_fs_call2", 0x10 },
{ "afs_protocol_error", 0x1 },
{ "afs_flock_ev", 0x10 },
/* cachefiles */
{ "cachefiles_lookup", 0x1 | 0x200 },
{ "cachefiles_unlink", 0x1 },
{ "cachefiles_rename", 0x1 },
{ "cachefiles_prep_read", 0x1 },
{ "cachefiles_mark_active", 0x1 },
{ "cachefiles_mark_failed", 0x1 },
{ "cachefiles_mark_inactive", 0x1 },
{ "cachefiles_vfs_error", 0x1 },
{ "cachefiles_io_error", 0x1 },
{ "cachefiles_ondemand_open", 0x1 },
{ "cachefiles_ondemand_copen", 0x1 },
{ "cachefiles_ondemand_close", 0x1 },
{ "cachefiles_ondemand_read", 0x1 },
{ "cachefiles_ondemand_cread", 0x1 },
{ "cachefiles_ondemand_fd_write", 0x1 },
{ "cachefiles_ondemand_fd_release", 0x1 },
/* ext4, from ext4__mballoc event class */
{ "ext4_mballoc_discard", 0x10 },
{ "ext4_mballoc_free", 0x10 },
/* fib */
{ "fib_table_lookup", 0x100 },
/* filelock */
/* ... from filelock_lock event class */
{ "posix_lock_inode", 0x10 },
{ "fcntl_setlk", 0x10 },
{ "locks_remove_posix", 0x10 },
{ "flock_lock_inode", 0x10 },
/* ... from filelock_lease event class */
{ "break_lease_noblock", 0x10 },
{ "break_lease_block", 0x10 },
{ "break_lease_unblock", 0x10 },
{ "generic_delete_lease", 0x10 },
{ "time_out_leases", 0x10 },
/* host1x */
{ "host1x_cdma_push_gather", 0x10000 },
/* huge_memory */
{ "mm_khugepaged_scan_pmd", 0x10 },
{ "mm_collapse_huge_page_isolate", 0x1 },
{ "mm_khugepaged_scan_file", 0x10 },
{ "mm_khugepaged_collapse_file", 0x10 },
/* kmem */
{ "mm_page_alloc", 0x1 },
{ "mm_page_pcpu_drain", 0x1 },
/* .. from mm_page event class */
{ "mm_page_alloc_zone_locked", 0x1 },
/* netfs */
{ "netfs_failure", 0x10 },
/* power */
{ "device_pm_callback_start", 0x10 },
/* qdisc */
{ "qdisc_dequeue", 0x1000 },
/* rxrpc */
{ "rxrpc_recvdata", 0x1 },
{ "rxrpc_resend", 0x10 },
/* sunrpc */
{ "xs_stream_read_data", 0x1 },
/* ... from xprt_cong_event event class */
{ "xprt_reserve_cong", 0x10 },
{ "xprt_release_cong", 0x10 },
{ "xprt_get_cong", 0x10 },
{ "xprt_put_cong", 0x10 },
/* tcp */
{ "tcp_send_reset", 0x11 },
/* tegra_apb_dma */
{ "tegra_dma_tx_status", 0x100 },
/* timer_migration */
{ "tmigr_update_events", 0x1 },
/* writeback, from writeback_folio_template event class */
{ "writeback_dirty_folio", 0x10 },
{ "folio_wait_writeback", 0x10 },
/* rdma */
{ "mr_integ_alloc", 0x2000 },
/* bpf_testmod */
{ "bpf_testmod_test_read", 0x0 },
};
bool btf_ctx_access(int off, int size, enum bpf_access_type type, bool btf_ctx_access(int off, int size, enum bpf_access_type type,
const struct bpf_prog *prog, const struct bpf_prog *prog,
struct bpf_insn_access_aux *info) struct bpf_insn_access_aux *info)
@ -6449,6 +6544,7 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
const char *tname = prog->aux->attach_func_name; const char *tname = prog->aux->attach_func_name;
struct bpf_verifier_log *log = info->log; struct bpf_verifier_log *log = info->log;
const struct btf_param *args; const struct btf_param *args;
bool ptr_err_raw_tp = false;
const char *tag_value; const char *tag_value;
u32 nr_args, arg; u32 nr_args, arg;
int i, ret; int i, ret;
@ -6543,6 +6639,12 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
return false; return false;
} }
if (size != sizeof(u64)) {
bpf_log(log, "func '%s' size %d must be 8\n",
tname, size);
return false;
}
/* check for PTR_TO_RDONLY_BUF_OR_NULL or PTR_TO_RDWR_BUF_OR_NULL */ /* check for PTR_TO_RDONLY_BUF_OR_NULL or PTR_TO_RDWR_BUF_OR_NULL */
for (i = 0; i < prog->aux->ctx_arg_info_size; i++) { for (i = 0; i < prog->aux->ctx_arg_info_size; i++) {
const struct bpf_ctx_arg_aux *ctx_arg_info = &prog->aux->ctx_arg_info[i]; const struct bpf_ctx_arg_aux *ctx_arg_info = &prog->aux->ctx_arg_info[i];
@ -6588,12 +6690,42 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
if (prog_args_trusted(prog)) if (prog_args_trusted(prog))
info->reg_type |= PTR_TRUSTED; info->reg_type |= PTR_TRUSTED;
/* Raw tracepoint arguments always get marked as maybe NULL */ if (btf_param_match_suffix(btf, &args[arg], "__nullable"))
if (bpf_prog_is_raw_tp(prog))
info->reg_type |= PTR_MAYBE_NULL;
else if (btf_param_match_suffix(btf, &args[arg], "__nullable"))
info->reg_type |= PTR_MAYBE_NULL; info->reg_type |= PTR_MAYBE_NULL;
if (prog->expected_attach_type == BPF_TRACE_RAW_TP) {
struct btf *btf = prog->aux->attach_btf;
const struct btf_type *t;
const char *tname;
/* BTF lookups cannot fail, return false on error */
t = btf_type_by_id(btf, prog->aux->attach_btf_id);
if (!t)
return false;
tname = btf_name_by_offset(btf, t->name_off);
if (!tname)
return false;
/* Checked by bpf_check_attach_target */
tname += sizeof("btf_trace_") - 1;
for (i = 0; i < ARRAY_SIZE(raw_tp_null_args); i++) {
/* Is this a func with potential NULL args? */
if (strcmp(tname, raw_tp_null_args[i].func))
continue;
if (raw_tp_null_args[i].mask & (0x1 << (arg * 4)))
info->reg_type |= PTR_MAYBE_NULL;
/* Is the current arg IS_ERR? */
if (raw_tp_null_args[i].mask & (0x2 << (arg * 4)))
ptr_err_raw_tp = true;
break;
}
/* If we don't know NULL-ness specification and the tracepoint
* is coming from a loadable module, be conservative and mark
* argument as PTR_MAYBE_NULL.
*/
if (i == ARRAY_SIZE(raw_tp_null_args) && btf_is_module(btf))
info->reg_type |= PTR_MAYBE_NULL;
}
if (tgt_prog) { if (tgt_prog) {
enum bpf_prog_type tgt_type; enum bpf_prog_type tgt_type;
@ -6638,6 +6770,15 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
bpf_log(log, "func '%s' arg%d has btf_id %d type %s '%s'\n", bpf_log(log, "func '%s' arg%d has btf_id %d type %s '%s'\n",
tname, arg, info->btf_id, btf_type_str(t), tname, arg, info->btf_id, btf_type_str(t),
__btf_name_by_offset(btf, t->name_off)); __btf_name_by_offset(btf, t->name_off));
/* Perform all checks on the validity of type for this argument, but if
* we know it can be IS_ERR at runtime, scrub pointer type and mark as
* scalar.
*/
if (ptr_err_raw_tp) {
bpf_log(log, "marking pointer arg%d as scalar as it may encode error", arg);
info->reg_type = SCALAR_VALUE;
}
return true; return true;
} }
EXPORT_SYMBOL_GPL(btf_ctx_access); EXPORT_SYMBOL_GPL(btf_ctx_access);

View File

@ -539,6 +539,8 @@ struct bpf_prog *bpf_patch_insn_single(struct bpf_prog *prog, u32 off,
int bpf_remove_insns(struct bpf_prog *prog, u32 off, u32 cnt) int bpf_remove_insns(struct bpf_prog *prog, u32 off, u32 cnt)
{ {
int err;
/* Branch offsets can't overflow when program is shrinking, no need /* Branch offsets can't overflow when program is shrinking, no need
* to call bpf_adj_branches(..., true) here * to call bpf_adj_branches(..., true) here
*/ */
@ -546,7 +548,9 @@ int bpf_remove_insns(struct bpf_prog *prog, u32 off, u32 cnt)
sizeof(struct bpf_insn) * (prog->len - off - cnt)); sizeof(struct bpf_insn) * (prog->len - off - cnt));
prog->len -= cnt; prog->len -= cnt;
return WARN_ON_ONCE(bpf_adj_branches(prog, off, off + cnt, off, false)); err = bpf_adj_branches(prog, off, off + cnt, off, false);
WARN_ON_ONCE(err);
return err;
} }
static void bpf_prog_kallsyms_del_subprogs(struct bpf_prog *fp) static void bpf_prog_kallsyms_del_subprogs(struct bpf_prog *fp)
@ -2936,7 +2940,7 @@ void __weak bpf_jit_compile(struct bpf_prog *prog)
{ {
} }
bool __weak bpf_helper_changes_pkt_data(void *func) bool __weak bpf_helper_changes_pkt_data(enum bpf_func_id func_id)
{ {
return false; return false;
} }

View File

@ -420,25 +420,6 @@ static struct btf_record *reg_btf_record(const struct bpf_reg_state *reg)
return rec; return rec;
} }
static bool mask_raw_tp_reg_cond(const struct bpf_verifier_env *env, struct bpf_reg_state *reg) {
return reg->type == (PTR_TO_BTF_ID | PTR_TRUSTED | PTR_MAYBE_NULL) &&
bpf_prog_is_raw_tp(env->prog) && !reg->ref_obj_id;
}
static bool mask_raw_tp_reg(const struct bpf_verifier_env *env, struct bpf_reg_state *reg)
{
if (!mask_raw_tp_reg_cond(env, reg))
return false;
reg->type &= ~PTR_MAYBE_NULL;
return true;
}
static void unmask_raw_tp_reg(struct bpf_reg_state *reg, bool result)
{
if (result)
reg->type |= PTR_MAYBE_NULL;
}
static bool subprog_is_global(const struct bpf_verifier_env *env, int subprog) static bool subprog_is_global(const struct bpf_verifier_env *env, int subprog)
{ {
struct bpf_func_info_aux *aux = env->prog->aux->func_info_aux; struct bpf_func_info_aux *aux = env->prog->aux->func_info_aux;
@ -2597,16 +2578,36 @@ static int cmp_subprogs(const void *a, const void *b)
((struct bpf_subprog_info *)b)->start; ((struct bpf_subprog_info *)b)->start;
} }
/* Find subprogram that contains instruction at 'off' */
static struct bpf_subprog_info *find_containing_subprog(struct bpf_verifier_env *env, int off)
{
struct bpf_subprog_info *vals = env->subprog_info;
int l, r, m;
if (off >= env->prog->len || off < 0 || env->subprog_cnt == 0)
return NULL;
l = 0;
r = env->subprog_cnt - 1;
while (l < r) {
m = l + (r - l + 1) / 2;
if (vals[m].start <= off)
l = m;
else
r = m - 1;
}
return &vals[l];
}
/* Find subprogram that starts exactly at 'off' */
static int find_subprog(struct bpf_verifier_env *env, int off) static int find_subprog(struct bpf_verifier_env *env, int off)
{ {
struct bpf_subprog_info *p; struct bpf_subprog_info *p;
p = bsearch(&off, env->subprog_info, env->subprog_cnt, p = find_containing_subprog(env, off);
sizeof(env->subprog_info[0]), cmp_subprogs); if (!p || p->start != off)
if (!p)
return -ENOENT; return -ENOENT;
return p - env->subprog_info; return p - env->subprog_info;
} }
static int add_subprog(struct bpf_verifier_env *env, int off) static int add_subprog(struct bpf_verifier_env *env, int off)
@ -6781,7 +6782,6 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
const char *field_name = NULL; const char *field_name = NULL;
enum bpf_type_flag flag = 0; enum bpf_type_flag flag = 0;
u32 btf_id = 0; u32 btf_id = 0;
bool mask;
int ret; int ret;
if (!env->allow_ptr_leaks) { if (!env->allow_ptr_leaks) {
@ -6853,21 +6853,7 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
if (ret < 0) if (ret < 0)
return ret; return ret;
/* For raw_tp progs, we allow dereference of PTR_MAYBE_NULL
* trusted PTR_TO_BTF_ID, these are the ones that are possibly
* arguments to the raw_tp. Since internal checks in for trusted
* reg in check_ptr_to_btf_access would consider PTR_MAYBE_NULL
* modifier as problematic, mask it out temporarily for the
* check. Don't apply this to pointers with ref_obj_id > 0, as
* those won't be raw_tp args.
*
* We may end up applying this relaxation to other trusted
* PTR_TO_BTF_ID with maybe null flag, since we cannot
* distinguish PTR_MAYBE_NULL tagged for arguments vs normal
* tagging, but that should expand allowed behavior, and not
* cause regression for existing behavior.
*/
mask = mask_raw_tp_reg(env, reg);
if (ret != PTR_TO_BTF_ID) { if (ret != PTR_TO_BTF_ID) {
/* just mark; */ /* just mark; */
@ -6928,13 +6914,8 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
clear_trusted_flags(&flag); clear_trusted_flags(&flag);
} }
if (atype == BPF_READ && value_regno >= 0) { if (atype == BPF_READ && value_regno >= 0)
mark_btf_ld_reg(env, regs, value_regno, ret, reg->btf, btf_id, flag); mark_btf_ld_reg(env, regs, value_regno, ret, reg->btf, btf_id, flag);
/* We've assigned a new type to regno, so don't undo masking. */
if (regno == value_regno)
mask = false;
}
unmask_raw_tp_reg(reg, mask);
return 0; return 0;
} }
@ -7309,7 +7290,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
if (!err && t == BPF_READ && value_regno >= 0) if (!err && t == BPF_READ && value_regno >= 0)
mark_reg_unknown(env, regs, value_regno); mark_reg_unknown(env, regs, value_regno);
} else if (base_type(reg->type) == PTR_TO_BTF_ID && } else if (base_type(reg->type) == PTR_TO_BTF_ID &&
(mask_raw_tp_reg_cond(env, reg) || !type_may_be_null(reg->type))) { !type_may_be_null(reg->type)) {
err = check_ptr_to_btf_access(env, regs, regno, off, size, t, err = check_ptr_to_btf_access(env, regs, regno, off, size, t,
value_regno); value_regno);
} else if (reg->type == CONST_PTR_TO_MAP) { } else if (reg->type == CONST_PTR_TO_MAP) {
@ -9012,7 +8993,6 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
enum bpf_reg_type type = reg->type; enum bpf_reg_type type = reg->type;
u32 *arg_btf_id = NULL; u32 *arg_btf_id = NULL;
int err = 0; int err = 0;
bool mask;
if (arg_type == ARG_DONTCARE) if (arg_type == ARG_DONTCARE)
return 0; return 0;
@ -9053,11 +9033,11 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
base_type(arg_type) == ARG_PTR_TO_SPIN_LOCK) base_type(arg_type) == ARG_PTR_TO_SPIN_LOCK)
arg_btf_id = fn->arg_btf_id[arg]; arg_btf_id = fn->arg_btf_id[arg];
mask = mask_raw_tp_reg(env, reg);
err = check_reg_type(env, regno, arg_type, arg_btf_id, meta); err = check_reg_type(env, regno, arg_type, arg_btf_id, meta);
if (err)
return err;
err = err ?: check_func_arg_reg_off(env, reg, regno, arg_type); err = check_func_arg_reg_off(env, reg, regno, arg_type);
unmask_raw_tp_reg(reg, mask);
if (err) if (err)
return err; return err;
@ -9852,17 +9832,14 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
return ret; return ret;
} else if (base_type(arg->arg_type) == ARG_PTR_TO_BTF_ID) { } else if (base_type(arg->arg_type) == ARG_PTR_TO_BTF_ID) {
struct bpf_call_arg_meta meta; struct bpf_call_arg_meta meta;
bool mask;
int err; int err;
if (register_is_null(reg) && type_may_be_null(arg->arg_type)) if (register_is_null(reg) && type_may_be_null(arg->arg_type))
continue; continue;
memset(&meta, 0, sizeof(meta)); /* leave func_id as zero */ memset(&meta, 0, sizeof(meta)); /* leave func_id as zero */
mask = mask_raw_tp_reg(env, reg);
err = check_reg_type(env, regno, arg->arg_type, &arg->btf_id, &meta); err = check_reg_type(env, regno, arg->arg_type, &arg->btf_id, &meta);
err = err ?: check_func_arg_reg_off(env, reg, regno, arg->arg_type); err = err ?: check_func_arg_reg_off(env, reg, regno, arg->arg_type);
unmask_raw_tp_reg(reg, mask);
if (err) if (err)
return err; return err;
} else { } else {
@ -10022,6 +9999,8 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
verbose(env, "Func#%d ('%s') is global and assumed valid.\n", verbose(env, "Func#%d ('%s') is global and assumed valid.\n",
subprog, sub_name); subprog, sub_name);
if (env->subprog_info[subprog].changes_pkt_data)
clear_all_pkt_pointers(env);
/* mark global subprog for verifying after main prog */ /* mark global subprog for verifying after main prog */
subprog_aux(env, subprog)->called = true; subprog_aux(env, subprog)->called = true;
clear_caller_saved_regs(env, caller->regs); clear_caller_saved_regs(env, caller->regs);
@ -10708,7 +10687,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
} }
/* With LD_ABS/IND some JITs save/restore skb from r1. */ /* With LD_ABS/IND some JITs save/restore skb from r1. */
changes_data = bpf_helper_changes_pkt_data(fn->func); changes_data = bpf_helper_changes_pkt_data(func_id);
if (changes_data && fn->arg1_type != ARG_PTR_TO_CTX) { if (changes_data && fn->arg1_type != ARG_PTR_TO_CTX) {
verbose(env, "kernel subsystem misconfigured func %s#%d: r1 != ctx\n", verbose(env, "kernel subsystem misconfigured func %s#%d: r1 != ctx\n",
func_id_name(func_id), func_id); func_id_name(func_id), func_id);
@ -12183,7 +12162,6 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
enum bpf_arg_type arg_type = ARG_DONTCARE; enum bpf_arg_type arg_type = ARG_DONTCARE;
u32 regno = i + 1, ref_id, type_size; u32 regno = i + 1, ref_id, type_size;
bool is_ret_buf_sz = false; bool is_ret_buf_sz = false;
bool mask = false;
int kf_arg_type; int kf_arg_type;
t = btf_type_skip_modifiers(btf, args[i].type, NULL); t = btf_type_skip_modifiers(btf, args[i].type, NULL);
@ -12242,15 +12220,12 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
return -EINVAL; return -EINVAL;
} }
mask = mask_raw_tp_reg(env, reg);
if ((is_kfunc_trusted_args(meta) || is_kfunc_rcu(meta)) && if ((is_kfunc_trusted_args(meta) || is_kfunc_rcu(meta)) &&
(register_is_null(reg) || type_may_be_null(reg->type)) && (register_is_null(reg) || type_may_be_null(reg->type)) &&
!is_kfunc_arg_nullable(meta->btf, &args[i])) { !is_kfunc_arg_nullable(meta->btf, &args[i])) {
verbose(env, "Possibly NULL pointer passed to trusted arg%d\n", i); verbose(env, "Possibly NULL pointer passed to trusted arg%d\n", i);
unmask_raw_tp_reg(reg, mask);
return -EACCES; return -EACCES;
} }
unmask_raw_tp_reg(reg, mask);
if (reg->ref_obj_id) { if (reg->ref_obj_id) {
if (is_kfunc_release(meta) && meta->ref_obj_id) { if (is_kfunc_release(meta) && meta->ref_obj_id) {
@ -12308,24 +12283,16 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
if (!is_kfunc_trusted_args(meta) && !is_kfunc_rcu(meta)) if (!is_kfunc_trusted_args(meta) && !is_kfunc_rcu(meta))
break; break;
/* Allow passing maybe NULL raw_tp arguments to
* kfuncs for compatibility. Don't apply this to
* arguments with ref_obj_id > 0.
*/
mask = mask_raw_tp_reg(env, reg);
if (!is_trusted_reg(reg)) { if (!is_trusted_reg(reg)) {
if (!is_kfunc_rcu(meta)) { if (!is_kfunc_rcu(meta)) {
verbose(env, "R%d must be referenced or trusted\n", regno); verbose(env, "R%d must be referenced or trusted\n", regno);
unmask_raw_tp_reg(reg, mask);
return -EINVAL; return -EINVAL;
} }
if (!is_rcu_reg(reg)) { if (!is_rcu_reg(reg)) {
verbose(env, "R%d must be a rcu pointer\n", regno); verbose(env, "R%d must be a rcu pointer\n", regno);
unmask_raw_tp_reg(reg, mask);
return -EINVAL; return -EINVAL;
} }
} }
unmask_raw_tp_reg(reg, mask);
fallthrough; fallthrough;
case KF_ARG_PTR_TO_CTX: case KF_ARG_PTR_TO_CTX:
case KF_ARG_PTR_TO_DYNPTR: case KF_ARG_PTR_TO_DYNPTR:
@ -12348,9 +12315,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
if (is_kfunc_release(meta) && reg->ref_obj_id) if (is_kfunc_release(meta) && reg->ref_obj_id)
arg_type |= OBJ_RELEASE; arg_type |= OBJ_RELEASE;
mask = mask_raw_tp_reg(env, reg);
ret = check_func_arg_reg_off(env, reg, regno, arg_type); ret = check_func_arg_reg_off(env, reg, regno, arg_type);
unmask_raw_tp_reg(reg, mask);
if (ret < 0) if (ret < 0)
return ret; return ret;
@ -12527,7 +12492,6 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
ref_tname = btf_name_by_offset(btf, ref_t->name_off); ref_tname = btf_name_by_offset(btf, ref_t->name_off);
fallthrough; fallthrough;
case KF_ARG_PTR_TO_BTF_ID: case KF_ARG_PTR_TO_BTF_ID:
mask = mask_raw_tp_reg(env, reg);
/* Only base_type is checked, further checks are done here */ /* Only base_type is checked, further checks are done here */
if ((base_type(reg->type) != PTR_TO_BTF_ID || if ((base_type(reg->type) != PTR_TO_BTF_ID ||
(bpf_type_has_unsafe_modifiers(reg->type) && !is_rcu_reg(reg))) && (bpf_type_has_unsafe_modifiers(reg->type) && !is_rcu_reg(reg))) &&
@ -12536,11 +12500,9 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
verbose(env, "expected %s or socket\n", verbose(env, "expected %s or socket\n",
reg_type_str(env, base_type(reg->type) | reg_type_str(env, base_type(reg->type) |
(type_flag(reg->type) & BPF_REG_TRUSTED_MODIFIERS))); (type_flag(reg->type) & BPF_REG_TRUSTED_MODIFIERS)));
unmask_raw_tp_reg(reg, mask);
return -EINVAL; return -EINVAL;
} }
ret = process_kf_arg_ptr_to_btf_id(env, reg, ref_t, ref_tname, ref_id, meta, i); ret = process_kf_arg_ptr_to_btf_id(env, reg, ref_t, ref_tname, ref_id, meta, i);
unmask_raw_tp_reg(reg, mask);
if (ret < 0) if (ret < 0)
return ret; return ret;
break; break;
@ -13513,7 +13475,7 @@ static int sanitize_check_bounds(struct bpf_verifier_env *env,
*/ */
static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
struct bpf_insn *insn, struct bpf_insn *insn,
struct bpf_reg_state *ptr_reg, const struct bpf_reg_state *ptr_reg,
const struct bpf_reg_state *off_reg) const struct bpf_reg_state *off_reg)
{ {
struct bpf_verifier_state *vstate = env->cur_state; struct bpf_verifier_state *vstate = env->cur_state;
@ -13527,7 +13489,6 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
struct bpf_sanitize_info info = {}; struct bpf_sanitize_info info = {};
u8 opcode = BPF_OP(insn->code); u8 opcode = BPF_OP(insn->code);
u32 dst = insn->dst_reg; u32 dst = insn->dst_reg;
bool mask;
int ret; int ret;
dst_reg = &regs[dst]; dst_reg = &regs[dst];
@ -13554,14 +13515,11 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
return -EACCES; return -EACCES;
} }
mask = mask_raw_tp_reg(env, ptr_reg);
if (ptr_reg->type & PTR_MAYBE_NULL) { if (ptr_reg->type & PTR_MAYBE_NULL) {
verbose(env, "R%d pointer arithmetic on %s prohibited, null-check it first\n", verbose(env, "R%d pointer arithmetic on %s prohibited, null-check it first\n",
dst, reg_type_str(env, ptr_reg->type)); dst, reg_type_str(env, ptr_reg->type));
unmask_raw_tp_reg(ptr_reg, mask);
return -EACCES; return -EACCES;
} }
unmask_raw_tp_reg(ptr_reg, mask);
switch (base_type(ptr_reg->type)) { switch (base_type(ptr_reg->type)) {
case PTR_TO_CTX: case PTR_TO_CTX:
@ -16226,6 +16184,29 @@ static int check_return_code(struct bpf_verifier_env *env, int regno, const char
return 0; return 0;
} }
static void mark_subprog_changes_pkt_data(struct bpf_verifier_env *env, int off)
{
struct bpf_subprog_info *subprog;
subprog = find_containing_subprog(env, off);
subprog->changes_pkt_data = true;
}
/* 't' is an index of a call-site.
* 'w' is a callee entry point.
* Eventually this function would be called when env->cfg.insn_state[w] == EXPLORED.
* Rely on DFS traversal order and absence of recursive calls to guarantee that
* callee's change_pkt_data marks would be correct at that moment.
*/
static void merge_callee_effects(struct bpf_verifier_env *env, int t, int w)
{
struct bpf_subprog_info *caller, *callee;
caller = find_containing_subprog(env, t);
callee = find_containing_subprog(env, w);
caller->changes_pkt_data |= callee->changes_pkt_data;
}
/* non-recursive DFS pseudo code /* non-recursive DFS pseudo code
* 1 procedure DFS-iterative(G,v): * 1 procedure DFS-iterative(G,v):
* 2 label v as discovered * 2 label v as discovered
@ -16359,6 +16340,7 @@ static int visit_func_call_insn(int t, struct bpf_insn *insns,
bool visit_callee) bool visit_callee)
{ {
int ret, insn_sz; int ret, insn_sz;
int w;
insn_sz = bpf_is_ldimm64(&insns[t]) ? 2 : 1; insn_sz = bpf_is_ldimm64(&insns[t]) ? 2 : 1;
ret = push_insn(t, t + insn_sz, FALLTHROUGH, env); ret = push_insn(t, t + insn_sz, FALLTHROUGH, env);
@ -16370,8 +16352,10 @@ static int visit_func_call_insn(int t, struct bpf_insn *insns,
mark_jmp_point(env, t + insn_sz); mark_jmp_point(env, t + insn_sz);
if (visit_callee) { if (visit_callee) {
w = t + insns[t].imm + 1;
mark_prune_point(env, t); mark_prune_point(env, t);
ret = push_insn(t, t + insns[t].imm + 1, BRANCH, env); merge_callee_effects(env, t, w);
ret = push_insn(t, w, BRANCH, env);
} }
return ret; return ret;
} }
@ -16688,6 +16672,8 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
mark_prune_point(env, t); mark_prune_point(env, t);
mark_jmp_point(env, t); mark_jmp_point(env, t);
} }
if (bpf_helper_call(insn) && bpf_helper_changes_pkt_data(insn->imm))
mark_subprog_changes_pkt_data(env, t);
if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) { if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) {
struct bpf_kfunc_call_arg_meta meta; struct bpf_kfunc_call_arg_meta meta;
@ -16822,6 +16808,7 @@ static int check_cfg(struct bpf_verifier_env *env)
} }
} }
ret = 0; /* cfg looks good */ ret = 0; /* cfg looks good */
env->prog->aux->changes_pkt_data = env->subprog_info[0].changes_pkt_data;
err_free: err_free:
kvfree(insn_state); kvfree(insn_state);
@ -20075,7 +20062,6 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env)
* for this case. * for this case.
*/ */
case PTR_TO_BTF_ID | MEM_ALLOC | PTR_UNTRUSTED: case PTR_TO_BTF_ID | MEM_ALLOC | PTR_UNTRUSTED:
case PTR_TO_BTF_ID | PTR_TRUSTED | PTR_MAYBE_NULL:
if (type == BPF_READ) { if (type == BPF_READ) {
if (BPF_MODE(insn->code) == BPF_MEM) if (BPF_MODE(insn->code) == BPF_MEM)
insn->code = BPF_LDX | BPF_PROBE_MEM | insn->code = BPF_LDX | BPF_PROBE_MEM |
@ -20311,6 +20297,7 @@ static int jit_subprogs(struct bpf_verifier_env *env)
func[i]->aux->num_exentries = num_exentries; func[i]->aux->num_exentries = num_exentries;
func[i]->aux->tail_call_reachable = env->subprog_info[i].tail_call_reachable; func[i]->aux->tail_call_reachable = env->subprog_info[i].tail_call_reachable;
func[i]->aux->exception_cb = env->subprog_info[i].is_exception_cb; func[i]->aux->exception_cb = env->subprog_info[i].is_exception_cb;
func[i]->aux->changes_pkt_data = env->subprog_info[i].changes_pkt_data;
if (!i) if (!i)
func[i]->aux->exception_boundary = env->seen_exception; func[i]->aux->exception_boundary = env->seen_exception;
func[i] = bpf_int_jit_compile(func[i]); func[i] = bpf_int_jit_compile(func[i]);
@ -22141,6 +22128,7 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
} }
if (tgt_prog) { if (tgt_prog) {
struct bpf_prog_aux *aux = tgt_prog->aux; struct bpf_prog_aux *aux = tgt_prog->aux;
bool tgt_changes_pkt_data;
if (bpf_prog_is_dev_bound(prog->aux) && if (bpf_prog_is_dev_bound(prog->aux) &&
!bpf_prog_dev_bound_match(prog, tgt_prog)) { !bpf_prog_dev_bound_match(prog, tgt_prog)) {
@ -22175,6 +22163,14 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
"Extension programs should be JITed\n"); "Extension programs should be JITed\n");
return -EINVAL; return -EINVAL;
} }
tgt_changes_pkt_data = aux->func
? aux->func[subprog]->aux->changes_pkt_data
: aux->changes_pkt_data;
if (prog->aux->changes_pkt_data && !tgt_changes_pkt_data) {
bpf_log(log,
"Extension program changes packet data, while original does not\n");
return -EINVAL;
}
} }
if (!tgt_prog->jited) { if (!tgt_prog->jited) {
bpf_log(log, "Can attach to only JITed progs\n"); bpf_log(log, "Can attach to only JITed progs\n");
@ -22640,10 +22636,6 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
if (ret < 0) if (ret < 0)
goto skip_full_check; goto skip_full_check;
ret = check_attach_btf_id(env);
if (ret)
goto skip_full_check;
ret = resolve_pseudo_ldimm64(env); ret = resolve_pseudo_ldimm64(env);
if (ret < 0) if (ret < 0)
goto skip_full_check; goto skip_full_check;
@ -22658,6 +22650,10 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
if (ret < 0) if (ret < 0)
goto skip_full_check; goto skip_full_check;
ret = check_attach_btf_id(env);
if (ret)
goto skip_full_check;
ret = mark_fastcall_patterns(env); ret = mark_fastcall_patterns(env);
if (ret < 0) if (ret < 0)
goto skip_full_check; goto skip_full_check;

View File

@ -2250,6 +2250,9 @@ void perf_event_detach_bpf_prog(struct perf_event *event)
goto unlock; goto unlock;
old_array = bpf_event_rcu_dereference(event->tp_event->prog_array); old_array = bpf_event_rcu_dereference(event->tp_event->prog_array);
if (!old_array)
goto put;
ret = bpf_prog_array_copy(old_array, event->prog, NULL, 0, &new_array); ret = bpf_prog_array_copy(old_array, event->prog, NULL, 0, &new_array);
if (ret < 0) { if (ret < 0) {
bpf_prog_array_delete_safe(old_array, event->prog); bpf_prog_array_delete_safe(old_array, event->prog);
@ -2258,6 +2261,14 @@ void perf_event_detach_bpf_prog(struct perf_event *event)
bpf_prog_array_free_sleepable(old_array); bpf_prog_array_free_sleepable(old_array);
} }
put:
/*
* It could be that the bpf_prog is not sleepable (and will be freed
* via normal RCU), but is called from a point that supports sleepable
* programs and uses tasks-trace-RCU.
*/
synchronize_rcu_tasks_trace();
bpf_prog_put(event->prog); bpf_prog_put(event->prog);
event->prog = NULL; event->prog = NULL;

View File

@ -1402,9 +1402,13 @@ static void __uprobe_perf_func(struct trace_uprobe *tu,
#ifdef CONFIG_BPF_EVENTS #ifdef CONFIG_BPF_EVENTS
if (bpf_prog_array_valid(call)) { if (bpf_prog_array_valid(call)) {
const struct bpf_prog_array *array;
u32 ret; u32 ret;
ret = bpf_prog_run_array_uprobe(call->prog_array, regs, bpf_prog_run); rcu_read_lock_trace();
array = rcu_dereference_check(call->prog_array, rcu_read_lock_trace_held());
ret = bpf_prog_run_array_uprobe(array, regs, bpf_prog_run);
rcu_read_unlock_trace();
if (!ret) if (!ret)
return; return;
} }

View File

@ -7899,42 +7899,37 @@ static const struct bpf_func_proto bpf_tcp_raw_check_syncookie_ipv6_proto = {
#endif /* CONFIG_INET */ #endif /* CONFIG_INET */
bool bpf_helper_changes_pkt_data(void *func) bool bpf_helper_changes_pkt_data(enum bpf_func_id func_id)
{ {
if (func == bpf_skb_vlan_push || switch (func_id) {
func == bpf_skb_vlan_pop || case BPF_FUNC_clone_redirect:
func == bpf_skb_store_bytes || case BPF_FUNC_l3_csum_replace:
func == bpf_skb_change_proto || case BPF_FUNC_l4_csum_replace:
func == bpf_skb_change_head || case BPF_FUNC_lwt_push_encap:
func == sk_skb_change_head || case BPF_FUNC_lwt_seg6_action:
func == bpf_skb_change_tail || case BPF_FUNC_lwt_seg6_adjust_srh:
func == sk_skb_change_tail || case BPF_FUNC_lwt_seg6_store_bytes:
func == bpf_skb_adjust_room || case BPF_FUNC_msg_pop_data:
func == sk_skb_adjust_room || case BPF_FUNC_msg_pull_data:
func == bpf_skb_pull_data || case BPF_FUNC_msg_push_data:
func == sk_skb_pull_data || case BPF_FUNC_skb_adjust_room:
func == bpf_clone_redirect || case BPF_FUNC_skb_change_head:
func == bpf_l3_csum_replace || case BPF_FUNC_skb_change_proto:
func == bpf_l4_csum_replace || case BPF_FUNC_skb_change_tail:
func == bpf_xdp_adjust_head || case BPF_FUNC_skb_pull_data:
func == bpf_xdp_adjust_meta || case BPF_FUNC_skb_store_bytes:
func == bpf_msg_pull_data || case BPF_FUNC_skb_vlan_pop:
func == bpf_msg_push_data || case BPF_FUNC_skb_vlan_push:
func == bpf_msg_pop_data || case BPF_FUNC_store_hdr_opt:
func == bpf_xdp_adjust_tail || case BPF_FUNC_xdp_adjust_head:
#if IS_ENABLED(CONFIG_IPV6_SEG6_BPF) case BPF_FUNC_xdp_adjust_meta:
func == bpf_lwt_seg6_store_bytes || case BPF_FUNC_xdp_adjust_tail:
func == bpf_lwt_seg6_adjust_srh || /* tail-called program could call any of the above */
func == bpf_lwt_seg6_action || case BPF_FUNC_tail_call:
#endif
#ifdef CONFIG_INET
func == bpf_sock_ops_store_hdr_opt ||
#endif
func == bpf_lwt_in_push_encap ||
func == bpf_lwt_xmit_push_encap)
return true; return true;
default:
return false; return false;
}
} }
const struct bpf_func_proto bpf_event_output_data_proto __weak; const struct bpf_func_proto bpf_event_output_data_proto __weak;

View File

@ -159,6 +159,7 @@ static void sock_map_del_link(struct sock *sk,
verdict_stop = true; verdict_stop = true;
list_del(&link->list); list_del(&link->list);
sk_psock_free_link(link); sk_psock_free_link(link);
break;
} }
} }
spin_unlock_bh(&psock->link_lock); spin_unlock_bh(&psock->link_lock);
@ -411,12 +412,11 @@ static void *sock_map_lookup_sys(struct bpf_map *map, void *key)
static int __sock_map_delete(struct bpf_stab *stab, struct sock *sk_test, static int __sock_map_delete(struct bpf_stab *stab, struct sock *sk_test,
struct sock **psk) struct sock **psk)
{ {
struct sock *sk; struct sock *sk = NULL;
int err = 0; int err = 0;
spin_lock_bh(&stab->lock); spin_lock_bh(&stab->lock);
sk = *psk; if (!sk_test || sk_test == *psk)
if (!sk_test || sk_test == sk)
sk = xchg(psk, NULL); sk = xchg(psk, NULL);
if (likely(sk)) if (likely(sk))

View File

@ -0,0 +1,107 @@
// SPDX-License-Identifier: GPL-2.0
#include "bpf/libbpf.h"
#include "changes_pkt_data_freplace.skel.h"
#include "changes_pkt_data.skel.h"
#include <test_progs.h>
static void print_verifier_log(const char *log)
{
if (env.verbosity >= VERBOSE_VERY)
fprintf(stdout, "VERIFIER LOG:\n=============\n%s=============\n", log);
}
static void test_aux(const char *main_prog_name,
const char *to_be_replaced,
const char *replacement,
bool expect_load)
{
struct changes_pkt_data_freplace *freplace = NULL;
struct bpf_program *freplace_prog = NULL;
struct bpf_program *main_prog = NULL;
LIBBPF_OPTS(bpf_object_open_opts, opts);
struct changes_pkt_data *main = NULL;
char log[16*1024];
int err;
opts.kernel_log_buf = log;
opts.kernel_log_size = sizeof(log);
if (env.verbosity >= VERBOSE_SUPER)
opts.kernel_log_level = 1 | 2 | 4;
main = changes_pkt_data__open_opts(&opts);
if (!ASSERT_OK_PTR(main, "changes_pkt_data__open"))
goto out;
main_prog = bpf_object__find_program_by_name(main->obj, main_prog_name);
if (!ASSERT_OK_PTR(main_prog, "main_prog"))
goto out;
bpf_program__set_autoload(main_prog, true);
err = changes_pkt_data__load(main);
print_verifier_log(log);
if (!ASSERT_OK(err, "changes_pkt_data__load"))
goto out;
freplace = changes_pkt_data_freplace__open_opts(&opts);
if (!ASSERT_OK_PTR(freplace, "changes_pkt_data_freplace__open"))
goto out;
freplace_prog = bpf_object__find_program_by_name(freplace->obj, replacement);
if (!ASSERT_OK_PTR(freplace_prog, "freplace_prog"))
goto out;
bpf_program__set_autoload(freplace_prog, true);
bpf_program__set_autoattach(freplace_prog, true);
bpf_program__set_attach_target(freplace_prog,
bpf_program__fd(main_prog),
to_be_replaced);
err = changes_pkt_data_freplace__load(freplace);
print_verifier_log(log);
if (expect_load) {
ASSERT_OK(err, "changes_pkt_data_freplace__load");
} else {
ASSERT_ERR(err, "changes_pkt_data_freplace__load");
ASSERT_HAS_SUBSTR(log, "Extension program changes packet data", "error log");
}
out:
changes_pkt_data_freplace__destroy(freplace);
changes_pkt_data__destroy(main);
}
/* There are two global subprograms in both changes_pkt_data.skel.h:
* - one changes packet data;
* - another does not.
* It is ok to freplace subprograms that change packet data with those
* that either do or do not. It is only ok to freplace subprograms
* that do not change packet data with those that do not as well.
* The below tests check outcomes for each combination of such freplace.
* Also test a case when main subprogram itself is replaced and is a single
* subprogram in a program.
*/
void test_changes_pkt_data_freplace(void)
{
struct {
const char *main;
const char *to_be_replaced;
bool changes;
} mains[] = {
{ "main_with_subprogs", "changes_pkt_data", true },
{ "main_with_subprogs", "does_not_change_pkt_data", false },
{ "main_changes", "main_changes", true },
{ "main_does_not_change", "main_does_not_change", false },
};
struct {
const char *func;
bool changes;
} replacements[] = {
{ "changes_pkt_data", true },
{ "does_not_change_pkt_data", false }
};
char buf[64];
for (int i = 0; i < ARRAY_SIZE(mains); ++i) {
for (int j = 0; j < ARRAY_SIZE(replacements); ++j) {
snprintf(buf, sizeof(buf), "%s_with_%s",
mains[i].to_be_replaced, replacements[j].func);
if (!test__start_subtest(buf))
continue;
test_aux(mains[i].main, mains[i].to_be_replaced, replacements[j].func,
mains[i].changes || !replacements[j].changes);
}
}
}

View File

@ -3,11 +3,14 @@
#include <test_progs.h> #include <test_progs.h>
#include "raw_tp_null.skel.h" #include "raw_tp_null.skel.h"
#include "raw_tp_null_fail.skel.h"
void test_raw_tp_null(void) void test_raw_tp_null(void)
{ {
struct raw_tp_null *skel; struct raw_tp_null *skel;
RUN_TESTS(raw_tp_null_fail);
skel = raw_tp_null__open_and_load(); skel = raw_tp_null__open_and_load();
if (!ASSERT_OK_PTR(skel, "raw_tp_null__open_and_load")) if (!ASSERT_OK_PTR(skel, "raw_tp_null__open_and_load"))
return; return;

View File

@ -934,8 +934,10 @@ static void test_sockmap_same_sock(void)
err = socketpair(AF_UNIX, SOCK_STREAM, 0, stream); err = socketpair(AF_UNIX, SOCK_STREAM, 0, stream);
ASSERT_OK(err, "socketpair(af_unix, sock_stream)"); ASSERT_OK(err, "socketpair(af_unix, sock_stream)");
if (err) if (err) {
close(tcp);
goto out; goto out;
}
for (i = 0; i < 2; i++) { for (i = 0; i < 2; i++) {
err = bpf_map_update_elem(map, &zero, &stream[0], BPF_ANY); err = bpf_map_update_elem(map, &zero, &stream[0], BPF_ANY);
@ -954,14 +956,14 @@ static void test_sockmap_same_sock(void)
ASSERT_OK(err, "bpf_map_update_elem(tcp)"); ASSERT_OK(err, "bpf_map_update_elem(tcp)");
} }
close(tcp);
err = bpf_map_delete_elem(map, &zero); err = bpf_map_delete_elem(map, &zero);
ASSERT_OK(err, "bpf_map_delete_elem(entry)"); ASSERT_ERR(err, "bpf_map_delete_elem(entry)");
close(stream[0]); close(stream[0]);
close(stream[1]); close(stream[1]);
out: out:
close(dgram); close(dgram);
close(tcp);
close(udp); close(udp);
test_sockmap_pass_prog__destroy(skel); test_sockmap_pass_prog__destroy(skel);
} }

View File

@ -0,0 +1,39 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
__noinline
long changes_pkt_data(struct __sk_buff *sk)
{
return bpf_skb_pull_data(sk, 0);
}
__noinline __weak
long does_not_change_pkt_data(struct __sk_buff *sk)
{
return 0;
}
SEC("?tc")
int main_with_subprogs(struct __sk_buff *sk)
{
changes_pkt_data(sk);
does_not_change_pkt_data(sk);
return 0;
}
SEC("?tc")
int main_changes(struct __sk_buff *sk)
{
bpf_skb_pull_data(sk, 0);
return 0;
}
SEC("?tc")
int main_does_not_change(struct __sk_buff *sk)
{
return 0;
}
char _license[] SEC("license") = "GPL";

View File

@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("?freplace")
long changes_pkt_data(struct __sk_buff *sk)
{
return bpf_skb_pull_data(sk, 0);
}
SEC("?freplace")
long does_not_change_pkt_data(struct __sk_buff *sk)
{
return 0;
}
char _license[] SEC("license") = "GPL";

View File

@ -3,6 +3,7 @@
#include <vmlinux.h> #include <vmlinux.h>
#include <bpf/bpf_tracing.h> #include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL"; char _license[] SEC("license") = "GPL";
@ -17,16 +18,14 @@ int BPF_PROG(test_raw_tp_null, struct sk_buff *skb)
if (task->pid != tid) if (task->pid != tid)
return 0; return 0;
i = i + skb->mark + 1; /* If dead code elimination kicks in, the increment +=2 will be
/* The compiler may move the NULL check before this deref, which causes * removed. For raw_tp programs attaching to tracepoints in kernel
* the load to fail as deref of scalar. Prevent that by using a barrier. * modules, we mark input arguments as PTR_MAYBE_NULL, so branch
* prediction should never kick in.
*/ */
barrier(); asm volatile ("%[i] += 1; if %[ctx] != 0 goto +1; %[i] += 2;"
/* If dead code elimination kicks in, the increment below will : [i]"+r"(i)
* be removed. For raw_tp programs, we mark input arguments as : [ctx]"r"(skb)
* PTR_MAYBE_NULL, so branch prediction should never kick in. : "memory");
*/
if (!skb)
i += 2;
return 0; return 0;
} }

View File

@ -0,0 +1,24 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
/* Ensure module parameter has PTR_MAYBE_NULL */
SEC("tp_btf/bpf_testmod_test_raw_tp_null")
__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
int test_raw_tp_null_bpf_testmod_test_raw_tp_null_arg_1(void *ctx) {
asm volatile("r1 = *(u64 *)(r1 +0); r1 = *(u64 *)(r1 +0);" ::: __clobber_all);
return 0;
}
/* Check NULL marking */
SEC("tp_btf/sched_pi_setprio")
__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
int test_raw_tp_null_sched_pi_setprio_arg_2(void *ctx) {
asm volatile("r1 = *(u64 *)(r1 +8); r1 = *(u64 *)(r1 +0);" ::: __clobber_all);
return 0;
}

View File

@ -11,6 +11,8 @@ int subprog_tc(struct __sk_buff *skb)
__sink(skb); __sink(skb);
__sink(ret); __sink(ret);
/* let verifier know that 'subprog_tc' can change pointers to skb->data */
bpf_skb_change_proto(skb, 0, 0);
return ret; return ret;
} }

View File

@ -7,11 +7,7 @@
#include "bpf_misc.h" #include "bpf_misc.h"
SEC("tp_btf/bpf_testmod_test_nullable_bare") SEC("tp_btf/bpf_testmod_test_nullable_bare")
/* This used to be a failure test, but raw_tp nullable arguments can now __failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
* directly be dereferenced, whether they have nullable annotation or not,
* and don't need to be explicitly checked.
*/
__success
int BPF_PROG(handle_tp_btf_nullable_bare1, struct bpf_testmod_test_read_ctx *nullable_ctx) int BPF_PROG(handle_tp_btf_nullable_bare1, struct bpf_testmod_test_read_ctx *nullable_ctx)
{ {
return nullable_ctx->len; return nullable_ctx->len;

View File

@ -11,7 +11,7 @@ __success __retval(0)
__naked void btf_ctx_access_accept(void) __naked void btf_ctx_access_accept(void)
{ {
asm volatile (" \ asm volatile (" \
r2 = *(u32*)(r1 + 8); /* load 2nd argument value (int pointer) */\ r2 = *(u64 *)(r1 + 8); /* load 2nd argument value (int pointer) */\
r0 = 0; \ r0 = 0; \
exit; \ exit; \
" ::: __clobber_all); " ::: __clobber_all);
@ -23,7 +23,43 @@ __success __retval(0)
__naked void ctx_access_u32_pointer_accept(void) __naked void ctx_access_u32_pointer_accept(void)
{ {
asm volatile (" \ asm volatile (" \
r2 = *(u32*)(r1 + 0); /* load 1nd argument value (u32 pointer) */\ r2 = *(u64 *)(r1 + 0); /* load 1nd argument value (u32 pointer) */\
r0 = 0; \
exit; \
" ::: __clobber_all);
}
SEC("fentry/bpf_fentry_test9")
__description("btf_ctx_access u32 pointer reject u32")
__failure __msg("size 4 must be 8")
__naked void ctx_access_u32_pointer_reject_32(void)
{
asm volatile (" \
r2 = *(u32 *)(r1 + 0); /* load 1st argument with narrow load */\
r0 = 0; \
exit; \
" ::: __clobber_all);
}
SEC("fentry/bpf_fentry_test9")
__description("btf_ctx_access u32 pointer reject u16")
__failure __msg("size 2 must be 8")
__naked void ctx_access_u32_pointer_reject_16(void)
{
asm volatile (" \
r2 = *(u16 *)(r1 + 0); /* load 1st argument with narrow load */\
r0 = 0; \
exit; \
" ::: __clobber_all);
}
SEC("fentry/bpf_fentry_test9")
__description("btf_ctx_access u32 pointer reject u8")
__failure __msg("size 1 must be 8")
__naked void ctx_access_u32_pointer_reject_8(void)
{
asm volatile (" \
r2 = *(u8 *)(r1 + 0); /* load 1st argument with narrow load */\
r0 = 0; \ r0 = 0; \
exit; \ exit; \
" ::: __clobber_all); " ::: __clobber_all);

View File

@ -11,7 +11,7 @@ __success __retval(0)
__naked void d_path_accept(void) __naked void d_path_accept(void)
{ {
asm volatile (" \ asm volatile (" \
r1 = *(u32*)(r1 + 0); \ r1 = *(u64 *)(r1 + 0); \
r2 = r10; \ r2 = r10; \
r2 += -8; \ r2 += -8; \
r6 = 0; \ r6 = 0; \
@ -31,7 +31,7 @@ __failure __msg("helper call is not allowed in probe")
__naked void d_path_reject(void) __naked void d_path_reject(void)
{ {
asm volatile (" \ asm volatile (" \
r1 = *(u32*)(r1 + 0); \ r1 = *(u64 *)(r1 + 0); \
r2 = r10; \ r2 = r10; \
r2 += -8; \ r2 += -8; \
r6 = 0; \ r6 = 0; \

View File

@ -50,6 +50,13 @@ struct {
__uint(map_flags, BPF_F_NO_PREALLOC); __uint(map_flags, BPF_F_NO_PREALLOC);
} sk_storage_map SEC(".maps"); } sk_storage_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
__uint(max_entries, 1);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(__u32));
} jmp_table SEC(".maps");
SEC("cgroup/skb") SEC("cgroup/skb")
__description("skb->sk: no NULL check") __description("skb->sk: no NULL check")
__failure __msg("invalid mem access 'sock_common_or_null'") __failure __msg("invalid mem access 'sock_common_or_null'")
@ -1037,4 +1044,53 @@ __naked void sock_create_read_src_port(void)
: __clobber_all); : __clobber_all);
} }
__noinline
long skb_pull_data2(struct __sk_buff *sk, __u32 len)
{
return bpf_skb_pull_data(sk, len);
}
__noinline
long skb_pull_data1(struct __sk_buff *sk, __u32 len)
{
return skb_pull_data2(sk, len);
}
/* global function calls bpf_skb_pull_data(), which invalidates packet
* pointers established before global function call.
*/
SEC("tc")
__failure __msg("invalid mem access")
int invalidate_pkt_pointers_from_global_func(struct __sk_buff *sk)
{
int *p = (void *)(long)sk->data;
if ((void *)(p + 1) > (void *)(long)sk->data_end)
return TCX_DROP;
skb_pull_data1(sk, 0);
*p = 42; /* this is unsafe */
return TCX_PASS;
}
__noinline
int tail_call(struct __sk_buff *sk)
{
bpf_tail_call_static(sk, &jmp_table, 0);
return 0;
}
/* Tail calls invalidate packet pointers. */
SEC("tc")
__failure __msg("invalid mem access")
int invalidate_pkt_pointers_by_tail_call(struct __sk_buff *sk)
{
int *p = (void *)(long)sk->data;
if ((void *)(p + 1) > (void *)(long)sk->data_end)
return TCX_DROP;
tail_call(sk);
*p = 42; /* this is unsafe */
return TCX_PASS;
}
char _license[] SEC("license") = "GPL"; char _license[] SEC("license") = "GPL";