| From foo@baz Thu Aug 24 17:44:02 PDT 2017 |
| From: Daniel Borkmann <daniel@iogearbox.net> |
| Date: Fri, 21 Jul 2017 00:00:21 +0200 |
| Subject: bpf: fix mixed signed/unsigned derived min/max value bounds |
| |
| From: Daniel Borkmann <daniel@iogearbox.net> |
| |
| |
| [ Upstream commit 4cabc5b186b5427b9ee5a7495172542af105f02b ] |
| |
| Edward reported that there's an issue in min/max value bounds |
| tracking when signed and unsigned compares both provide hints |
| on limits when having unknown variables. E.g. a program such |
| as the following should have been rejected: |
| |
| 0: (7a) *(u64 *)(r10 -8) = 0 |
| 1: (bf) r2 = r10 |
| 2: (07) r2 += -8 |
| 3: (18) r1 = 0xffff8a94cda93400 |
| 5: (85) call bpf_map_lookup_elem#1 |
| 6: (15) if r0 == 0x0 goto pc+7 |
| R0=map_value(ks=8,vs=8,id=0),min_value=0,max_value=0 R10=fp |
| 7: (7a) *(u64 *)(r10 -16) = -8 |
| 8: (79) r1 = *(u64 *)(r10 -16) |
| 9: (b7) r2 = -1 |
| 10: (2d) if r1 > r2 goto pc+3 |
| R0=map_value(ks=8,vs=8,id=0),min_value=0,max_value=0 R1=inv,min_value=0 |
| R2=imm-1,max_value=18446744073709551615,min_align=1 R10=fp |
| 11: (65) if r1 s> 0x1 goto pc+2 |
| R0=map_value(ks=8,vs=8,id=0),min_value=0,max_value=0 R1=inv,min_value=0,max_value=1 |
| R2=imm-1,max_value=18446744073709551615,min_align=1 R10=fp |
| 12: (0f) r0 += r1 |
| 13: (72) *(u8 *)(r0 +0) = 0 |
| R0=map_value_adj(ks=8,vs=8,id=0),min_value=0,max_value=1 R1=inv,min_value=0,max_value=1 |
| R2=imm-1,max_value=18446744073709551615,min_align=1 R10=fp |
| 14: (b7) r0 = 0 |
| 15: (95) exit |
| |
| What happens is that in the first part ... |
| |
| 8: (79) r1 = *(u64 *)(r10 -16) |
| 9: (b7) r2 = -1 |
| 10: (2d) if r1 > r2 goto pc+3 |
| |
| ... r1 carries an unsigned value, and is compared as unsigned |
| against a register carrying an immediate. Verifier deduces in |
| reg_set_min_max() that since the compare is unsigned and operation |
| is greater than (>), that in the fall-through/false case, r1's |
| minimum bound must be 0 and maximum bound must be r2. Latter is |
| larger than the bound and thus max value is reset back to being |
| 'invalid' aka BPF_REGISTER_MAX_RANGE. Thus, r1 state is now |
| 'R1=inv,min_value=0'. The subsequent test ... |
| |
| 11: (65) if r1 s> 0x1 goto pc+2 |
| |
| ... is a signed compare of r1 with immediate value 1. Here, |
| verifier deduces in reg_set_min_max() that since the compare |
| is signed this time and operation is greater than (>), that |
| in the fall-through/false case, we can deduce that r1's maximum |
| bound must be 1, meaning with prior test, we result in r1 having |
| the following state: R1=inv,min_value=0,max_value=1. Given that |
| the actual value this holds is -8, the bounds are wrongly deduced. |
| When this is being added to r0 which holds the map_value(_adj) |
| type, then subsequent store access in above case will go through |
| check_mem_access() which invokes check_map_access_adj(), that |
| will then probe whether the map memory is in bounds based |
| on the min_value and max_value as well as access size since |
| the actual unknown value is min_value <= x <= max_value; commit |
| fce366a9dd0d ("bpf, verifier: fix alu ops against map_value{, |
| _adj} register types") provides some more explanation on the |
| semantics. |
| |
| It's worth to note in this context that in the current code, |
| min_value and max_value tracking are used for two things, i) |
| dynamic map value access via check_map_access_adj() and since |
| commit 06c1c049721a ("bpf: allow helpers access to variable memory") |
| ii) also enforced at check_helper_mem_access() when passing a |
| memory address (pointer to packet, map value, stack) and length |
| pair to a helper and the length in this case is an unknown value |
| defining an access range through min_value/max_value in that |
| case. The min_value/max_value tracking is /not/ used in the |
| direct packet access case to track ranges. However, the issue |
| also affects case ii), for example, the following crafted program |
| based on the same principle must be rejected as well: |
| |
| 0: (b7) r2 = 0 |
| 1: (bf) r3 = r10 |
| 2: (07) r3 += -512 |
| 3: (7a) *(u64 *)(r10 -16) = -8 |
| 4: (79) r4 = *(u64 *)(r10 -16) |
| 5: (b7) r6 = -1 |
| 6: (2d) if r4 > r6 goto pc+5 |
| R1=ctx R2=imm0,min_value=0,max_value=0,min_align=2147483648 R3=fp-512 |
| R4=inv,min_value=0 R6=imm-1,max_value=18446744073709551615,min_align=1 R10=fp |
| 7: (65) if r4 s> 0x1 goto pc+4 |
| R1=ctx R2=imm0,min_value=0,max_value=0,min_align=2147483648 R3=fp-512 |
| R4=inv,min_value=0,max_value=1 R6=imm-1,max_value=18446744073709551615,min_align=1 |
| R10=fp |
| 8: (07) r4 += 1 |
| 9: (b7) r5 = 0 |
| 10: (6a) *(u16 *)(r10 -512) = 0 |
| 11: (85) call bpf_skb_load_bytes#26 |
| 12: (b7) r0 = 0 |
| 13: (95) exit |
| |
| Meaning, while we initialize the max_value stack slot that the |
| verifier thinks we access in the [1,2] range, in reality we |
| pass -7 as length which is interpreted as u32 in the helper. |
| Thus, this issue is relevant also for the case of helper ranges. |
| Resetting both bounds in check_reg_overflow() in case only one |
| of them exceeds limits is also not enough as similar test can be |
| created that uses values which are within range, thus also here |
| learned min value in r1 is incorrect when mixed with later signed |
| test to create a range: |
| |
| 0: (7a) *(u64 *)(r10 -8) = 0 |
| 1: (bf) r2 = r10 |
| 2: (07) r2 += -8 |
| 3: (18) r1 = 0xffff880ad081fa00 |
| 5: (85) call bpf_map_lookup_elem#1 |
| 6: (15) if r0 == 0x0 goto pc+7 |
| R0=map_value(ks=8,vs=8,id=0),min_value=0,max_value=0 R10=fp |
| 7: (7a) *(u64 *)(r10 -16) = -8 |
| 8: (79) r1 = *(u64 *)(r10 -16) |
| 9: (b7) r2 = 2 |
| 10: (3d) if r2 >= r1 goto pc+3 |
| R0=map_value(ks=8,vs=8,id=0),min_value=0,max_value=0 R1=inv,min_value=3 |
| R2=imm2,min_value=2,max_value=2,min_align=2 R10=fp |
| 11: (65) if r1 s> 0x4 goto pc+2 |
| R0=map_value(ks=8,vs=8,id=0),min_value=0,max_value=0 |
| R1=inv,min_value=3,max_value=4 R2=imm2,min_value=2,max_value=2,min_align=2 R10=fp |
| 12: (0f) r0 += r1 |
| 13: (72) *(u8 *)(r0 +0) = 0 |
| R0=map_value_adj(ks=8,vs=8,id=0),min_value=3,max_value=4 |
| R1=inv,min_value=3,max_value=4 R2=imm2,min_value=2,max_value=2,min_align=2 R10=fp |
| 14: (b7) r0 = 0 |
| 15: (95) exit |
| |
| This leaves us with two options for fixing this: i) to invalidate |
| all prior learned information once we switch signed context, ii) |
| to track min/max signed and unsigned boundaries separately as |
| done in [0]. (Given latter introduces major changes throughout |
| the whole verifier, it's rather net-next material, thus this |
| patch follows option i), meaning we can derive bounds either |
| from only signed tests or only unsigned tests.) There is still the |
| case of adjust_reg_min_max_vals(), where we adjust bounds on ALU |
| operations, meaning programs like the following where boundaries |
| on the reg get mixed in context later on when bounds are merged |
| on the dst reg must get rejected, too: |
| |
| 0: (7a) *(u64 *)(r10 -8) = 0 |
| 1: (bf) r2 = r10 |
| 2: (07) r2 += -8 |
| 3: (18) r1 = 0xffff89b2bf87ce00 |
| 5: (85) call bpf_map_lookup_elem#1 |
| 6: (15) if r0 == 0x0 goto pc+6 |
| R0=map_value(ks=8,vs=8,id=0),min_value=0,max_value=0 R10=fp |
| 7: (7a) *(u64 *)(r10 -16) = -8 |
| 8: (79) r1 = *(u64 *)(r10 -16) |
| 9: (b7) r2 = 2 |
| 10: (3d) if r2 >= r1 goto pc+2 |
| R0=map_value(ks=8,vs=8,id=0),min_value=0,max_value=0 R1=inv,min_value=3 |
| R2=imm2,min_value=2,max_value=2,min_align=2 R10=fp |
| 11: (b7) r7 = 1 |
| 12: (65) if r7 s> 0x0 goto pc+2 |
| R0=map_value(ks=8,vs=8,id=0),min_value=0,max_value=0 R1=inv,min_value=3 |
| R2=imm2,min_value=2,max_value=2,min_align=2 R7=imm1,max_value=0 R10=fp |
| 13: (b7) r0 = 0 |
| 14: (95) exit |
| |
| from 12 to 15: R0=map_value(ks=8,vs=8,id=0),min_value=0,max_value=0 |
| R1=inv,min_value=3 R2=imm2,min_value=2,max_value=2,min_align=2 R7=imm1,min_value=1 R10=fp |
| 15: (0f) r7 += r1 |
| 16: (65) if r7 s> 0x4 goto pc+2 |
| R0=map_value(ks=8,vs=8,id=0),min_value=0,max_value=0 R1=inv,min_value=3 |
| R2=imm2,min_value=2,max_value=2,min_align=2 R7=inv,min_value=4,max_value=4 R10=fp |
| 17: (0f) r0 += r7 |
| 18: (72) *(u8 *)(r0 +0) = 0 |
| R0=map_value_adj(ks=8,vs=8,id=0),min_value=4,max_value=4 R1=inv,min_value=3 |
| R2=imm2,min_value=2,max_value=2,min_align=2 R7=inv,min_value=4,max_value=4 R10=fp |
| 19: (b7) r0 = 0 |
| 20: (95) exit |
| |
| Meaning, in adjust_reg_min_max_vals() we must also reset range |
| values on the dst when src/dst registers have mixed signed/ |
| unsigned derived min/max value bounds with one unbounded value |
| as otherwise they can be added together deducing false boundaries. |
| Once both boundaries are established from either ALU ops or |
| compare operations w/o mixing signed/unsigned insns, then they |
| can safely be added to other regs also having both boundaries |
| established. Adding regs with one unbounded side to a map value |
| where the bounded side has been learned w/o mixing ops is |
| possible, but the resulting map value won't recover from that, |
| meaning such op is considered invalid on the time of actual |
| access. Invalid bounds are set on the dst reg in case i) src reg, |
| or ii) in case dst reg already had them. The only way to recover |
| would be to perform i) ALU ops but only 'add' is allowed on map |
| value types or ii) comparisons, but these are disallowed on |
| pointers in case they span a range. This is fine as only BPF_JEQ |
| and BPF_JNE may be performed on PTR_TO_MAP_VALUE_OR_NULL registers |
| which potentially turn them into PTR_TO_MAP_VALUE type depending |
| on the branch, so only here min/max value cannot be invalidated |
| for them. |
| |
| In terms of state pruning, value_from_signed is considered |
| as well in states_equal() when dealing with adjusted map values. |
| With regards to breaking existing programs, there is a small |
| risk, but use-cases are rather quite narrow where this could |
| occur and mixing compares probably unlikely. |
| |
| Joint work with Josef and Edward. |
| |
| [0] https://lists.iovisor.org/pipermail/iovisor-dev/2017-June/000822.html |
| |
| Fixes: 484611357c19 ("bpf: allow access into map value arrays") |
| Reported-by: Edward Cree <ecree@solarflare.com> |
| Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> |
| Signed-off-by: Edward Cree <ecree@solarflare.com> |
| Signed-off-by: Josef Bacik <jbacik@fb.com> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| include/linux/bpf_verifier.h | 1 |
| kernel/bpf/verifier.c | 110 +++++++++++++++++++++++++++++++++++++------ |
| 2 files changed, 97 insertions(+), 14 deletions(-) |
| |
| --- a/include/linux/bpf_verifier.h |
| +++ b/include/linux/bpf_verifier.h |
| @@ -40,6 +40,7 @@ struct bpf_reg_state { |
| */ |
| s64 min_value; |
| u64 max_value; |
| + bool value_from_signed; |
| }; |
| |
| enum bpf_stack_slot_type { |
| --- a/kernel/bpf/verifier.c |
| +++ b/kernel/bpf/verifier.c |
| @@ -682,12 +682,13 @@ static int check_ctx_access(struct bpf_v |
| return -EACCES; |
| } |
| |
| -static bool is_pointer_value(struct bpf_verifier_env *env, int regno) |
| +static bool __is_pointer_value(bool allow_ptr_leaks, |
| + const struct bpf_reg_state *reg) |
| { |
| - if (env->allow_ptr_leaks) |
| + if (allow_ptr_leaks) |
| return false; |
| |
| - switch (env->cur_state.regs[regno].type) { |
| + switch (reg->type) { |
| case UNKNOWN_VALUE: |
| case CONST_IMM: |
| return false; |
| @@ -696,6 +697,11 @@ static bool is_pointer_value(struct bpf_ |
| } |
| } |
| |
| +static bool is_pointer_value(struct bpf_verifier_env *env, int regno) |
| +{ |
| + return __is_pointer_value(env->allow_ptr_leaks, &env->cur_state.regs[regno]); |
| +} |
| + |
| static int check_ptr_alignment(struct bpf_verifier_env *env, |
| struct bpf_reg_state *reg, int off, int size) |
| { |
| @@ -1592,10 +1598,24 @@ static void adjust_reg_min_max_vals(stru |
| } |
| |
| /* We don't know anything about what was done to this register, mark it |
| - * as unknown. |
| + * as unknown. Also, if both derived bounds came from signed/unsigned |
| + * mixed compares and one side is unbounded, we cannot really do anything |
| + * with them as boundaries cannot be trusted. Thus, arithmetic of two |
| + * regs of such kind will get invalidated bounds on the dst side. |
| */ |
| - if (min_val == BPF_REGISTER_MIN_RANGE && |
| - max_val == BPF_REGISTER_MAX_RANGE) { |
| + if ((min_val == BPF_REGISTER_MIN_RANGE && |
| + max_val == BPF_REGISTER_MAX_RANGE) || |
| + (BPF_SRC(insn->code) == BPF_X && |
| + ((min_val != BPF_REGISTER_MIN_RANGE && |
| + max_val == BPF_REGISTER_MAX_RANGE) || |
| + (min_val == BPF_REGISTER_MIN_RANGE && |
| + max_val != BPF_REGISTER_MAX_RANGE) || |
| + (dst_reg->min_value != BPF_REGISTER_MIN_RANGE && |
| + dst_reg->max_value == BPF_REGISTER_MAX_RANGE) || |
| + (dst_reg->min_value == BPF_REGISTER_MIN_RANGE && |
| + dst_reg->max_value != BPF_REGISTER_MAX_RANGE)) && |
| + regs[insn->dst_reg].value_from_signed != |
| + regs[insn->src_reg].value_from_signed)) { |
| reset_reg_range_values(regs, insn->dst_reg); |
| return; |
| } |
| @@ -1939,38 +1959,63 @@ static void reg_set_min_max(struct bpf_r |
| struct bpf_reg_state *false_reg, u64 val, |
| u8 opcode) |
| { |
| + bool value_from_signed = true; |
| + bool is_range = true; |
| + |
| switch (opcode) { |
| case BPF_JEQ: |
| /* If this is false then we know nothing Jon Snow, but if it is |
| * true then we know for sure. |
| */ |
| true_reg->max_value = true_reg->min_value = val; |
| + is_range = false; |
| break; |
| case BPF_JNE: |
| /* If this is true we know nothing Jon Snow, but if it is false |
| * we know the value for sure; |
| */ |
| false_reg->max_value = false_reg->min_value = val; |
| + is_range = false; |
| break; |
| case BPF_JGT: |
| - /* Unsigned comparison, the minimum value is 0. */ |
| - false_reg->min_value = 0; |
| + value_from_signed = false; |
| + /* fallthrough */ |
| case BPF_JSGT: |
| + if (true_reg->value_from_signed != value_from_signed) |
| + reset_reg_range_values(true_reg, 0); |
| + if (false_reg->value_from_signed != value_from_signed) |
| + reset_reg_range_values(false_reg, 0); |
| + if (opcode == BPF_JGT) { |
| + /* Unsigned comparison, the minimum value is 0. */ |
| + false_reg->min_value = 0; |
| + } |
| /* If this is false then we know the maximum val is val, |
| * otherwise we know the min val is val+1. |
| */ |
| false_reg->max_value = val; |
| + false_reg->value_from_signed = value_from_signed; |
| true_reg->min_value = val + 1; |
| + true_reg->value_from_signed = value_from_signed; |
| break; |
| case BPF_JGE: |
| - /* Unsigned comparison, the minimum value is 0. */ |
| - false_reg->min_value = 0; |
| + value_from_signed = false; |
| + /* fallthrough */ |
| case BPF_JSGE: |
| + if (true_reg->value_from_signed != value_from_signed) |
| + reset_reg_range_values(true_reg, 0); |
| + if (false_reg->value_from_signed != value_from_signed) |
| + reset_reg_range_values(false_reg, 0); |
| + if (opcode == BPF_JGE) { |
| + /* Unsigned comparison, the minimum value is 0. */ |
| + false_reg->min_value = 0; |
| + } |
| /* If this is false then we know the maximum value is val - 1, |
| * otherwise we know the mimimum value is val. |
| */ |
| false_reg->max_value = val - 1; |
| + false_reg->value_from_signed = value_from_signed; |
| true_reg->min_value = val; |
| + true_reg->value_from_signed = value_from_signed; |
| break; |
| default: |
| break; |
| @@ -1978,6 +2023,12 @@ static void reg_set_min_max(struct bpf_r |
| |
| check_reg_overflow(false_reg); |
| check_reg_overflow(true_reg); |
| + if (is_range) { |
| + if (__is_pointer_value(false, false_reg)) |
| + reset_reg_range_values(false_reg, 0); |
| + if (__is_pointer_value(false, true_reg)) |
| + reset_reg_range_values(true_reg, 0); |
| + } |
| } |
| |
| /* Same as above, but for the case that dst_reg is a CONST_IMM reg and src_reg |
| @@ -1987,39 +2038,64 @@ static void reg_set_min_max_inv(struct b |
| struct bpf_reg_state *false_reg, u64 val, |
| u8 opcode) |
| { |
| + bool value_from_signed = true; |
| + bool is_range = true; |
| + |
| switch (opcode) { |
| case BPF_JEQ: |
| /* If this is false then we know nothing Jon Snow, but if it is |
| * true then we know for sure. |
| */ |
| true_reg->max_value = true_reg->min_value = val; |
| + is_range = false; |
| break; |
| case BPF_JNE: |
| /* If this is true we know nothing Jon Snow, but if it is false |
| * we know the value for sure; |
| */ |
| false_reg->max_value = false_reg->min_value = val; |
| + is_range = false; |
| break; |
| case BPF_JGT: |
| - /* Unsigned comparison, the minimum value is 0. */ |
| - true_reg->min_value = 0; |
| + value_from_signed = false; |
| + /* fallthrough */ |
| case BPF_JSGT: |
| + if (true_reg->value_from_signed != value_from_signed) |
| + reset_reg_range_values(true_reg, 0); |
| + if (false_reg->value_from_signed != value_from_signed) |
| + reset_reg_range_values(false_reg, 0); |
| + if (opcode == BPF_JGT) { |
| + /* Unsigned comparison, the minimum value is 0. */ |
| + true_reg->min_value = 0; |
| + } |
| /* |
| * If this is false, then the val is <= the register, if it is |
| * true the register <= to the val. |
| */ |
| false_reg->min_value = val; |
| + false_reg->value_from_signed = value_from_signed; |
| true_reg->max_value = val - 1; |
| + true_reg->value_from_signed = value_from_signed; |
| break; |
| case BPF_JGE: |
| - /* Unsigned comparison, the minimum value is 0. */ |
| - true_reg->min_value = 0; |
| + value_from_signed = false; |
| + /* fallthrough */ |
| case BPF_JSGE: |
| + if (true_reg->value_from_signed != value_from_signed) |
| + reset_reg_range_values(true_reg, 0); |
| + if (false_reg->value_from_signed != value_from_signed) |
| + reset_reg_range_values(false_reg, 0); |
| + if (opcode == BPF_JGE) { |
| + /* Unsigned comparison, the minimum value is 0. */ |
| + true_reg->min_value = 0; |
| + } |
| /* If this is false then constant < register, if it is true then |
| * the register < constant. |
| */ |
| false_reg->min_value = val + 1; |
| + false_reg->value_from_signed = value_from_signed; |
| true_reg->max_value = val; |
| + true_reg->value_from_signed = value_from_signed; |
| break; |
| default: |
| break; |
| @@ -2027,6 +2103,12 @@ static void reg_set_min_max_inv(struct b |
| |
| check_reg_overflow(false_reg); |
| check_reg_overflow(true_reg); |
| + if (is_range) { |
| + if (__is_pointer_value(false, false_reg)) |
| + reset_reg_range_values(false_reg, 0); |
| + if (__is_pointer_value(false, true_reg)) |
| + reset_reg_range_values(true_reg, 0); |
| + } |
| } |
| |
| static void mark_map_reg(struct bpf_reg_state *regs, u32 regno, u32 id, |