Commit 82abbf8d authored by Alexei Starovoitov's avatar Alexei Starovoitov Committed by Daniel Borkmann

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 f1174f77 ("bpf/verifier: rework value tracking")
yet some of it was allowed even earlier.
Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
Signed-off-by: default avatarDaniel Borkmann <daniel@iogearbox.net>
parent 3db9128f
...@@ -1890,29 +1890,25 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, ...@@ -1890,29 +1890,25 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
if (BPF_CLASS(insn->code) != BPF_ALU64) { if (BPF_CLASS(insn->code) != BPF_ALU64) {
/* 32-bit ALU ops on pointers produce (meaningless) scalars */ /* 32-bit ALU ops on pointers produce (meaningless) scalars */
if (!env->allow_ptr_leaks) verbose(env,
verbose(env, "R%d 32-bit pointer arithmetic prohibited\n",
"R%d 32-bit pointer arithmetic prohibited\n", dst);
dst);
return -EACCES; return -EACCES;
} }
if (ptr_reg->type == PTR_TO_MAP_VALUE_OR_NULL) { 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",
verbose(env, "R%d pointer arithmetic on PTR_TO_MAP_VALUE_OR_NULL prohibited, null-check it first\n", dst);
dst);
return -EACCES; return -EACCES;
} }
if (ptr_reg->type == CONST_PTR_TO_MAP) { 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",
verbose(env, "R%d pointer arithmetic on CONST_PTR_TO_MAP prohibited\n", dst);
dst);
return -EACCES; return -EACCES;
} }
if (ptr_reg->type == PTR_TO_PACKET_END) { 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",
verbose(env, "R%d pointer arithmetic on PTR_TO_PACKET_END prohibited\n", dst);
dst);
return -EACCES; return -EACCES;
} }
...@@ -1979,9 +1975,8 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, ...@@ -1979,9 +1975,8 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
case BPF_SUB: case BPF_SUB:
if (dst_reg == off_reg) { if (dst_reg == off_reg) {
/* scalar -= pointer. Creates an unknown scalar */ /* scalar -= pointer. Creates an unknown scalar */
if (!env->allow_ptr_leaks) verbose(env, "R%d tried to subtract pointer from scalar\n",
verbose(env, "R%d tried to subtract pointer from scalar\n", dst);
dst);
return -EACCES; return -EACCES;
} }
/* We don't allow subtraction from FP, because (according to /* 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, ...@@ -1989,9 +1984,8 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
* be able to deal with it. * be able to deal with it.
*/ */
if (ptr_reg->type == PTR_TO_STACK) { if (ptr_reg->type == PTR_TO_STACK) {
if (!env->allow_ptr_leaks) verbose(env, "R%d subtraction from stack pointer prohibited\n",
verbose(env, "R%d subtraction from stack pointer prohibited\n", dst);
dst);
return -EACCES; return -EACCES;
} }
if (known && (ptr_reg->off - smin_val == if (known && (ptr_reg->off - smin_val ==
...@@ -2040,19 +2034,14 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, ...@@ -2040,19 +2034,14 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
case BPF_AND: case BPF_AND:
case BPF_OR: case BPF_OR:
case BPF_XOR: case BPF_XOR:
/* bitwise ops on pointers are troublesome, prohibit for now. /* bitwise ops on pointers are troublesome, prohibit. */
* (However, in principle we could allow some cases, e.g. verbose(env, "R%d bitwise operator %s on pointer prohibited\n",
* ptr &= ~3 which would reduce min_value by 3.) dst, bpf_alu_string[opcode >> 4]);
*/
if (!env->allow_ptr_leaks)
verbose(env, "R%d bitwise operator %s on pointer prohibited\n",
dst, bpf_alu_string[opcode >> 4]);
return -EACCES; return -EACCES;
default: default:
/* other operators (e.g. MUL,LSH) produce non-pointer results */ /* 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",
verbose(env, "R%d pointer arithmetic with %s operator prohibited\n", dst, bpf_alu_string[opcode >> 4]);
dst, bpf_alu_string[opcode >> 4]);
return -EACCES; return -EACCES;
} }
...@@ -2308,7 +2297,6 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env, ...@@ -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 *regs = cur_regs(env), *dst_reg, *src_reg;
struct bpf_reg_state *ptr_reg = NULL, off_reg = {0}; struct bpf_reg_state *ptr_reg = NULL, off_reg = {0};
u8 opcode = BPF_OP(insn->code); u8 opcode = BPF_OP(insn->code);
int rc;
dst_reg = &regs[insn->dst_reg]; dst_reg = &regs[insn->dst_reg];
src_reg = NULL; src_reg = NULL;
...@@ -2319,43 +2307,29 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env, ...@@ -2319,43 +2307,29 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
if (src_reg->type != SCALAR_VALUE) { if (src_reg->type != SCALAR_VALUE) {
if (dst_reg->type != SCALAR_VALUE) { if (dst_reg->type != SCALAR_VALUE) {
/* Combining two pointers by any ALU op yields /* 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) { if (opcode == BPF_SUB){
verbose(env, "R%d pointer %s pointer prohibited\n", mark_reg_unknown(env, regs, insn->dst_reg);
insn->dst_reg, return 0;
bpf_alu_string[opcode >> 4]);
return -EACCES;
} }
mark_reg_unknown(env, regs, insn->dst_reg); verbose(env, "R%d pointer %s pointer prohibited\n",
return 0; insn->dst_reg,
bpf_alu_string[opcode >> 4]);
return -EACCES;
} else { } else {
/* scalar += pointer /* scalar += pointer
* This is legal, but we have to reverse our * This is legal, but we have to reverse our
* src/dest handling in computing the range * src/dest handling in computing the range
*/ */
rc = adjust_ptr_min_max_vals(env, insn, return adjust_ptr_min_max_vals(env, insn,
src_reg, dst_reg); 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;
} }
} else if (ptr_reg) { } else if (ptr_reg) {
/* pointer += scalar */ /* pointer += scalar */
rc = adjust_ptr_min_max_vals(env, insn, return adjust_ptr_min_max_vals(env, insn,
dst_reg, src_reg); 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;
} }
} else { } else {
/* Pretend the src is a reg with a known value, since we only /* 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, ...@@ -2364,17 +2338,9 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
off_reg.type = SCALAR_VALUE; off_reg.type = SCALAR_VALUE;
__mark_reg_known(&off_reg, insn->imm); __mark_reg_known(&off_reg, insn->imm);
src_reg = &off_reg; src_reg = &off_reg;
if (ptr_reg) { /* pointer += K */ if (ptr_reg) /* pointer += K */
rc = adjust_ptr_min_max_vals(env, insn, return adjust_ptr_min_max_vals(env, insn,
ptr_reg, src_reg); 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;
}
} }
/* Got here implies adding two SCALAR_VALUEs */ /* Got here implies adding two SCALAR_VALUEs */
......
...@@ -422,9 +422,7 @@ static struct bpf_test tests[] = { ...@@ -422,9 +422,7 @@ static struct bpf_test tests[] = {
BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_0, 0), BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_0, 0),
BPF_EXIT_INSN(), BPF_EXIT_INSN(),
}, },
.errstr_unpriv = "R1 subtraction from stack pointer", .errstr = "R1 subtraction from stack pointer",
.result_unpriv = REJECT,
.errstr = "R1 invalid mem access",
.result = REJECT, .result = REJECT,
}, },
{ {
...@@ -1859,9 +1857,8 @@ static struct bpf_test tests[] = { ...@@ -1859,9 +1857,8 @@ static struct bpf_test tests[] = {
BPF_MOV64_IMM(BPF_REG_0, 0), BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(), BPF_EXIT_INSN(),
}, },
.result = ACCEPT, .result = REJECT,
.result_unpriv = REJECT, .errstr = "R1 pointer += pointer",
.errstr_unpriv = "R1 pointer += pointer",
}, },
{ {
"unpriv: neg pointer", "unpriv: neg pointer",
...@@ -2589,7 +2586,8 @@ static struct bpf_test tests[] = { ...@@ -2589,7 +2586,8 @@ static struct bpf_test tests[] = {
BPF_LDX_MEM(BPF_W, BPF_REG_3, BPF_REG_1, BPF_LDX_MEM(BPF_W, BPF_REG_3, BPF_REG_1,
offsetof(struct __sk_buff, data)), offsetof(struct __sk_buff, data)),
BPF_ALU64_REG(BPF_ADD, BPF_REG_3, BPF_REG_4), 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_LSH, BPF_REG_2, 49),
BPF_ALU64_IMM(BPF_RSH, BPF_REG_2, 49), BPF_ALU64_IMM(BPF_RSH, BPF_REG_2, 49),
BPF_ALU64_REG(BPF_ADD, BPF_REG_3, BPF_REG_2), BPF_ALU64_REG(BPF_ADD, BPF_REG_3, BPF_REG_2),
...@@ -2896,7 +2894,7 @@ static struct bpf_test tests[] = { ...@@ -2896,7 +2894,7 @@ static struct bpf_test tests[] = {
BPF_MOV64_IMM(BPF_REG_0, 0), BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(), BPF_EXIT_INSN(),
}, },
.errstr = "invalid access to packet", .errstr = "R3 pointer arithmetic on PTR_TO_PACKET_END",
.result = REJECT, .result = REJECT,
.prog_type = BPF_PROG_TYPE_SCHED_CLS, .prog_type = BPF_PROG_TYPE_SCHED_CLS,
}, },
...@@ -3882,9 +3880,7 @@ static struct bpf_test tests[] = { ...@@ -3882,9 +3880,7 @@ static struct bpf_test tests[] = {
BPF_EXIT_INSN(), BPF_EXIT_INSN(),
}, },
.fixup_map2 = { 3, 11 }, .fixup_map2 = { 3, 11 },
.errstr_unpriv = "R0 pointer += pointer", .errstr = "R0 pointer += pointer",
.errstr = "R0 invalid mem access 'inv'",
.result_unpriv = REJECT,
.result = REJECT, .result = REJECT,
.flags = F_NEEDS_EFFICIENT_UNALIGNED_ACCESS, .flags = F_NEEDS_EFFICIENT_UNALIGNED_ACCESS,
}, },
...@@ -3925,7 +3921,7 @@ static struct bpf_test tests[] = { ...@@ -3925,7 +3921,7 @@ static struct bpf_test tests[] = {
BPF_EXIT_INSN(), BPF_EXIT_INSN(),
}, },
.fixup_map1 = { 4 }, .fixup_map1 = { 4 },
.errstr = "R4 invalid mem access", .errstr = "R4 pointer arithmetic on PTR_TO_MAP_VALUE_OR_NULL",
.result = REJECT, .result = REJECT,
.prog_type = BPF_PROG_TYPE_SCHED_CLS .prog_type = BPF_PROG_TYPE_SCHED_CLS
}, },
...@@ -3946,7 +3942,7 @@ static struct bpf_test tests[] = { ...@@ -3946,7 +3942,7 @@ static struct bpf_test tests[] = {
BPF_EXIT_INSN(), BPF_EXIT_INSN(),
}, },
.fixup_map1 = { 4 }, .fixup_map1 = { 4 },
.errstr = "R4 invalid mem access", .errstr = "R4 pointer arithmetic on PTR_TO_MAP_VALUE_OR_NULL",
.result = REJECT, .result = REJECT,
.prog_type = BPF_PROG_TYPE_SCHED_CLS .prog_type = BPF_PROG_TYPE_SCHED_CLS
}, },
...@@ -3967,7 +3963,7 @@ static struct bpf_test tests[] = { ...@@ -3967,7 +3963,7 @@ static struct bpf_test tests[] = {
BPF_EXIT_INSN(), BPF_EXIT_INSN(),
}, },
.fixup_map1 = { 4 }, .fixup_map1 = { 4 },
.errstr = "R4 invalid mem access", .errstr = "R4 pointer arithmetic on PTR_TO_MAP_VALUE_OR_NULL",
.result = REJECT, .result = REJECT,
.prog_type = BPF_PROG_TYPE_SCHED_CLS .prog_type = BPF_PROG_TYPE_SCHED_CLS
}, },
...@@ -5192,10 +5188,8 @@ static struct bpf_test tests[] = { ...@@ -5192,10 +5188,8 @@ static struct bpf_test tests[] = {
BPF_EXIT_INSN(), BPF_EXIT_INSN(),
}, },
.fixup_map2 = { 3 }, .fixup_map2 = { 3 },
.errstr_unpriv = "R0 bitwise operator &= on pointer", .errstr = "R0 bitwise operator &= on pointer",
.errstr = "invalid mem access 'inv'",
.result = REJECT, .result = REJECT,
.result_unpriv = REJECT,
}, },
{ {
"map element value illegal alu op, 2", "map element value illegal alu op, 2",
...@@ -5211,10 +5205,8 @@ static struct bpf_test tests[] = { ...@@ -5211,10 +5205,8 @@ static struct bpf_test tests[] = {
BPF_EXIT_INSN(), BPF_EXIT_INSN(),
}, },
.fixup_map2 = { 3 }, .fixup_map2 = { 3 },
.errstr_unpriv = "R0 32-bit pointer arithmetic prohibited", .errstr = "R0 32-bit pointer arithmetic prohibited",
.errstr = "invalid mem access 'inv'",
.result = REJECT, .result = REJECT,
.result_unpriv = REJECT,
}, },
{ {
"map element value illegal alu op, 3", "map element value illegal alu op, 3",
...@@ -5230,10 +5222,8 @@ static struct bpf_test tests[] = { ...@@ -5230,10 +5222,8 @@ static struct bpf_test tests[] = {
BPF_EXIT_INSN(), BPF_EXIT_INSN(),
}, },
.fixup_map2 = { 3 }, .fixup_map2 = { 3 },
.errstr_unpriv = "R0 pointer arithmetic with /= operator", .errstr = "R0 pointer arithmetic with /= operator",
.errstr = "invalid mem access 'inv'",
.result = REJECT, .result = REJECT,
.result_unpriv = REJECT,
}, },
{ {
"map element value illegal alu op, 4", "map element value illegal alu op, 4",
...@@ -6016,8 +6006,7 @@ static struct bpf_test tests[] = { ...@@ -6016,8 +6006,7 @@ static struct bpf_test tests[] = {
BPF_EXIT_INSN(), BPF_EXIT_INSN(),
}, },
.fixup_map_in_map = { 3 }, .fixup_map_in_map = { 3 },
.errstr = "R1 type=inv expected=map_ptr", .errstr = "R1 pointer arithmetic on CONST_PTR_TO_MAP prohibited",
.errstr_unpriv = "R1 pointer arithmetic on CONST_PTR_TO_MAP prohibited",
.result = REJECT, .result = REJECT,
}, },
{ {
...@@ -7644,6 +7633,19 @@ static struct bpf_test tests[] = { ...@@ -7644,6 +7633,19 @@ static struct bpf_test tests[] = {
.result = REJECT, .result = REJECT,
.prog_type = BPF_PROG_TYPE_SCHED_CLS, .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", "XDP pkt read, pkt_end mangling, bad access 1",
.insns = { .insns = {
...@@ -7659,7 +7661,7 @@ static struct bpf_test tests[] = { ...@@ -7659,7 +7661,7 @@ static struct bpf_test tests[] = {
BPF_MOV64_IMM(BPF_REG_0, 0), BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(), BPF_EXIT_INSN(),
}, },
.errstr = "R1 offset is outside of the packet", .errstr = "R3 pointer arithmetic on PTR_TO_PACKET_END",
.result = REJECT, .result = REJECT,
.prog_type = BPF_PROG_TYPE_XDP, .prog_type = BPF_PROG_TYPE_XDP,
}, },
...@@ -7678,7 +7680,7 @@ static struct bpf_test tests[] = { ...@@ -7678,7 +7680,7 @@ static struct bpf_test tests[] = {
BPF_MOV64_IMM(BPF_REG_0, 0), BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(), BPF_EXIT_INSN(),
}, },
.errstr = "R1 offset is outside of the packet", .errstr = "R3 pointer arithmetic on PTR_TO_PACKET_END",
.result = REJECT, .result = REJECT,
.prog_type = BPF_PROG_TYPE_XDP, .prog_type = BPF_PROG_TYPE_XDP,
}, },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment