diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst index 73578a4c3b1f..901c37504217 100644 --- a/Documentation/bpf/kfuncs.rst +++ b/Documentation/bpf/kfuncs.rst @@ -407,6 +407,13 @@ referenced kptr (by invoking bpf_kptr_xchg). If not, the verifier fails the loading of the BPF program until no lingering references remain in all possible explored states of the program. +A pointer to a struct in the arguments of a KF_ACQUIRE kfunc accepts only an +object of that type. The same applies to the argument that a KF_RELEASE kfunc +releases. For other arguments a kfunc also accepts memory of the program, if the +struct has only scalar members. A struct without members, struct __sk_buff and +struct xdp_md never accept memory. A KF_ACQUIRE kfunc which wants such memory +has to pair the pointer with a __sz argument. + 2.5.2 KF_RET_NULL flag ---------------------- diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 1a8c3e514284..0b44bcb4f56e 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1752,12 +1752,22 @@ enum bpf_func_context { BPF_FUNC_CTX_CNT, }; +struct bpf_subprog_arg_info { + enum bpf_arg_type arg_type; + union { + u32 mem_size; + u32 btf_id; + }; +}; + struct bpf_func_info_aux { u16 linkage; bool unreliable; bool called[BPF_FUNC_CTX_CNT]; bool verified[BPF_FUNC_CTX_CNT]; bool called_with_pkt_ptrs; + /* what the callers of a global function are checked against */ + struct bpf_subprog_arg_info args[MAX_BPF_FUNC_REG_ARGS]; }; enum bpf_jit_poke_reason { diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 8852c173c53e..33dc3d402057 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -923,14 +923,6 @@ struct bpf_func_ptr { /* the subprog that a bpf_func_ptr pointed to was removed as dead code */ #define BPF_FUNC_PTR_DELETED ((u32)-1) -struct bpf_subprog_arg_info { - enum bpf_arg_type arg_type; - union { - u32 mem_size; - u32 btf_id; - }; -}; - enum priv_stack_mode { PRIV_STACK_UNKNOWN, NO_PRIV_STACK, diff --git a/include/linux/btf.h b/include/linux/btf.h index a18254b7521b..87fad4076f0e 100644 --- a/include/linux/btf.h +++ b/include/linux/btf.h @@ -631,6 +631,7 @@ s32 btf_find_dtor_kfunc(struct btf *btf, u32 btf_id); int register_btf_id_dtor_kfuncs(const struct btf_id_dtor_kfunc *dtors, u32 add_cnt, struct module *owner); struct btf_struct_meta *btf_find_struct_meta(const struct btf *btf, u32 btf_id); +bool btf_is_projection_type(const char *pname); bool btf_is_projection_of(const char *pname, const char *tname); bool btf_is_prog_ctx_type(struct bpf_verifier_log *log, const struct btf *btf, const struct btf_type *t, enum bpf_prog_type prog_type, diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c index 3c61c181a6fa..0b1ec3306ea4 100644 --- a/kernel/bpf/arena.c +++ b/kernel/bpf/arena.c @@ -634,7 +634,7 @@ static unsigned long arena_get_unmapped_area(struct file *filp, unsigned long ad if (pgoff) return -EINVAL; - if (len > SZ_4G) + if (len > (u64)map->max_entries * PAGE_SIZE) return -E2BIG; /* if user_vm_start was specified at arena creation time */ @@ -650,11 +650,11 @@ static unsigned long arena_get_unmapped_area(struct file *filp, unsigned long ad ret = mm_get_unmapped_area(filp, addr, len * 2, 0, flags); if (IS_ERR_VALUE(ret)) return ret; + if (!ret) + /* user_vm_start == 0 means that the user range is not set yet */ + return -EINVAL; if ((ret >> 32) == ((ret + len - 1) >> 32)) return ret; - if (WARN_ON_ONCE(arena->user_vm_start)) - /* checks at map creation time should prevent this */ - return -EFAULT; return round_up(ret, SZ_4G); } @@ -679,7 +679,7 @@ static int arena_map_mmap(struct bpf_map *map, struct vm_area_struct *vma) return -EBUSY; /* Earlier checks should prevent this */ - if (WARN_ON_ONCE(vma->vm_end - vma->vm_start > SZ_4G || vma->vm_pgoff)) + if (WARN_ON_ONCE(vma_pages(vma) > map->max_entries || vma->vm_pgoff)) return -EFAULT; if (remember_vma(arena, vma)) @@ -988,6 +988,8 @@ static int arena_free_range(struct bpf_arena *arena, long uaddr, long page_cnt) static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt, bool sleepable) { + /* The first mmap() may be setting the user range right now */ + u64 user_vm_start = READ_ONCE(arena->user_vm_start); struct mem_cgroup *new_memcg, *old_memcg; u64 full_uaddr, uaddr_end; struct arena_free_span *s; @@ -995,8 +997,8 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt, /* only aligned lower 32-bit are relevant */ uaddr = (u32)uaddr; uaddr &= PAGE_MASK; - full_uaddr = clear_lo32(arena->user_vm_start) + uaddr; - if (full_uaddr < arena->user_vm_start) + full_uaddr = clear_lo32(user_vm_start) + uaddr; + if (!user_vm_start || full_uaddr < user_vm_start) return; uaddr_end = min(arena->user_vm_end, full_uaddr + (page_cnt << PAGE_SHIFT)); if (full_uaddr >= uaddr_end) diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 1be30487a2fa..8bf39098ba97 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -6590,6 +6590,15 @@ static int find_kern_ctx_type_id(enum bpf_prog_type prog_type) return ctx_type->type; } +/* + * A projection type is the view that programs have of a kernel object with + * another layout: see btf_is_projection_of() for the two types behind them. + */ +bool btf_is_projection_type(const char *pname) +{ + return strcmp(pname, "__sk_buff") == 0 || strcmp(pname, "xdp_md") == 0; +} + bool btf_is_projection_of(const char *pname, const char *tname) { if (strcmp(pname, "__sk_buff") == 0 && strcmp(tname, "sk_buff") == 0) @@ -8483,9 +8492,8 @@ static int btf_check_func_type_match(struct bpf_verifier_log *log, } /* This is an optional check to make program writing easier. * Compare names of structs and report an error to the user. - * btf_prepare_func_args() already checked that t2 struct - * is a context type. btf_prepare_func_args() will check - * later that t1 struct is a context type as well. + * check_ext_args() compares what btf_prepare_func_args() + * derives for the two arguments. */ s1 = btf_name_by_offset(btf1, t1->name_off); s2 = btf_name_by_offset(btf2, t2->name_off); @@ -9019,6 +9027,11 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog) return err; sub->arg_slot_cnt = slots_used; + /* check_ext_args() needs them after env is freed */ + if (is_global) + memcpy(subprog_aux(env, subprog)->args, sub->args, + sizeof(subprog_aux(env, subprog)->args)); + sub->args_cached = true; return 0; diff --git a/kernel/bpf/cgroup.c b/kernel/bpf/cgroup.c index ce82963bcfc7..57a785241d42 100644 --- a/kernel/bpf/cgroup.c +++ b/kernel/bpf/cgroup.c @@ -1279,9 +1279,14 @@ static struct bpf_prog_list *find_detach_entry(struct hlist_head *progs, return ERR_PTR(-ENOENT); /* to maintain backward compatibility NONE and OVERRIDE cgroups - * allow detaching with invalid FD (prog==NULL) in legacy mode + * allow detaching with invalid FD (prog==NULL) in legacy mode. + * Without the FD of the attached program the caller needs + * CAP_NET_ADMIN or CAP_SYS_ADMIN in the initial user namespace. */ - return hlist_entry(progs->first, typeof(*pl), node); + pl = hlist_entry(progs->first, typeof(*pl), node); + if (pl->prog != prog && !bpf_net_capable()) + return ERR_PTR(-EPERM); + return pl; } if (!prog && !link) @@ -1785,7 +1790,7 @@ int __cgroup_bpf_run_filter_skb(struct sock *sk, struct sk_buff *skb, enum cgroup_bpf_attach_type atype) { - unsigned int offset = -skb_network_offset(skb); + int offset = skb_network_offset(skb); struct sock *save_sk; void *saved_data_end; struct cgroup *cgrp; @@ -1797,7 +1802,15 @@ int __cgroup_bpf_run_filter_skb(struct sock *sk, cgrp = sock_cgroup_ptr(&sk->sk_cgrp_data); save_sk = skb->sk; skb->sk = sk; - __skb_push(skb, offset); + + /* + * The program sees the packet from its network header on, on + * whichever side of skb->data that header lies. + */ + if (offset > 0) + __skb_pull(skb, offset); + else + __skb_push(skb, -offset); /* compute pointers for the bpf prog */ bpf_compute_and_save_data_end(skb, &saved_data_end); @@ -1839,7 +1852,10 @@ int __cgroup_bpf_run_filter_skb(struct sock *sk, ret = -EFAULT; } bpf_restore_data_end(skb, saved_data_end); - __skb_pull(skb, offset); + if (offset > 0) + __skb_push(skb, offset); + else + __skb_pull(skb, -offset); skb->sk = save_sk; return ret; diff --git a/kernel/bpf/stackmap.c b/kernel/bpf/stackmap.c index f7e8d766d128..a19126f5db02 100644 --- a/kernel/bpf/stackmap.c +++ b/kernel/bpf/stackmap.c @@ -522,12 +522,14 @@ static int stackid_init(struct stackid *stackid, struct bpf_map *map, u32 skip = flags & BPF_F_SKIP_FIELD_MASK; u32 max_depth; + max_depth = stack_map_calculate_max_depth(map->value_size, stack_map_data_size(map), flags); + /* trace_nr can be above max_depth and max_depth below skip: clamp first */ + trace_nr = min_t(u32, trace_nr, max_depth); if (trace_nr <= skip) /* skipping more than usable stack trace */ return -EFAULT; - max_depth = stack_map_calculate_max_depth(map->value_size, stack_map_data_size(map), flags); - stackid->nr = min_t(u32, trace_nr - skip, max_depth - skip); + stackid->nr = trace_nr - skip; stackid->len = stackid->nr * sizeof(u64); stackid->ips = trace->ip + skip; stackid->hash = jhash2((const u32 *)stackid->ips, stackid->len / sizeof(u32), 0); @@ -642,7 +644,8 @@ BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map, /* couldn't fetch the stack trace */ return -EFAULT; - err = stackid_fastpath(&stackid, map, trace, trace->nr, flags); + /* the entry is released already, an NMI can change trace->nr */ + err = stackid_fastpath(&stackid, map, trace, READ_ONCE(trace->nr), flags); if (err != -ENOENT) return err; @@ -822,14 +825,20 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, crosstask, false, 0); } - if (unlikely(!trace) || trace->nr < skip) { - if (may_fault) - rcu_read_unlock(); - preempt_enable(); - goto err_fault; - } + if (unlikely(!trace)) + goto err_unlock; - trace_nr = callchain_store(trace, trace->nr, buf, elem_size, flags); + /* + * trace->nr can be above max_depth. The per-CPU entry is released + * already: a tracing program or an NMI which takes a callchain in the + * same context before the copy below reuses it. And the unwinders of + * PowerPC store context markers on top of max_depth. + */ + trace_nr = min_t(u32, READ_ONCE(trace->nr), max_depth); + if (trace_nr < skip) + goto err_unlock; + + trace_nr = callchain_store(trace, trace_nr, buf, elem_size, flags); /* trace should not be dereferenced after this point */ if (may_fault) @@ -838,6 +847,10 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, return callchain_finalize(buf, size, trace_nr, elem_size, flags, may_fault); +err_unlock: + if (may_fault) + rcu_read_unlock(); + preempt_enable(); err_fault: err = -EFAULT; clear: diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index a90bc6c4033a..217bcfa6ebfe 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -13574,7 +13574,12 @@ static int resolve_func_arg_type(struct bpf_verifier_env *env, if (base_type(*arg_type) != ARG_PTR_TO_BTF_ID) return 0; + /* + * A kfunc that releases an object, or that takes a reference to one, + * needs the object itself: memory of the program cannot stand in for it. + */ if (!is_kfunc(meta) || arg_type_is_release(*arg_type) || + is_kfunc_acquire(meta) || base_type(reg->type) == PTR_TO_BTF_ID || reg2btf_ids[base_type(reg->type)]) return 0; @@ -13585,7 +13590,15 @@ static int resolve_func_arg_type(struct bpf_verifier_env *env, ref_t = btf_type_skip_modifiers(btf, ref_id, &ref_id); ref_tname = btf_name_by_offset(btf, ref_t->name_off); - if (!btf_type_is_scalar_struct(env, btf, ref_t)) + /* + * A kfunc that takes a projection type (ie. xdp_md) casts it to the + * underlying type. So memory of the program cannot stand in for it, + * even if the projection type holds only scalars. A struct without + * members (ie. iou_ctx) is a handle for an object of another type. + */ + if (btf_is_projection_type(ref_tname) || + !btf_type_is_scalar_struct(env, btf, ref_t) || + !btf_type_vlen(ref_t)) return 0; resolve_ret = btf_resolve_size(btf, ref_t, arg_size); @@ -14340,9 +14353,13 @@ static int process_arg_ptr_to_btf_id(struct bpf_verifier_env *env, struct bpf_re /* If kfunc is accepting a projection type (ie. __sk_buff), it cannot * actually use it -- it must cast to the underlying type. So we allow - * caller to pass in the underlying type. + * caller to pass in the underlying type. Only kernel BTF has that type: + * a type of the same name in the BTF of the program is program memory. + * And only a pointer to the start of the object points to that type. */ - taking_projection = is_kfunc(meta) && btf_is_projection_of(arg_tname, reg_tname); + taking_projection = is_kfunc(meta) && btf_is_kernel(reg_btf) && + !reg->var_off.value && + btf_is_projection_of(arg_tname, reg_tname); if (!taking_projection && !struct_same) { verbose(env, "%s %s expected pointer to %s %s but %s has a pointer to %s %s\n", meta->func_name, reg_arg_name(env, argno), @@ -22510,6 +22527,37 @@ static bool func_has_rcu_read_lock(const struct bpf_prog *prog, int subprog) !sub_aux->called[BPF_FUNC_CTX_NO_RCU_LOCK]; } +/* + * The callers of a global function are checked against the arguments that its + * BTF describes, and an extension is verified with those of its own BTF. It + * must not expect more than the callers of the function provide. + * + * While an extension is loaded, bpf_check_attach_target() runs before its + * arguments are known. check_attach_btf_id() does the check when they are. + */ +static int check_ext_args(struct bpf_verifier_log *log, const struct bpf_prog *prog, + const struct bpf_prog *tgt_prog, int subprog) +{ + const struct bpf_subprog_arg_info *tgt = tgt_prog->aux->func_info_aux[subprog].args; + const struct bpf_subprog_arg_info *ext = prog->aux->func_info_aux[0].args; + int i; + + for (i = 0; i < MAX_BPF_FUNC_REG_ARGS && ext[i].arg_type != ARG_UNUSED; i++) { + /* An extension may accept NULL where the callers never pass it. */ + if ((ext[i].arg_type == tgt[i].arg_type || + ext[i].arg_type == (tgt[i].arg_type | PTR_MAYBE_NULL)) && + (base_type(ext[i].arg_type) == ARG_PTR_TO_MEM ? + ext[i].mem_size <= tgt[i].mem_size : ext[i].btf_id == tgt[i].btf_id)) + continue; + + bpf_log(log, "Extension program's argument in R%d is incompatible with original\n", + i + 1); + return -EINVAL; + } + + return 0; +} + int bpf_check_attach_target(struct bpf_verifier_log *log, const struct bpf_prog *prog, const struct bpf_prog *tgt_prog, @@ -22797,7 +22845,8 @@ int bpf_check_attach_target(struct bpf_verifier_log *log, return -EINVAL; } if (prog_extension && - btf_check_type_match(log, prog, btf, t)) + (btf_check_type_match(log, prog, btf, t) || + check_ext_args(log, prog, tgt_prog, subprog))) return -EINVAL; t = btf_attach_func_proto(log, btf, btf_id); if (!t || !btf_type_is_func_proto(t)) @@ -23017,6 +23066,15 @@ static int check_attach_btf_id(struct bpf_verifier_env *env) */ env->ops = bpf_verifier_ops[tgt_prog->type]; prog->expected_attach_type = tgt_prog->expected_attach_type; + + /* only now it is known what the arguments of prog stand for */ + ret = btf_prepare_func_args(env, 0); + if (ret) + return ret; + ret = check_ext_args(&env->log, prog, tgt_prog, + find_tgt_subprog(tgt_prog, btf_id)); + if (ret) + return ret; } /* store info about the attachment target that will be used later */ diff --git a/net/bpf/test_run.c b/net/bpf/test_run.c index 3043a7393c29..d6c9dc62182e 100644 --- a/net/bpf/test_run.c +++ b/net/bpf/test_run.c @@ -1210,7 +1210,8 @@ int bpf_prog_test_run_skb(struct bpf_prog *prog, const union bpf_attr *kattr, skb->ip_summed = CHECKSUM_COMPLETE; } - if (prog->type == BPF_PROG_TYPE_LWT_XMIT) { + if (prog->type == BPF_PROG_TYPE_LWT_IN || + prog->type == BPF_PROG_TYPE_LWT_XMIT) { if (!ipv6_mod_enabled()) { pr_warn_once("Please test this program with IPv6 enabled kernel\n"); ret = -EOPNOTSUPP; diff --git a/net/core/filter.c b/net/core/filter.c index 303c0c44dc2c..9245009d9f1e 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -3369,6 +3369,10 @@ static const struct bpf_func_proto bpf_skb_vlan_pop_proto = { static int bpf_skb_generic_push(struct sk_buff *skb, u32 off, u32 len) { + /* The bytes in front of @off are moved with skb->data. */ + if (unlikely(off > skb->len || !pskb_may_pull(skb, off))) + return -ENOMEM; + /* Caller already did skb_cow() with meta_len+len as headroom, * so no need to do it here. */ @@ -3389,9 +3393,10 @@ static int bpf_skb_generic_pop(struct sk_buff *skb, u32 off, u32 len) void *old_data; /* skb_ensure_writable() is not needed here, as we're - * already working on an uncloned skb. + * already working on an uncloned skb. The first test + * keeps off + len from wrapping. */ - if (unlikely(!pskb_may_pull(skb, off + len))) + if (unlikely(off > skb->len || !pskb_may_pull(skb, off + len))) return -ENOMEM; old_data = skb->data; @@ -3402,9 +3407,25 @@ static int bpf_skb_generic_pop(struct sk_buff *skb, u32 off, u32 len) return 0; } +/* + * bpf_skb_net_hdr_push() inserts bytes at @off, bpf_skb_net_hdr_pop() removes + * bytes at @off. Both move the bytes in front of @off together with skb->data, + * and leave the rest of the packet where it is. Return true if the transport + * header is in the part that moves. A transport header that is equal to the + * network header stays equal to it. + */ +static bool bpf_skb_trans_hdr_moves(const struct sk_buff *skb, u32 off) +{ + if (skb->transport_header == skb->network_header) + return true; + + return skb_transport_header_was_set(skb) && + (u32)skb_transport_offset(skb) < off; +} + static int bpf_skb_net_hdr_push(struct sk_buff *skb, u32 off, u32 len) { - bool trans_same = skb->transport_header == skb->network_header; + bool trans_moves = bpf_skb_trans_hdr_moves(skb, off); int ret; /* There's no need for __skb_push()/__skb_pull() pair to @@ -3415,8 +3436,8 @@ static int bpf_skb_net_hdr_push(struct sk_buff *skb, u32 off, u32 len) if (likely(!ret)) { skb->mac_header -= len; skb->network_header -= len; - if (trans_same) - skb->transport_header = skb->network_header; + if (trans_moves) + skb->transport_header -= len; } return ret; @@ -3424,7 +3445,7 @@ static int bpf_skb_net_hdr_push(struct sk_buff *skb, u32 off, u32 len) static int bpf_skb_net_hdr_pop(struct sk_buff *skb, u32 off, u32 len) { - bool trans_same = skb->transport_header == skb->network_header; + bool trans_moves = bpf_skb_trans_hdr_moves(skb, off); int ret; /* Same here, __skb_push()/__skb_pull() pair not needed. */ @@ -3432,8 +3453,17 @@ static int bpf_skb_net_hdr_pop(struct sk_buff *skb, u32 off, u32 len) if (likely(!ret)) { skb->mac_header += len; skb->network_header += len; - if (trans_same) - skb->transport_header = skb->network_header; + if (trans_moves) { + skb->transport_header += len; + } else if (skb_transport_header_was_set(skb) && + skb_transport_offset(skb) < (int)off) { + /* It was in the removed bytes or before skb->data. */ + skb_set_transport_header(skb, off); + } + /* Do not leave the inner transport header in front of @off. */ + if (skb->encapsulation && + skb_inner_transport_offset(skb) < (int)off) + skb_set_inner_transport_header(skb, off); } return ret; @@ -3744,6 +3774,25 @@ static int bpf_skb_net_grow(struct sk_buff *skb, u32 off, u32 len_diff, return 0; } +/* + * Code that went by the inner transport header while skb->encapsulation was set + * goes by the transport header from now on. So take the inner one over, unless + * it is the smaller one, as it is if nobody has set it. Do not leave the result + * in front of @off, where the bytes were removed. + */ +static void bpf_skb_clear_encap(struct sk_buff *skb, u32 off) +{ + int thoff; + + if (!skb->encapsulation) + return; + + thoff = max(skb->inner_transport_header, skb->transport_header) - + skb_headroom(skb); + skb_set_transport_header(skb, max_t(int, thoff, off)); + skb->encapsulation = 0; +} + static int bpf_skb_net_shrink(struct sk_buff *skb, u32 off, u32 len_diff, u64 flags) { @@ -3762,6 +3811,14 @@ static int bpf_skb_net_shrink(struct sk_buff *skb, u32 off, u32 len_diff, return -ENOTSUPP; } + /* + * The MSS is raised by len_diff. It has 16 bits, and GSO_BY_FRAGS is + * not a size. + */ + if (skb_is_gso(skb) && !(flags & BPF_F_ADJ_ROOM_FIXED_GSO) && + skb_shinfo(skb)->gso_size + len_diff >= GSO_BY_FRAGS) + return -ENOTSUPP; + ret = skb_unclone(skb, GFP_ATOMIC); if (unlikely(ret < 0)) return ret; @@ -3817,8 +3874,7 @@ static int bpf_skb_net_shrink(struct sk_buff *skb, u32 off, u32 len_diff, SKB_GSO_IPXIP4 | SKB_GSO_IPXIP6 | SKB_GSO_ESP))) - if (skb->encapsulation) - skb->encapsulation = 0; + bpf_skb_clear_encap(skb, off); } /* Header must be checked, and gso_segs recomputed. */ @@ -3826,10 +3882,9 @@ static int bpf_skb_net_shrink(struct sk_buff *skb, u32 off, u32 len_diff, shinfo->gso_segs = 0; } else { /* For non-GSO packets, clear encapsulation if decap flags are set */ - if ((flags & (BPF_F_ADJ_ROOM_DECAP_L4_MASK | - BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK)) && - skb->encapsulation) - skb->encapsulation = 0; + if (flags & (BPF_F_ADJ_ROOM_DECAP_L4_MASK | + BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK)) + bpf_skb_clear_encap(skb, off); } return 0; @@ -6916,14 +6971,22 @@ static int bpf_push_seg6_encap(struct sk_buff *skb, u32 type, void *hdr, u32 len int err; struct ipv6_sr_hdr *srh = (struct ipv6_sr_hdr *)hdr; + /* With ipv6.disable=1 the netns has no seg6 data and no IPv6 tables. */ + if (!ipv6_mod_enabled()) + return -EAFNOSUPPORT; + if (!seg6_validate_srh(srh, len, false)) return -EINVAL; + /* + * The encap mode names IPv6 as the inner protocol, and neither mode + * updates skb->protocol. + */ + if (skb->protocol != htons(ETH_P_IPV6)) + return -EBADMSG; + switch (type) { case BPF_LWT_ENCAP_SEG6_INLINE: - if (skb->protocol != htons(ETH_P_IPV6)) - return -EBADMSG; - err = seg6_do_srh_inline(skb, srh); break; case BPF_LWT_ENCAP_SEG6: @@ -7012,12 +7075,13 @@ BPF_CALL_4(bpf_lwt_seg6_store_bytes, struct sk_buff *, skb, u32, offset, this_cpu_ptr(&seg6_bpf_srh_states); struct ipv6_sr_hdr *srh = srh_state->srh; void *srh_tlvs, *srh_end, *ptr; - int srhoff = 0; + int srhoff; lockdep_assert_held(&srh_state->bh_lock); if (srh == NULL) return -EINVAL; + srhoff = (unsigned char *)srh - skb->data; srh_tlvs = (void *)((char *)srh + ((srh->first_segment + 1) << 4)); srh_end = (void *)((char *)srh + sizeof(*srh) + srh_state->hdrlen); @@ -7030,8 +7094,7 @@ BPF_CALL_4(bpf_lwt_seg6_store_bytes, struct sk_buff *, skb, u32, offset, if (unlikely(bpf_try_make_writable(skb, offset + len))) return -EFAULT; - if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0) - return -EINVAL; + /* The head may have moved. */ srh_state->srh = (struct ipv6_sr_hdr *)(skb->data + srhoff); memcpy(skb->data + offset, from, len); @@ -7147,16 +7210,18 @@ BPF_CALL_3(bpf_lwt_seg6_adjust_srh, struct sk_buff *, skb, u32, offset, { struct seg6_bpf_srh_state *srh_state = this_cpu_ptr(&seg6_bpf_srh_states); + bool mac_set = skb_mac_header_was_set(skb); struct ipv6_sr_hdr *srh = srh_state->srh; void *srh_end, *srh_tlvs, *ptr; struct ipv6hdr *hdr; - int srhoff = 0; + int srhoff; int ret; lockdep_assert_held(&srh_state->bh_lock); if (unlikely(srh == NULL)) return -EINVAL; + srhoff = (unsigned char *)srh - skb->data; srh_tlvs = (void *)((unsigned char *)srh + sizeof(*srh) + ((srh->first_segment + 1) << 4)); srh_end = (void *)((unsigned char *)srh + sizeof(*srh) + @@ -7167,26 +7232,46 @@ BPF_CALL_3(bpf_lwt_seg6_adjust_srh, struct sk_buff *, skb, u32, offset, return -EFAULT; if (unlikely(len < 0 && (void *)((char *)ptr - len) > srh_end)) return -EFAULT; + /* The length field of the SRH has 8 bits, in units of 8 bytes. */ + if (unlikely(len > (U8_MAX << 3) - srh_state->hdrlen)) + return -EFAULT; + /* + * skb->data is the network header here. The MAC header is in front of + * it, so bpf_skb_net_hdr_push() and bpf_skb_net_hdr_pop() do not move + * it with the other headers. + */ if (len > 0) { - ret = skb_cow_head(skb, len); + ret = skb_cow_head(skb, len + skb->mac_len); if (unlikely(ret < 0)) return ret; + /* The push can write over the MAC header, so move it first. */ + if (mac_set) + memmove(skb->data - len - skb->mac_len, + skb_mac_header(skb), skb->mac_len); ret = bpf_skb_net_hdr_push(skb, offset, len); } else { ret = bpf_skb_net_hdr_pop(skb, offset, -1 * len); + /* The pop has added -len to skb->mac_header. */ + if (likely(!ret) && mac_set) + memmove(skb->data - skb->mac_len, + skb_mac_header(skb) + len, skb->mac_len); } bpf_compute_data_pointers(skb); if (unlikely(ret < 0)) return ret; + if (mac_set) + skb_set_mac_header(skb, -skb->mac_len); + else + skb_unset_mac_header(skb); + hdr = (struct ipv6hdr *)skb->data; hdr->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); - if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0) - return -EINVAL; + /* The bytes in front of offset have moved together with skb->data. */ srh_state->srh = (struct ipv6_sr_hdr *)(skb->data + srhoff); srh_state->hdrlen += len; srh_state->valid = false; diff --git a/net/core/lwt_bpf.c b/net/core/lwt_bpf.c index da49364ec63d..774fad4e9fbf 100644 --- a/net/core/lwt_bpf.c +++ b/net/core/lwt_bpf.c @@ -85,6 +85,32 @@ static int run_lwt_bpf(struct sk_buff *skb, struct bpf_lwt_prog *lwt, return ret; } +/* + * skb->cb still describes the IP header that was the first one before the + * prog ran. The option offsets in IPCB(skb)->opt are wrong for a pushed + * header, and a header of the other family needs the other layout. iif + * stays: behind a VRF skb->skb_iif is the L3 master and not the port. + * bpf_lwt_push_encap() cannot do this, because bpf_prog_run_save_cb() can + * restore skb->cb after the prog. + */ +static void bpf_lwt_reset_cb(struct sk_buff *skb, __be16 proto) +{ + int iif; + + if (skb->protocol == proto) { + if (proto == htons(ETH_P_IP)) + memset(&IPCB(skb)->opt, 0, sizeof(IPCB(skb)->opt)); + } else if (skb->protocol == htons(ETH_P_IP)) { + iif = IP6CB(skb)->iif; + memset(IPCB(skb), 0, sizeof(*IPCB(skb))); + IPCB(skb)->iif = iif; + } else { + iif = IPCB(skb)->iif; + memset(IP6CB(skb), 0, sizeof(*IP6CB(skb))); + IP6CB(skb)->iif = iif; + } +} + static int bpf_lwt_input_reroute(struct sk_buff *skb) { enum skb_drop_reason reason; @@ -102,7 +128,7 @@ static int bpf_lwt_input_reroute(struct sk_buff *skb) dev_put(dev); } else if (skb->protocol == htons(ETH_P_IPV6)) { skb_dst_drop(skb); - if (IS_ENABLED(CONFIG_IPV6)) { + if (ipv6_mod_enabled()) { ip6_route_input(skb); err = skb_dst(skb)->error; } else { @@ -124,6 +150,8 @@ static int bpf_lwt_input_reroute(struct sk_buff *skb) static int bpf_input(struct sk_buff *skb) { struct dst_entry *dst = skb_dst(skb); + __be16 proto = skb->protocol; + unsigned int len = skb->len; struct bpf_lwt *bpf; int ret; @@ -132,8 +160,21 @@ static int bpf_input(struct sk_buff *skb) ret = run_lwt_bpf(skb, &bpf->in, dst, NO_REDIRECT); if (ret < 0) return ret; + /* An lwt_in prog can change the length only with a push. */ + if (skb->len != len) + bpf_lwt_reset_cb(skb, proto); if (ret == BPF_LWT_REROUTE) return bpf_lwt_input_reroute(skb); + /* + * orig_input() is for the old protocol only, see bpf_xmit(). + * It also needs a dst of the old family. BPF_LWT_ENCAP_SEG6 + * puts an IPv6 dst into the skb. + */ + if (skb->protocol != proto || + skb_dst(skb)->ops->family != dst->ops->family) { + kfree_skb(skb); + return -EINVAL; + } } if (unlikely(!dst->lwtstate->orig_input)) { @@ -311,6 +352,8 @@ static int bpf_xmit(struct sk_buff *skb) case BPF_REDIRECT: return LWTUNNEL_XMIT_DONE; case BPF_LWT_REROUTE: + /* An lwt_xmit prog can also write to the options. */ + bpf_lwt_reset_cb(skb, proto); return bpf_lwt_xmit_reroute(skb); default: return ret; @@ -649,6 +692,14 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) if (ingress) skb_postpush_rcsum(skb, iph, len); skb_reset_network_header(skb); + /* + * The new header goes over the MAC header. On ingress move it first. + * On egress the packet gets a new one, so forget the old one. + */ + if (ingress) + skb_mac_header_rebuild(skb); + else + skb_unset_mac_header(skb); if (is_udp_tunnel) { size_t iph_sz = ipv4 ? iph->ihl * 4 : sizeof(struct ipv6hdr); diff --git a/tools/testing/selftests/bpf/prog_tests/arena_mmap.c b/tools/testing/selftests/bpf/prog_tests/arena_mmap.c new file mode 100644 index 000000000000..2babc9ecf182 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/arena_mmap.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +#define ARENA_PAGES 8 + +static void test_refused(int pages, int flags, int err) +{ + LIBBPF_OPTS(bpf_map_create_opts, opts, .map_flags = BPF_F_MMAPABLE); + void *area; + int fd; + + fd = bpf_map_create(BPF_MAP_TYPE_ARENA, "arena", 0, 0, ARENA_PAGES, &opts); + if (fd == -EOPNOTSUPP) { + test__skip(); + return; + } + if (!ASSERT_GE(fd, 0, "bpf_map_create")) + return; + + area = mmap(NULL, pages * getpagesize(), PROT_READ | PROT_WRITE, + MAP_SHARED | flags, fd, 0); + if (ASSERT_EQ(area, MAP_FAILED, "mmap")) + ASSERT_EQ(errno, err, "errno"); + else + munmap(area, pages * getpagesize()); + close(fd); +} + +void test_arena_mmap(void) +{ + if (test__start_subtest("long")) + test_refused(ARENA_PAGES + 1, 0, E2BIG); + if (test__start_subtest("address_0")) + test_refused(ARENA_PAGES, MAP_FIXED, EINVAL); +} diff --git a/tools/testing/selftests/bpf/prog_tests/freplace_args.c b/tools/testing/selftests/bpf/prog_tests/freplace_args.c new file mode 100644 index 000000000000..d1930af23fa0 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/freplace_args.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include "freplace_args.skel.h" +#include "freplace_args_tgt.skel.h" + +/* + * An extension must not expect more of its arguments than the callers of the + * function that it replaces provide. The progs are loaded and attached to each + * other. None of them runs. + */ +static const struct test { + const char *name; + const char *ext; /* prog of freplace_args.c */ + const char *tgt; /* prog of freplace_args_tgt.c */ + const char *func; /* function of tgt that ext is loaded for */ + const char *func2; /* function of tgt that ext is attached to */ + int bad_reg; /* argument that is refused, 0: none */ +} tests[] = { + { "same_nonnull", "ext_mem_nonnull", "tc_prog", "mem_nonnull" }, + { "same_trusted", "ext_task_trusted", "tc_prog", "task_trusted" }, + { "smaller_struct", "ext_mem_smaller", "tc_prog", "mem_smaller" }, + { "larger_struct", "ext_mem_larger", "tc_prog", "mem_larger", NULL, 1 }, + { "nullable_for_nonnull", "ext_mem", "tc_prog", "mem_nonnull" }, + { "nonnull_for_nullable", "ext_mem_nonnull", "tc_prog", "mem", NULL, 2 }, + { "trusted_for_mem", "ext_task_trusted", "tc_prog", "task_mem", NULL, 1 }, + { "arg_ctx_for_ctx_type", "ext_skb_ctx", "tc_prog", "skb_ctx" }, + { "mem_for_arg_ctx", "ext_tp_mem", "tp_prog", "tagged_ctx", NULL, 1 }, + { "attach_nullable_to_nonnull", "ext_mem", "tc_prog", "mem", "mem_nonnull" }, + { "attach_nonnull_to_nullable", "ext_mem_nonnull", "tc_prog", "mem_nonnull", "mem", 2 }, +}; + +static void run_test(struct freplace_args_tgt *tgt_skel, const struct test *t) +{ + struct bpf_program *ext, *tgt; + struct freplace_args *skel; + char log[4096], msg[128]; + struct bpf_link *link; + int err; + + skel = freplace_args__open(); + if (!ASSERT_OK_PTR(skel, "open")) + return; + + ext = bpf_object__find_program_by_name(skel->obj, t->ext); + tgt = bpf_object__find_program_by_name(tgt_skel->obj, t->tgt); + if (!ASSERT_OK_PTR(ext, "ext") || !ASSERT_OK_PTR(tgt, "tgt")) + goto out; + + log[0] = '\0'; + bpf_program__set_autoload(ext, true); + /* libbpf prints the log of a load that fails and must not */ + if (t->bad_reg && !t->func2) + bpf_program__set_log_buf(ext, log, sizeof(log)); + err = bpf_program__set_attach_target(ext, bpf_program__fd(tgt), t->func); + if (!ASSERT_OK(err, "set_attach_target")) + goto out; + + err = freplace_args__load(skel); + if (t->bad_reg && !t->func2) { + snprintf(msg, sizeof(msg), + "Extension program's argument in R%d is incompatible with original", + t->bad_reg); + if (ASSERT_ERR(err, "load")) + ASSERT_HAS_SUBSTR(log, msg, "log"); + goto out; + } + if (!ASSERT_OK(err, "load") || !t->func2) + goto out; + + link = bpf_program__attach_freplace(ext, bpf_program__fd(tgt), t->func2); + ASSERT_EQ(libbpf_get_error(link), t->bad_reg ? -EINVAL : 0, "attach"); + bpf_link__destroy(link); +out: + freplace_args__destroy(skel); +} + +void test_freplace_args(void) +{ + struct freplace_args_tgt *tgt_skel; + int i; + + tgt_skel = freplace_args_tgt__open_and_load(); + if (!ASSERT_OK_PTR(tgt_skel, "tgt_open_and_load")) + return; + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + if (test__start_subtest(tests[i].name)) + run_test(tgt_skel, &tests[i]); + } + + freplace_args_tgt__destroy(tgt_skel); +} \ No newline at end of file diff --git a/tools/testing/selftests/bpf/progs/freplace_args.c b/tools/testing/selftests/bpf/progs/freplace_args.c new file mode 100644 index 000000000000..6e8f16bd5d8e --- /dev/null +++ b/tools/testing/selftests/bpf/progs/freplace_args.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include + +/* In freplace_args_tgt.c each of them has 16 bytes. */ +struct buf { __u64 v[2]; }; +struct larger { __u64 v[4]; }; +struct smaller { __u64 v[1]; }; +struct tp_ctx { __u64 v[2]; }; + +SEC("?freplace") +int ext_mem(int a, struct buf *p) +{ + return 0; +} + +SEC("?freplace") +int ext_mem_nonnull(int a, struct buf *p __arg_nonnull) +{ + return 0; +} + +SEC("?freplace") +int ext_mem_larger(struct larger *p) +{ + return 0; +} + +SEC("?freplace") +int ext_mem_smaller(struct smaller *p) +{ + return 0; +} + +SEC("?freplace") +int ext_task_trusted(struct task_struct *t __arg_trusted) +{ + return 0; +} + +SEC("?freplace") +int ext_skb_ctx(struct __sk_buff *skb __arg_ctx) +{ + return 0; +} + +SEC("?freplace") +int ext_tp_mem(struct tp_ctx *p) +{ + return 0; +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/freplace_args_tgt.c b/tools/testing/selftests/bpf/progs/freplace_args_tgt.c new file mode 100644 index 000000000000..4f1d1d3d2891 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/freplace_args_tgt.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include + +/* In freplace_args.c 'larger' has 32 bytes and 'smaller' has 8 bytes. */ +struct buf { __u64 v[2]; }; +struct larger { __u64 v[2]; }; +struct smaller { __u64 v[2]; }; +struct tp_ctx { __u64 v[2]; }; + +__weak int mem(int a, struct buf *p) +{ + return 0; +} + +__weak int mem_nonnull(int a, struct buf *p __arg_nonnull) +{ + return 0; +} + +__weak int mem_larger(struct larger *p) +{ + return 0; +} + +__weak int mem_smaller(struct smaller *p) +{ + return 0; +} + +/* No tag: memory of the size of the struct. */ +__weak int task_mem(struct task_struct *t) +{ + return 0; +} + +__weak int task_trusted(struct task_struct *t __arg_trusted) +{ + return 0; +} + +__weak int skb_ctx(struct __sk_buff *skb) +{ + return 0; +} + +SEC("tc") +int tc_prog(struct __sk_buff *skb) +{ + struct smaller smaller = {}; + struct larger larger = {}; + struct buf buf = {}; + + return mem(0, &buf) + mem_nonnull(0, &buf) + mem_larger(&larger) + mem_smaller(&smaller) + + task_mem(NULL) + task_trusted(bpf_get_current_task_btf()) + skb_ctx(skb); +} + +/* In a tracepoint prog the tag makes a context of any struct. */ +__weak int tagged_ctx(struct tp_ctx *ctx __arg_ctx) +{ + return 0; +} + +SEC("tracepoint") +int tp_prog(void *ctx) +{ + return tagged_ctx(ctx); +} + +char _license[] SEC("license") = "GPL";