diff options
Diffstat (limited to 'common/recipes-kernel/linux/linux-yocto-4.9.21/0044-bpf-prevent-out-of-bounds-speculation.patch')
-rw-r--r-- | common/recipes-kernel/linux/linux-yocto-4.9.21/0044-bpf-prevent-out-of-bounds-speculation.patch | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/common/recipes-kernel/linux/linux-yocto-4.9.21/0044-bpf-prevent-out-of-bounds-speculation.patch b/common/recipes-kernel/linux/linux-yocto-4.9.21/0044-bpf-prevent-out-of-bounds-speculation.patch new file mode 100644 index 00000000..c78bafc9 --- /dev/null +++ b/common/recipes-kernel/linux/linux-yocto-4.9.21/0044-bpf-prevent-out-of-bounds-speculation.patch @@ -0,0 +1,274 @@ +From 282d67fffa131c0df11807ce60f9ff3fea1dc340 Mon Sep 17 00:00:00 2001 +From: Alexei Starovoitov <ast@kernel.org> +Date: Sun, 7 Jan 2018 17:33:02 -0800 +Subject: [PATCH 044/102] bpf: prevent out-of-bounds speculation + +commit b2157399cc9898260d6031c5bfe45fe137c1fbe7 upstream. + +Under speculation, CPUs may mis-predict branches in bounds checks. Thus, +memory accesses under a bounds check may be speculated even if the +bounds check fails, providing a primitive for building a side channel. + +To avoid leaking kernel data round up array-based maps and mask the index +after bounds check, so speculated load with out of bounds index will load +either valid value from the array or zero from the padded area. + +Unconditionally mask index for all array types even when max_entries +are not rounded to power of 2 for root user. +When map is created by unpriv user generate a sequence of bpf insns +that includes AND operation to make sure that JITed code includes +the same 'index & index_mask' operation. + +If prog_array map is created by unpriv user replace + bpf_tail_call(ctx, map, index); +with + if (index >= max_entries) { + index &= map->index_mask; + bpf_tail_call(ctx, map, index); + } +(along with roundup to power 2) to prevent out-of-bounds speculation. +There is secondary redundant 'if (index >= max_entries)' in the interpreter +and in all JITs, but they can be optimized later if necessary. + +Other array-like maps (cpumap, devmap, sockmap, perf_event_array, cgroup_array) +cannot be used by unpriv, so no changes there. + +That fixes bpf side of "Variant 1: bounds check bypass (CVE-2017-5753)" on +all architectures with and without JIT. + +v2->v3: +Daniel noticed that attack potentially can be crafted via syscall commands +without loading the program, so add masking to those paths as well. + +Signed-off-by: Alexei Starovoitov <ast@kernel.org> +Acked-by: John Fastabend <john.fastabend@gmail.com> +Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> +Cc: Jiri Slaby <jslaby@suse.cz> +[ Backported to 4.9 - gregkh ] +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> +--- + include/linux/bpf.h | 2 ++ + include/linux/bpf_verifier.h | 6 +++++- + kernel/bpf/arraymap.c | 31 ++++++++++++++++++++++--------- + kernel/bpf/verifier.c | 42 +++++++++++++++++++++++++++++++++++++++--- + 4 files changed, 68 insertions(+), 13 deletions(-) + +diff --git a/include/linux/bpf.h b/include/linux/bpf.h +index c201017..0dbb21b 100644 +--- a/include/linux/bpf.h ++++ b/include/linux/bpf.h +@@ -43,6 +43,7 @@ struct bpf_map { + u32 max_entries; + u32 map_flags; + u32 pages; ++ bool unpriv_array; + struct user_struct *user; + const struct bpf_map_ops *ops; + struct work_struct work; +@@ -189,6 +190,7 @@ struct bpf_prog_aux { + struct bpf_array { + struct bpf_map map; + u32 elem_size; ++ u32 index_mask; + /* 'ownership' of prog_array is claimed by the first program that + * is going to use this map or by the first program which FD is stored + * in the map to make sure that all callers and callees have the same +diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h +index a13b031..2edf8de 100644 +--- a/include/linux/bpf_verifier.h ++++ b/include/linux/bpf_verifier.h +@@ -66,7 +66,11 @@ struct bpf_verifier_state_list { + }; + + struct bpf_insn_aux_data { +- enum bpf_reg_type ptr_type; /* pointer type for load/store insns */ ++ union { ++ enum bpf_reg_type ptr_type; /* pointer type for load/store insns */ ++ struct bpf_map *map_ptr; /* pointer for call insn into lookup_elem */ ++ }; ++ bool seen; /* this insn was processed by the verifier */ + }; + + #define MAX_USED_MAPS 64 /* max number of maps accessed by one eBPF program */ +diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c +index a2ac051..eeb7f1b 100644 +--- a/kernel/bpf/arraymap.c ++++ b/kernel/bpf/arraymap.c +@@ -47,9 +47,10 @@ static int bpf_array_alloc_percpu(struct bpf_array *array) + static struct bpf_map *array_map_alloc(union bpf_attr *attr) + { + bool percpu = attr->map_type == BPF_MAP_TYPE_PERCPU_ARRAY; ++ u32 elem_size, index_mask, max_entries; ++ bool unpriv = !capable(CAP_SYS_ADMIN); + struct bpf_array *array; + u64 array_size; +- u32 elem_size; + + /* check sanity of attributes */ + if (attr->max_entries == 0 || attr->key_size != 4 || +@@ -64,11 +65,20 @@ static struct bpf_map *array_map_alloc(union bpf_attr *attr) + + elem_size = round_up(attr->value_size, 8); + ++ max_entries = attr->max_entries; ++ index_mask = roundup_pow_of_two(max_entries) - 1; ++ ++ if (unpriv) ++ /* round up array size to nearest power of 2, ++ * since cpu will speculate within index_mask limits ++ */ ++ max_entries = index_mask + 1; ++ + array_size = sizeof(*array); + if (percpu) +- array_size += (u64) attr->max_entries * sizeof(void *); ++ array_size += (u64) max_entries * sizeof(void *); + else +- array_size += (u64) attr->max_entries * elem_size; ++ array_size += (u64) max_entries * elem_size; + + /* make sure there is no u32 overflow later in round_up() */ + if (array_size >= U32_MAX - PAGE_SIZE) +@@ -82,6 +92,8 @@ static struct bpf_map *array_map_alloc(union bpf_attr *attr) + if (!array) + return ERR_PTR(-ENOMEM); + } ++ array->index_mask = index_mask; ++ array->map.unpriv_array = unpriv; + + /* copy mandatory map attributes */ + array->map.map_type = attr->map_type; +@@ -115,7 +127,7 @@ static void *array_map_lookup_elem(struct bpf_map *map, void *key) + if (unlikely(index >= array->map.max_entries)) + return NULL; + +- return array->value + array->elem_size * index; ++ return array->value + array->elem_size * (index & array->index_mask); + } + + /* Called from eBPF program */ +@@ -127,7 +139,7 @@ static void *percpu_array_map_lookup_elem(struct bpf_map *map, void *key) + if (unlikely(index >= array->map.max_entries)) + return NULL; + +- return this_cpu_ptr(array->pptrs[index]); ++ return this_cpu_ptr(array->pptrs[index & array->index_mask]); + } + + int bpf_percpu_array_copy(struct bpf_map *map, void *key, void *value) +@@ -147,7 +159,7 @@ int bpf_percpu_array_copy(struct bpf_map *map, void *key, void *value) + */ + size = round_up(map->value_size, 8); + rcu_read_lock(); +- pptr = array->pptrs[index]; ++ pptr = array->pptrs[index & array->index_mask]; + for_each_possible_cpu(cpu) { + bpf_long_memcpy(value + off, per_cpu_ptr(pptr, cpu), size); + off += size; +@@ -195,10 +207,11 @@ static int array_map_update_elem(struct bpf_map *map, void *key, void *value, + return -EEXIST; + + if (array->map.map_type == BPF_MAP_TYPE_PERCPU_ARRAY) +- memcpy(this_cpu_ptr(array->pptrs[index]), ++ memcpy(this_cpu_ptr(array->pptrs[index & array->index_mask]), + value, map->value_size); + else +- memcpy(array->value + array->elem_size * index, ++ memcpy(array->value + ++ array->elem_size * (index & array->index_mask), + value, map->value_size); + return 0; + } +@@ -232,7 +245,7 @@ int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value, + */ + size = round_up(map->value_size, 8); + rcu_read_lock(); +- pptr = array->pptrs[index]; ++ pptr = array->pptrs[index & array->index_mask]; + for_each_possible_cpu(cpu) { + bpf_long_memcpy(per_cpu_ptr(pptr, cpu), value + off, size); + off += size; +diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c +index 5118d3e..56a867f 100644 +--- a/kernel/bpf/verifier.c ++++ b/kernel/bpf/verifier.c +@@ -1165,7 +1165,7 @@ static void clear_all_pkt_pointers(struct bpf_verifier_env *env) + } + } + +-static int check_call(struct bpf_verifier_env *env, int func_id) ++static int check_call(struct bpf_verifier_env *env, int func_id, int insn_idx) + { + struct bpf_verifier_state *state = &env->cur_state; + const struct bpf_func_proto *fn = NULL; +@@ -1216,6 +1216,13 @@ static int check_call(struct bpf_verifier_env *env, int func_id) + err = check_func_arg(env, BPF_REG_2, fn->arg2_type, &meta); + if (err) + return err; ++ if (func_id == BPF_FUNC_tail_call) { ++ if (meta.map_ptr == NULL) { ++ verbose("verifier bug\n"); ++ return -EINVAL; ++ } ++ env->insn_aux_data[insn_idx].map_ptr = meta.map_ptr; ++ } + err = check_func_arg(env, BPF_REG_3, fn->arg3_type, &meta); + if (err) + return err; +@@ -2799,7 +2806,7 @@ static int do_check(struct bpf_verifier_env *env) + return -EINVAL; + } + +- err = check_call(env, insn->imm); ++ err = check_call(env, insn->imm, insn_idx); + if (err) + return err; + +@@ -3095,7 +3102,11 @@ static int fixup_bpf_calls(struct bpf_verifier_env *env) + struct bpf_insn *insn = prog->insnsi; + const struct bpf_func_proto *fn; + const int insn_cnt = prog->len; +- int i; ++ struct bpf_insn insn_buf[16]; ++ struct bpf_prog *new_prog; ++ struct bpf_map *map_ptr; ++ int i, cnt, delta = 0; ++ + + for (i = 0; i < insn_cnt; i++, insn++) { + if (insn->code != (BPF_JMP | BPF_CALL)) +@@ -3113,6 +3124,31 @@ static int fixup_bpf_calls(struct bpf_verifier_env *env) + */ + insn->imm = 0; + insn->code |= BPF_X; ++ ++ /* instead of changing every JIT dealing with tail_call ++ * emit two extra insns: ++ * if (index >= max_entries) goto out; ++ * index &= array->index_mask; ++ * to avoid out-of-bounds cpu speculation ++ */ ++ map_ptr = env->insn_aux_data[i + delta].map_ptr; ++ if (!map_ptr->unpriv_array) ++ continue; ++ insn_buf[0] = BPF_JMP_IMM(BPF_JGE, BPF_REG_3, ++ map_ptr->max_entries, 2); ++ insn_buf[1] = BPF_ALU32_IMM(BPF_AND, BPF_REG_3, ++ container_of(map_ptr, ++ struct bpf_array, ++ map)->index_mask); ++ insn_buf[2] = *insn; ++ cnt = 3; ++ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); ++ if (!new_prog) ++ return -ENOMEM; ++ ++ delta += cnt - 1; ++ env->prog = prog = new_prog; ++ insn = new_prog->insnsi + i + delta; + continue; + } + +-- +2.7.4 + |