mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +02:00 
			
		
		
		
	bpf: Add bpf_skc_to_tcp6_sock() helper
The helper is used in tracing programs to cast a socket pointer to a tcp6_sock pointer. The return value could be NULL if the casting is illegal. A new helper return type RET_PTR_TO_BTF_ID_OR_NULL is added so the verifier is able to deduce proper return types for the helper. Different from the previous BTF_ID based helpers, the bpf_skc_to_tcp6_sock() argument can be several possible btf_ids. More specifically, all possible socket data structures with sock_common appearing in the first in the memory layout. This patch only added socket types related to tcp and udp. All possible argument btf_id and return value btf_id for helper bpf_skc_to_tcp6_sock() are pre-calculcated and cached. In the future, it is even possible to precompute these btf_id's at kernel build time. Signed-off-by: Yonghong Song <yhs@fb.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Acked-by: Andrii Nakryiko <andriin@fb.com> Acked-by: Martin KaFai Lau <kafai@fb.com> Link: https://lore.kernel.org/bpf/20200623230809.3988195-1-yhs@fb.com
This commit is contained in:
		
							parent
							
								
									72e2b2b66f
								
							
						
					
					
						commit
						af7ec13833
					
				
					 8 changed files with 148 additions and 12 deletions
				
			
		|  | @ -265,6 +265,7 @@ enum bpf_return_type { | |||
| 	RET_PTR_TO_TCP_SOCK_OR_NULL,	/* returns a pointer to a tcp_sock or NULL */ | ||||
| 	RET_PTR_TO_SOCK_COMMON_OR_NULL,	/* returns a pointer to a sock_common or NULL */ | ||||
| 	RET_PTR_TO_ALLOC_MEM_OR_NULL,	/* returns a pointer to dynamically allocated memory or NULL */ | ||||
| 	RET_PTR_TO_BTF_ID_OR_NULL,	/* returns a pointer to a btf_id or NULL */ | ||||
| }; | ||||
| 
 | ||||
| /* eBPF function prototype used by verifier to allow BPF_CALLs from eBPF programs
 | ||||
|  | @ -287,6 +288,12 @@ struct bpf_func_proto { | |||
| 		enum bpf_arg_type arg_type[5]; | ||||
| 	}; | ||||
| 	int *btf_id; /* BTF ids of arguments */ | ||||
| 	bool (*check_btf_id)(u32 btf_id, u32 arg); /* if the argument btf_id is
 | ||||
| 						    * valid. Often used if more | ||||
| 						    * than one btf id is permitted | ||||
| 						    * for this argument. | ||||
| 						    */ | ||||
| 	int *ret_btf_id; /* return value btf_id */ | ||||
| }; | ||||
| 
 | ||||
| /* bpf_context is intentionally undefined structure. Pointer to bpf_context is
 | ||||
|  | @ -1524,6 +1531,7 @@ static inline bool bpf_map_is_dev_bound(struct bpf_map *map) | |||
| 
 | ||||
| struct bpf_map *bpf_map_offload_map_alloc(union bpf_attr *attr); | ||||
| void bpf_map_offload_map_free(struct bpf_map *map); | ||||
| void init_btf_sock_ids(struct btf *btf); | ||||
| #else | ||||
| static inline int bpf_prog_offload_init(struct bpf_prog *prog, | ||||
| 					union bpf_attr *attr) | ||||
|  | @ -1549,6 +1557,9 @@ static inline struct bpf_map *bpf_map_offload_map_alloc(union bpf_attr *attr) | |||
| static inline void bpf_map_offload_map_free(struct bpf_map *map) | ||||
| { | ||||
| } | ||||
| static inline void init_btf_sock_ids(struct btf *btf) | ||||
| { | ||||
| } | ||||
| #endif /* CONFIG_NET && CONFIG_BPF_SYSCALL */ | ||||
| 
 | ||||
| #if defined(CONFIG_BPF_STREAM_PARSER) | ||||
|  | @ -1638,6 +1649,7 @@ extern const struct bpf_func_proto bpf_ringbuf_reserve_proto; | |||
| extern const struct bpf_func_proto bpf_ringbuf_submit_proto; | ||||
| extern const struct bpf_func_proto bpf_ringbuf_discard_proto; | ||||
| extern const struct bpf_func_proto bpf_ringbuf_query_proto; | ||||
| extern const struct bpf_func_proto bpf_skc_to_tcp6_sock_proto; | ||||
| 
 | ||||
| const struct bpf_func_proto *bpf_tracing_func_proto( | ||||
| 	enum bpf_func_id func_id, const struct bpf_prog *prog); | ||||
|  |  | |||
|  | @ -3255,6 +3255,12 @@ union bpf_attr { | |||
|  * 		case of **BPF_CSUM_LEVEL_QUERY**, the current skb->csum_level | ||||
|  * 		is returned or the error code -EACCES in case the skb is not | ||||
|  * 		subject to CHECKSUM_UNNECESSARY. | ||||
|  * | ||||
|  * struct tcp6_sock *bpf_skc_to_tcp6_sock(void *sk) | ||||
|  *	Description | ||||
|  *		Dynamically cast a *sk* pointer to a *tcp6_sock* pointer. | ||||
|  *	Return | ||||
|  *		*sk* if casting is valid, or NULL otherwise. | ||||
|  */ | ||||
| #define __BPF_FUNC_MAPPER(FN)		\ | ||||
| 	FN(unspec),			\ | ||||
|  | @ -3392,7 +3398,8 @@ union bpf_attr { | |||
| 	FN(ringbuf_submit),		\ | ||||
| 	FN(ringbuf_discard),		\ | ||||
| 	FN(ringbuf_query),		\ | ||||
| 	FN(csum_level), | ||||
| 	FN(csum_level),			\ | ||||
| 	FN(skc_to_tcp6_sock), | ||||
| 
 | ||||
| /* integer value in 'imm' field of BPF_CALL instruction selects which helper
 | ||||
|  * function eBPF program intends to call | ||||
|  |  | |||
|  | @ -3674,6 +3674,7 @@ struct btf *btf_parse_vmlinux(void) | |||
| 		goto errout; | ||||
| 
 | ||||
| 	bpf_struct_ops_init(btf, log); | ||||
| 	init_btf_sock_ids(btf); | ||||
| 
 | ||||
| 	btf_verifier_env_free(env); | ||||
| 	refcount_set(&btf->refcnt, 1); | ||||
|  |  | |||
|  | @ -3800,12 +3800,14 @@ static int int_ptr_type_to_size(enum bpf_arg_type type) | |||
| 	return -EINVAL; | ||||
| } | ||||
| 
 | ||||
| static int check_func_arg(struct bpf_verifier_env *env, u32 regno, | ||||
| 			  enum bpf_arg_type arg_type, | ||||
| 			  struct bpf_call_arg_meta *meta) | ||||
| static int check_func_arg(struct bpf_verifier_env *env, u32 arg, | ||||
| 			  struct bpf_call_arg_meta *meta, | ||||
| 			  const struct bpf_func_proto *fn) | ||||
| { | ||||
| 	u32 regno = BPF_REG_1 + arg; | ||||
| 	struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[regno]; | ||||
| 	enum bpf_reg_type expected_type, type = reg->type; | ||||
| 	enum bpf_arg_type arg_type = fn->arg_type[arg]; | ||||
| 	int err = 0; | ||||
| 
 | ||||
| 	if (arg_type == ARG_DONTCARE) | ||||
|  | @ -3885,9 +3887,16 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 regno, | |||
| 		expected_type = PTR_TO_BTF_ID; | ||||
| 		if (type != expected_type) | ||||
| 			goto err_type; | ||||
| 		if (reg->btf_id != meta->btf_id) { | ||||
| 			verbose(env, "Helper has type %s got %s in R%d\n", | ||||
| 				kernel_type_name(meta->btf_id), | ||||
| 		if (!fn->check_btf_id) { | ||||
| 			if (reg->btf_id != meta->btf_id) { | ||||
| 				verbose(env, "Helper has type %s got %s in R%d\n", | ||||
| 					kernel_type_name(meta->btf_id), | ||||
| 					kernel_type_name(reg->btf_id), regno); | ||||
| 
 | ||||
| 				return -EACCES; | ||||
| 			} | ||||
| 		} else if (!fn->check_btf_id(reg->btf_id, arg)) { | ||||
| 			verbose(env, "Helper does not support %s in R%d\n", | ||||
| 				kernel_type_name(reg->btf_id), regno); | ||||
| 
 | ||||
| 			return -EACCES; | ||||
|  | @ -4709,10 +4718,12 @@ static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn | |||
| 	meta.func_id = func_id; | ||||
| 	/* check args */ | ||||
| 	for (i = 0; i < 5; i++) { | ||||
| 		err = btf_resolve_helper_id(&env->log, fn, i); | ||||
| 		if (err > 0) | ||||
| 			meta.btf_id = err; | ||||
| 		err = check_func_arg(env, BPF_REG_1 + i, fn->arg_type[i], &meta); | ||||
| 		if (!fn->check_btf_id) { | ||||
| 			err = btf_resolve_helper_id(&env->log, fn, i); | ||||
| 			if (err > 0) | ||||
| 				meta.btf_id = err; | ||||
| 		} | ||||
| 		err = check_func_arg(env, i, &meta, fn); | ||||
| 		if (err) | ||||
| 			return err; | ||||
| 	} | ||||
|  | @ -4815,6 +4826,18 @@ static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn | |||
| 		regs[BPF_REG_0].type = PTR_TO_MEM_OR_NULL; | ||||
| 		regs[BPF_REG_0].id = ++env->id_gen; | ||||
| 		regs[BPF_REG_0].mem_size = meta.mem_size; | ||||
| 	} else if (fn->ret_type == RET_PTR_TO_BTF_ID_OR_NULL) { | ||||
| 		int ret_btf_id; | ||||
| 
 | ||||
| 		mark_reg_known_zero(env, regs, BPF_REG_0); | ||||
| 		regs[BPF_REG_0].type = PTR_TO_BTF_ID_OR_NULL; | ||||
| 		ret_btf_id = *fn->ret_btf_id; | ||||
| 		if (ret_btf_id == 0) { | ||||
| 			verbose(env, "invalid return type %d of func %s#%d\n", | ||||
| 				fn->ret_type, func_id_name(func_id), func_id); | ||||
| 			return -EINVAL; | ||||
| 		} | ||||
| 		regs[BPF_REG_0].btf_id = ret_btf_id; | ||||
| 	} else { | ||||
| 		verbose(env, "unknown return type %d of func %s#%d\n", | ||||
| 			fn->ret_type, func_id_name(func_id), func_id); | ||||
|  |  | |||
|  | @ -1515,6 +1515,8 @@ tracing_prog_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) | |||
| 		return &bpf_skb_output_proto; | ||||
| 	case BPF_FUNC_xdp_output: | ||||
| 		return &bpf_xdp_output_proto; | ||||
| 	case BPF_FUNC_skc_to_tcp6_sock: | ||||
| 		return &bpf_skc_to_tcp6_sock_proto; | ||||
| #endif | ||||
| 	case BPF_FUNC_seq_printf: | ||||
| 		return prog->expected_attach_type == BPF_TRACE_ITER ? | ||||
|  |  | |||
|  | @ -47,6 +47,7 @@ | |||
| #include <linux/seccomp.h> | ||||
| #include <linux/if_vlan.h> | ||||
| #include <linux/bpf.h> | ||||
| #include <linux/btf.h> | ||||
| #include <net/sch_generic.h> | ||||
| #include <net/cls_cgroup.h> | ||||
| #include <net/dst_metadata.h> | ||||
|  | @ -9225,3 +9226,84 @@ void bpf_prog_change_xdp(struct bpf_prog *prev_prog, struct bpf_prog *prog) | |||
| { | ||||
| 	bpf_dispatcher_change_prog(BPF_DISPATCHER_PTR(xdp), prev_prog, prog); | ||||
| } | ||||
| 
 | ||||
| /* Define a list of socket types which can be the argument for
 | ||||
|  * skc_to_*_sock() helpers. All these sockets should have | ||||
|  * sock_common as the first argument in its memory layout. | ||||
|  */ | ||||
| #define BTF_SOCK_TYPE_xxx \ | ||||
| 	BTF_SOCK_TYPE(BTF_SOCK_TYPE_INET, "inet_sock")			\ | ||||
| 	BTF_SOCK_TYPE(BTF_SOCK_TYPE_INET_CONN, "inet_connection_sock")	\ | ||||
| 	BTF_SOCK_TYPE(BTF_SOCK_TYPE_INET_REQ, "inet_request_sock")	\ | ||||
| 	BTF_SOCK_TYPE(BTF_SOCK_TYPE_INET_TW, "inet_timewait_sock")	\ | ||||
| 	BTF_SOCK_TYPE(BTF_SOCK_TYPE_REQ, "request_sock")		\ | ||||
| 	BTF_SOCK_TYPE(BTF_SOCK_TYPE_SOCK, "sock")			\ | ||||
| 	BTF_SOCK_TYPE(BTF_SOCK_TYPE_SOCK_COMMON, "sock_common")		\ | ||||
| 	BTF_SOCK_TYPE(BTF_SOCK_TYPE_TCP, "tcp_sock")			\ | ||||
| 	BTF_SOCK_TYPE(BTF_SOCK_TYPE_TCP_REQ, "tcp_request_sock")	\ | ||||
| 	BTF_SOCK_TYPE(BTF_SOCK_TYPE_TCP_TW, "tcp_timewait_sock")	\ | ||||
| 	BTF_SOCK_TYPE(BTF_SOCK_TYPE_TCP6, "tcp6_sock")			\ | ||||
| 	BTF_SOCK_TYPE(BTF_SOCK_TYPE_UDP, "udp_sock")			\ | ||||
| 	BTF_SOCK_TYPE(BTF_SOCK_TYPE_UDP6, "udp6_sock") | ||||
| 
 | ||||
| enum { | ||||
| #define BTF_SOCK_TYPE(name, str) name, | ||||
| BTF_SOCK_TYPE_xxx | ||||
| #undef BTF_SOCK_TYPE | ||||
| MAX_BTF_SOCK_TYPE, | ||||
| }; | ||||
| 
 | ||||
| static int btf_sock_ids[MAX_BTF_SOCK_TYPE]; | ||||
| 
 | ||||
| #ifdef CONFIG_BPF_SYSCALL | ||||
| static const char *bpf_sock_types[] = { | ||||
| #define BTF_SOCK_TYPE(name, str) str, | ||||
| BTF_SOCK_TYPE_xxx | ||||
| #undef BTF_SOCK_TYPE | ||||
| }; | ||||
| 
 | ||||
| void init_btf_sock_ids(struct btf *btf) | ||||
| { | ||||
| 	int i, btf_id; | ||||
| 
 | ||||
| 	for (i = 0; i < MAX_BTF_SOCK_TYPE; i++) { | ||||
| 		btf_id = btf_find_by_name_kind(btf, bpf_sock_types[i], | ||||
| 					       BTF_KIND_STRUCT); | ||||
| 		if (btf_id > 0) | ||||
| 			btf_sock_ids[i] = btf_id; | ||||
| 	} | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| static bool check_arg_btf_id(u32 btf_id, u32 arg) | ||||
| { | ||||
| 	int i; | ||||
| 
 | ||||
| 	/* only one argument, no need to check arg */ | ||||
| 	for (i = 0; i < MAX_BTF_SOCK_TYPE; i++) | ||||
| 		if (btf_sock_ids[i] == btf_id) | ||||
| 			return true; | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| BPF_CALL_1(bpf_skc_to_tcp6_sock, struct sock *, sk) | ||||
| { | ||||
| 	/* tcp6_sock type is not generated in dwarf and hence btf,
 | ||||
| 	 * trigger an explicit type generation here. | ||||
| 	 */ | ||||
| 	BTF_TYPE_EMIT(struct tcp6_sock); | ||||
| 	if (sk_fullsock(sk) && sk->sk_protocol == IPPROTO_TCP && | ||||
| 	    sk->sk_family == AF_INET6) | ||||
| 		return (unsigned long)sk; | ||||
| 
 | ||||
| 	return (unsigned long)NULL; | ||||
| } | ||||
| 
 | ||||
| const struct bpf_func_proto bpf_skc_to_tcp6_sock_proto = { | ||||
| 	.func			= bpf_skc_to_tcp6_sock, | ||||
| 	.gpl_only		= false, | ||||
| 	.ret_type		= RET_PTR_TO_BTF_ID_OR_NULL, | ||||
| 	.arg1_type		= ARG_PTR_TO_BTF_ID, | ||||
| 	.check_btf_id		= check_arg_btf_id, | ||||
| 	.ret_btf_id		= &btf_sock_ids[BTF_SOCK_TYPE_TCP6], | ||||
| }; | ||||
|  |  | |||
|  | @ -421,6 +421,7 @@ class PrinterHelpers(Printer): | |||
|             'struct sockaddr', | ||||
|             'struct tcphdr', | ||||
|             'struct seq_file', | ||||
|             'struct tcp6_sock', | ||||
| 
 | ||||
|             'struct __sk_buff', | ||||
|             'struct sk_msg_md', | ||||
|  | @ -458,6 +459,7 @@ class PrinterHelpers(Printer): | |||
|             'struct sockaddr', | ||||
|             'struct tcphdr', | ||||
|             'struct seq_file', | ||||
|             'struct tcp6_sock', | ||||
|     } | ||||
|     mapped_types = { | ||||
|             'u8': '__u8', | ||||
|  |  | |||
|  | @ -3255,6 +3255,12 @@ union bpf_attr { | |||
|  * 		case of **BPF_CSUM_LEVEL_QUERY**, the current skb->csum_level | ||||
|  * 		is returned or the error code -EACCES in case the skb is not | ||||
|  * 		subject to CHECKSUM_UNNECESSARY. | ||||
|  * | ||||
|  * struct tcp6_sock *bpf_skc_to_tcp6_sock(void *sk) | ||||
|  *	Description | ||||
|  *		Dynamically cast a *sk* pointer to a *tcp6_sock* pointer. | ||||
|  *	Return | ||||
|  *		*sk* if casting is valid, or NULL otherwise. | ||||
|  */ | ||||
| #define __BPF_FUNC_MAPPER(FN)		\ | ||||
| 	FN(unspec),			\ | ||||
|  | @ -3392,7 +3398,8 @@ union bpf_attr { | |||
| 	FN(ringbuf_submit),		\ | ||||
| 	FN(ringbuf_discard),		\ | ||||
| 	FN(ringbuf_query),		\ | ||||
| 	FN(csum_level), | ||||
| 	FN(csum_level),			\ | ||||
| 	FN(skc_to_tcp6_sock), | ||||
| 
 | ||||
| /* integer value in 'imm' field of BPF_CALL instruction selects which helper
 | ||||
|  * function eBPF program intends to call | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Yonghong Song
						Yonghong Song