mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	bpf: Verifier track null pointer branch_taken with JNE and JEQ
Currently, when considering the branches that may be taken for a jump
instruction if the register being compared is a pointer the verifier
assumes both branches may be taken. But, if the jump instruction
is comparing if a pointer is NULL we have this information in the
verifier encoded in the reg->type so we can do better in these cases.
Specifically, these two common cases can be handled.
 * If the instruction is BPF_JEQ and we are comparing against a
   zero value. This test is 'if ptr == 0 goto +X' then using the
   type information in reg->type we can decide if the ptr is not
   null. This allows us to avoid pushing both branches onto the
   stack and instead only use the != 0 case. For example
   PTR_TO_SOCK and PTR_TO_SOCK_OR_NULL encode the null pointer.
   Note if the type is PTR_TO_SOCK_OR_NULL we can not learn anything.
   And also if the value is non-zero we learn nothing because it
   could be any arbitrary value a different pointer for example
 * If the instruction is BPF_JNE and ware comparing against a zero
   value then a similar analysis as above can be done. The test in
   asm looks like 'if ptr != 0 goto +X'. Again using the type
   information if the non null type is set (from above PTR_TO_SOCK)
   we know the jump is taken.
In this patch we extend is_branch_taken() to consider this extra
information and to return only the branch that will be taken. This
resolves a verifier issue reported with C code like the following.
See progs/test_sk_lookup_kern.c in selftests.
 sk = bpf_sk_lookup_tcp(skb, tuple, tuple_len, BPF_F_CURRENT_NETNS, 0);
 bpf_printk("sk=%d\n", sk ? 1 : 0);
 if (sk)
   bpf_sk_release(sk);
 return sk ? TC_ACT_OK : TC_ACT_UNSPEC;
In the above the bpf_printk() will resolve the pointer from
PTR_TO_SOCK_OR_NULL to PTR_TO_SOCK. Then the second test guarding
the release will cause the verifier to walk both paths resulting
in the an unreleased sock reference. See verifier/ref_tracking.c
in selftests for an assembly version of the above.
After the above additional logic is added the C code above passes
as expected.
Reported-by: Andrey Ignatov <rdna@fb.com>
Suggested-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: John Fastabend <john.fastabend@gmail.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/bpf/159009164651.6313.380418298578070501.stgit@john-Precision-5820-Tower
			
			
This commit is contained in:
		
							parent
							
								
									79917b242c
								
							
						
					
					
						commit
						cac616db39
					
				
					 1 changed files with 33 additions and 3 deletions
				
			
		| 
						 | 
					@ -393,6 +393,15 @@ static bool type_is_sk_pointer(enum bpf_reg_type type)
 | 
				
			||||||
		type == PTR_TO_XDP_SOCK;
 | 
							type == PTR_TO_XDP_SOCK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool reg_type_not_null(enum bpf_reg_type type)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return type == PTR_TO_SOCKET ||
 | 
				
			||||||
 | 
							type == PTR_TO_TCP_SOCK ||
 | 
				
			||||||
 | 
							type == PTR_TO_MAP_VALUE ||
 | 
				
			||||||
 | 
							type == PTR_TO_SOCK_COMMON ||
 | 
				
			||||||
 | 
						        type == PTR_TO_BTF_ID;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool reg_type_may_be_null(enum bpf_reg_type type)
 | 
					static bool reg_type_may_be_null(enum bpf_reg_type type)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return type == PTR_TO_MAP_VALUE_OR_NULL ||
 | 
						return type == PTR_TO_MAP_VALUE_OR_NULL ||
 | 
				
			||||||
| 
						 | 
					@ -6308,9 +6317,26 @@ static int is_branch64_taken(struct bpf_reg_state *reg, u64 val, u8 opcode)
 | 
				
			||||||
static int is_branch_taken(struct bpf_reg_state *reg, u64 val, u8 opcode,
 | 
					static int is_branch_taken(struct bpf_reg_state *reg, u64 val, u8 opcode,
 | 
				
			||||||
			   bool is_jmp32)
 | 
								   bool is_jmp32)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	if (__is_pointer_value(false, reg))
 | 
						if (__is_pointer_value(false, reg)) {
 | 
				
			||||||
 | 
							if (!reg_type_not_null(reg->type))
 | 
				
			||||||
			return -1;
 | 
								return -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/* If pointer is valid tests against zero will fail so we can
 | 
				
			||||||
 | 
							 * use this to direct branch taken.
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							if (val != 0)
 | 
				
			||||||
 | 
								return -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							switch (opcode) {
 | 
				
			||||||
 | 
							case BPF_JEQ:
 | 
				
			||||||
 | 
								return 0;
 | 
				
			||||||
 | 
							case BPF_JNE:
 | 
				
			||||||
 | 
								return 1;
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								return -1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (is_jmp32)
 | 
						if (is_jmp32)
 | 
				
			||||||
		return is_branch32_taken(reg, val, opcode);
 | 
							return is_branch32_taken(reg, val, opcode);
 | 
				
			||||||
	return is_branch64_taken(reg, val, opcode);
 | 
						return is_branch64_taken(reg, val, opcode);
 | 
				
			||||||
| 
						 | 
					@ -6808,6 +6834,10 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (pred >= 0) {
 | 
						if (pred >= 0) {
 | 
				
			||||||
 | 
							/* If we get here with a dst_reg pointer type it is because
 | 
				
			||||||
 | 
							 * above is_branch_taken() special cased the 0 comparison.
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							if (!__is_pointer_value(false, dst_reg))
 | 
				
			||||||
			err = mark_chain_precision(env, insn->dst_reg);
 | 
								err = mark_chain_precision(env, insn->dst_reg);
 | 
				
			||||||
		if (BPF_SRC(insn->code) == BPF_X && !err)
 | 
							if (BPF_SRC(insn->code) == BPF_X && !err)
 | 
				
			||||||
			err = mark_chain_precision(env, insn->src_reg);
 | 
								err = mark_chain_precision(env, insn->src_reg);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue