bpf: do not allow root to mangle valid pointers
Do not allow root to convert valid pointers into unknown scalars.
In particular disallow:
ptr &= reg
ptr <<= reg
ptr += ptr
and explicitly allow:
ptr -= ptr
since pkt_end - pkt == length
1.
This minimizes amount of address leaks root can do.
In the future may need to further tighten the leaks with kptr_restrict.
2.
If program has such pointer math it's likely a user mistake and
when verifier complains about it right away instead of many instructions
later on invalid memory access it's easier for users to fix their progs.
3.
when register holding a pointer cannot change to scalar it allows JITs to
optimize better. Like 32-bit archs could use single register for pointers
instead of a pair required to hold 64-bit scalars.
4.
reduces architecture dependent behavior. Since code:
r1 = r10;
r1 &= 0xff;
if (r1 ...)
will behave differently arm64 vs x64 and offloaded vs native.
A significant chunk of ptr mangling was allowed by
commit f1174f77b5
("bpf/verifier: rework value tracking")
yet some of it was allowed even earlier.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
This commit is contained in:
parent
3db9128fcf
commit
82abbf8d2f
@ -1890,29 +1890,25 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
|
||||
|
||||
if (BPF_CLASS(insn->code) != BPF_ALU64) {
|
||||
/* 32-bit ALU ops on pointers produce (meaningless) scalars */
|
||||
if (!env->allow_ptr_leaks)
|
||||
verbose(env,
|
||||
"R%d 32-bit pointer arithmetic prohibited\n",
|
||||
dst);
|
||||
verbose(env,
|
||||
"R%d 32-bit pointer arithmetic prohibited\n",
|
||||
dst);
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
if (ptr_reg->type == PTR_TO_MAP_VALUE_OR_NULL) {
|
||||
if (!env->allow_ptr_leaks)
|
||||
verbose(env, "R%d pointer arithmetic on PTR_TO_MAP_VALUE_OR_NULL prohibited, null-check it first\n",
|
||||
dst);
|
||||
verbose(env, "R%d pointer arithmetic on PTR_TO_MAP_VALUE_OR_NULL prohibited, null-check it first\n",
|
||||
dst);
|
||||
return -EACCES;
|
||||
}
|
||||
if (ptr_reg->type == CONST_PTR_TO_MAP) {
|
||||
if (!env->allow_ptr_leaks)
|
||||
verbose(env, "R%d pointer arithmetic on CONST_PTR_TO_MAP prohibited\n",
|
||||
dst);
|
||||
verbose(env, "R%d pointer arithmetic on CONST_PTR_TO_MAP prohibited\n",
|
||||
dst);
|
||||
return -EACCES;
|
||||
}
|
||||
if (ptr_reg->type == PTR_TO_PACKET_END) {
|
||||
if (!env->allow_ptr_leaks)
|
||||
verbose(env, "R%d pointer arithmetic on PTR_TO_PACKET_END prohibited\n",
|
||||
dst);
|
||||
verbose(env, "R%d pointer arithmetic on PTR_TO_PACKET_END prohibited\n",
|
||||
dst);
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
@ -1979,9 +1975,8 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
|
||||
case BPF_SUB:
|
||||
if (dst_reg == off_reg) {
|
||||
/* scalar -= pointer. Creates an unknown scalar */
|
||||
if (!env->allow_ptr_leaks)
|
||||
verbose(env, "R%d tried to subtract pointer from scalar\n",
|
||||
dst);
|
||||
verbose(env, "R%d tried to subtract pointer from scalar\n",
|
||||
dst);
|
||||
return -EACCES;
|
||||
}
|
||||
/* We don't allow subtraction from FP, because (according to
|
||||
@ -1989,9 +1984,8 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
|
||||
* be able to deal with it.
|
||||
*/
|
||||
if (ptr_reg->type == PTR_TO_STACK) {
|
||||
if (!env->allow_ptr_leaks)
|
||||
verbose(env, "R%d subtraction from stack pointer prohibited\n",
|
||||
dst);
|
||||
verbose(env, "R%d subtraction from stack pointer prohibited\n",
|
||||
dst);
|
||||
return -EACCES;
|
||||
}
|
||||
if (known && (ptr_reg->off - smin_val ==
|
||||
@ -2040,19 +2034,14 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
|
||||
case BPF_AND:
|
||||
case BPF_OR:
|
||||
case BPF_XOR:
|
||||
/* bitwise ops on pointers are troublesome, prohibit for now.
|
||||
* (However, in principle we could allow some cases, e.g.
|
||||
* ptr &= ~3 which would reduce min_value by 3.)
|
||||
*/
|
||||
if (!env->allow_ptr_leaks)
|
||||
verbose(env, "R%d bitwise operator %s on pointer prohibited\n",
|
||||
dst, bpf_alu_string[opcode >> 4]);
|
||||
/* bitwise ops on pointers are troublesome, prohibit. */
|
||||
verbose(env, "R%d bitwise operator %s on pointer prohibited\n",
|
||||
dst, bpf_alu_string[opcode >> 4]);
|
||||
return -EACCES;
|
||||
default:
|
||||
/* other operators (e.g. MUL,LSH) produce non-pointer results */
|
||||
if (!env->allow_ptr_leaks)
|
||||
verbose(env, "R%d pointer arithmetic with %s operator prohibited\n",
|
||||
dst, bpf_alu_string[opcode >> 4]);
|
||||
verbose(env, "R%d pointer arithmetic with %s operator prohibited\n",
|
||||
dst, bpf_alu_string[opcode >> 4]);
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
@ -2308,7 +2297,6 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
|
||||
struct bpf_reg_state *regs = cur_regs(env), *dst_reg, *src_reg;
|
||||
struct bpf_reg_state *ptr_reg = NULL, off_reg = {0};
|
||||
u8 opcode = BPF_OP(insn->code);
|
||||
int rc;
|
||||
|
||||
dst_reg = ®s[insn->dst_reg];
|
||||
src_reg = NULL;
|
||||
@ -2319,43 +2307,29 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
|
||||
if (src_reg->type != SCALAR_VALUE) {
|
||||
if (dst_reg->type != SCALAR_VALUE) {
|
||||
/* Combining two pointers by any ALU op yields
|
||||
* an arbitrary scalar.
|
||||
* an arbitrary scalar. Disallow all math except
|
||||
* pointer subtraction
|
||||
*/
|
||||
if (!env->allow_ptr_leaks) {
|
||||
verbose(env, "R%d pointer %s pointer prohibited\n",
|
||||
insn->dst_reg,
|
||||
bpf_alu_string[opcode >> 4]);
|
||||
return -EACCES;
|
||||
if (opcode == BPF_SUB){
|
||||
mark_reg_unknown(env, regs, insn->dst_reg);
|
||||
return 0;
|
||||
}
|
||||
mark_reg_unknown(env, regs, insn->dst_reg);
|
||||
return 0;
|
||||
verbose(env, "R%d pointer %s pointer prohibited\n",
|
||||
insn->dst_reg,
|
||||
bpf_alu_string[opcode >> 4]);
|
||||
return -EACCES;
|
||||
} else {
|
||||
/* scalar += pointer
|
||||
* This is legal, but we have to reverse our
|
||||
* src/dest handling in computing the range
|
||||
*/
|
||||
rc = adjust_ptr_min_max_vals(env, insn,
|
||||
src_reg, dst_reg);
|
||||
if (rc == -EACCES && env->allow_ptr_leaks) {
|
||||
/* scalar += unknown scalar */
|
||||
__mark_reg_unknown(&off_reg);
|
||||
return adjust_scalar_min_max_vals(
|
||||
env, insn,
|
||||
dst_reg, off_reg);
|
||||
}
|
||||
return rc;
|
||||
return adjust_ptr_min_max_vals(env, insn,
|
||||
src_reg, dst_reg);
|
||||
}
|
||||
} else if (ptr_reg) {
|
||||
/* pointer += scalar */
|
||||
rc = adjust_ptr_min_max_vals(env, insn,
|
||||
dst_reg, src_reg);
|
||||
if (rc == -EACCES && env->allow_ptr_leaks) {
|
||||
/* unknown scalar += scalar */
|
||||
__mark_reg_unknown(dst_reg);
|
||||
return adjust_scalar_min_max_vals(
|
||||
env, insn, dst_reg, *src_reg);
|
||||
}
|
||||
return rc;
|
||||
return adjust_ptr_min_max_vals(env, insn,
|
||||
dst_reg, src_reg);
|
||||
}
|
||||
} else {
|
||||
/* Pretend the src is a reg with a known value, since we only
|
||||
@ -2364,17 +2338,9 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
|
||||
off_reg.type = SCALAR_VALUE;
|
||||
__mark_reg_known(&off_reg, insn->imm);
|
||||
src_reg = &off_reg;
|
||||
if (ptr_reg) { /* pointer += K */
|
||||
rc = adjust_ptr_min_max_vals(env, insn,
|
||||
ptr_reg, src_reg);
|
||||
if (rc == -EACCES && env->allow_ptr_leaks) {
|
||||
/* unknown scalar += K */
|
||||
__mark_reg_unknown(dst_reg);
|
||||
return adjust_scalar_min_max_vals(
|
||||
env, insn, dst_reg, off_reg);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
if (ptr_reg) /* pointer += K */
|
||||
return adjust_ptr_min_max_vals(env, insn,
|
||||
ptr_reg, src_reg);
|
||||
}
|
||||
|
||||
/* Got here implies adding two SCALAR_VALUEs */
|
||||
|
@ -422,9 +422,7 @@ static struct bpf_test tests[] = {
|
||||
BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_0, 0),
|
||||
BPF_EXIT_INSN(),
|
||||
},
|
||||
.errstr_unpriv = "R1 subtraction from stack pointer",
|
||||
.result_unpriv = REJECT,
|
||||
.errstr = "R1 invalid mem access",
|
||||
.errstr = "R1 subtraction from stack pointer",
|
||||
.result = REJECT,
|
||||
},
|
||||
{
|
||||
@ -1859,9 +1857,8 @@ static struct bpf_test tests[] = {
|
||||
BPF_MOV64_IMM(BPF_REG_0, 0),
|
||||
BPF_EXIT_INSN(),
|
||||
},
|
||||
.result = ACCEPT,
|
||||
.result_unpriv = REJECT,
|
||||
.errstr_unpriv = "R1 pointer += pointer",
|
||||
.result = REJECT,
|
||||
.errstr = "R1 pointer += pointer",
|
||||
},
|
||||
{
|
||||
"unpriv: neg pointer",
|
||||
@ -2589,7 +2586,8 @@ static struct bpf_test tests[] = {
|
||||
BPF_LDX_MEM(BPF_W, BPF_REG_3, BPF_REG_1,
|
||||
offsetof(struct __sk_buff, data)),
|
||||
BPF_ALU64_REG(BPF_ADD, BPF_REG_3, BPF_REG_4),
|
||||
BPF_MOV64_REG(BPF_REG_2, BPF_REG_1),
|
||||
BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1,
|
||||
offsetof(struct __sk_buff, len)),
|
||||
BPF_ALU64_IMM(BPF_LSH, BPF_REG_2, 49),
|
||||
BPF_ALU64_IMM(BPF_RSH, BPF_REG_2, 49),
|
||||
BPF_ALU64_REG(BPF_ADD, BPF_REG_3, BPF_REG_2),
|
||||
@ -2896,7 +2894,7 @@ static struct bpf_test tests[] = {
|
||||
BPF_MOV64_IMM(BPF_REG_0, 0),
|
||||
BPF_EXIT_INSN(),
|
||||
},
|
||||
.errstr = "invalid access to packet",
|
||||
.errstr = "R3 pointer arithmetic on PTR_TO_PACKET_END",
|
||||
.result = REJECT,
|
||||
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
|
||||
},
|
||||
@ -3882,9 +3880,7 @@ static struct bpf_test tests[] = {
|
||||
BPF_EXIT_INSN(),
|
||||
},
|
||||
.fixup_map2 = { 3, 11 },
|
||||
.errstr_unpriv = "R0 pointer += pointer",
|
||||
.errstr = "R0 invalid mem access 'inv'",
|
||||
.result_unpriv = REJECT,
|
||||
.errstr = "R0 pointer += pointer",
|
||||
.result = REJECT,
|
||||
.flags = F_NEEDS_EFFICIENT_UNALIGNED_ACCESS,
|
||||
},
|
||||
@ -3925,7 +3921,7 @@ static struct bpf_test tests[] = {
|
||||
BPF_EXIT_INSN(),
|
||||
},
|
||||
.fixup_map1 = { 4 },
|
||||
.errstr = "R4 invalid mem access",
|
||||
.errstr = "R4 pointer arithmetic on PTR_TO_MAP_VALUE_OR_NULL",
|
||||
.result = REJECT,
|
||||
.prog_type = BPF_PROG_TYPE_SCHED_CLS
|
||||
},
|
||||
@ -3946,7 +3942,7 @@ static struct bpf_test tests[] = {
|
||||
BPF_EXIT_INSN(),
|
||||
},
|
||||
.fixup_map1 = { 4 },
|
||||
.errstr = "R4 invalid mem access",
|
||||
.errstr = "R4 pointer arithmetic on PTR_TO_MAP_VALUE_OR_NULL",
|
||||
.result = REJECT,
|
||||
.prog_type = BPF_PROG_TYPE_SCHED_CLS
|
||||
},
|
||||
@ -3967,7 +3963,7 @@ static struct bpf_test tests[] = {
|
||||
BPF_EXIT_INSN(),
|
||||
},
|
||||
.fixup_map1 = { 4 },
|
||||
.errstr = "R4 invalid mem access",
|
||||
.errstr = "R4 pointer arithmetic on PTR_TO_MAP_VALUE_OR_NULL",
|
||||
.result = REJECT,
|
||||
.prog_type = BPF_PROG_TYPE_SCHED_CLS
|
||||
},
|
||||
@ -5192,10 +5188,8 @@ static struct bpf_test tests[] = {
|
||||
BPF_EXIT_INSN(),
|
||||
},
|
||||
.fixup_map2 = { 3 },
|
||||
.errstr_unpriv = "R0 bitwise operator &= on pointer",
|
||||
.errstr = "invalid mem access 'inv'",
|
||||
.errstr = "R0 bitwise operator &= on pointer",
|
||||
.result = REJECT,
|
||||
.result_unpriv = REJECT,
|
||||
},
|
||||
{
|
||||
"map element value illegal alu op, 2",
|
||||
@ -5211,10 +5205,8 @@ static struct bpf_test tests[] = {
|
||||
BPF_EXIT_INSN(),
|
||||
},
|
||||
.fixup_map2 = { 3 },
|
||||
.errstr_unpriv = "R0 32-bit pointer arithmetic prohibited",
|
||||
.errstr = "invalid mem access 'inv'",
|
||||
.errstr = "R0 32-bit pointer arithmetic prohibited",
|
||||
.result = REJECT,
|
||||
.result_unpriv = REJECT,
|
||||
},
|
||||
{
|
||||
"map element value illegal alu op, 3",
|
||||
@ -5230,10 +5222,8 @@ static struct bpf_test tests[] = {
|
||||
BPF_EXIT_INSN(),
|
||||
},
|
||||
.fixup_map2 = { 3 },
|
||||
.errstr_unpriv = "R0 pointer arithmetic with /= operator",
|
||||
.errstr = "invalid mem access 'inv'",
|
||||
.errstr = "R0 pointer arithmetic with /= operator",
|
||||
.result = REJECT,
|
||||
.result_unpriv = REJECT,
|
||||
},
|
||||
{
|
||||
"map element value illegal alu op, 4",
|
||||
@ -6016,8 +6006,7 @@ static struct bpf_test tests[] = {
|
||||
BPF_EXIT_INSN(),
|
||||
},
|
||||
.fixup_map_in_map = { 3 },
|
||||
.errstr = "R1 type=inv expected=map_ptr",
|
||||
.errstr_unpriv = "R1 pointer arithmetic on CONST_PTR_TO_MAP prohibited",
|
||||
.errstr = "R1 pointer arithmetic on CONST_PTR_TO_MAP prohibited",
|
||||
.result = REJECT,
|
||||
},
|
||||
{
|
||||
@ -7644,6 +7633,19 @@ static struct bpf_test tests[] = {
|
||||
.result = REJECT,
|
||||
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
|
||||
},
|
||||
{
|
||||
"pkt_end - pkt_start is allowed",
|
||||
.insns = {
|
||||
BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_1,
|
||||
offsetof(struct __sk_buff, data_end)),
|
||||
BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1,
|
||||
offsetof(struct __sk_buff, data)),
|
||||
BPF_ALU64_REG(BPF_SUB, BPF_REG_0, BPF_REG_2),
|
||||
BPF_EXIT_INSN(),
|
||||
},
|
||||
.result = ACCEPT,
|
||||
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
|
||||
},
|
||||
{
|
||||
"XDP pkt read, pkt_end mangling, bad access 1",
|
||||
.insns = {
|
||||
@ -7659,7 +7661,7 @@ static struct bpf_test tests[] = {
|
||||
BPF_MOV64_IMM(BPF_REG_0, 0),
|
||||
BPF_EXIT_INSN(),
|
||||
},
|
||||
.errstr = "R1 offset is outside of the packet",
|
||||
.errstr = "R3 pointer arithmetic on PTR_TO_PACKET_END",
|
||||
.result = REJECT,
|
||||
.prog_type = BPF_PROG_TYPE_XDP,
|
||||
},
|
||||
@ -7678,7 +7680,7 @@ static struct bpf_test tests[] = {
|
||||
BPF_MOV64_IMM(BPF_REG_0, 0),
|
||||
BPF_EXIT_INSN(),
|
||||
},
|
||||
.errstr = "R1 offset is outside of the packet",
|
||||
.errstr = "R3 pointer arithmetic on PTR_TO_PACKET_END",
|
||||
.result = REJECT,
|
||||
.prog_type = BPF_PROG_TYPE_XDP,
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user