aboutsummaryrefslogtreecommitdiffstats
path: root/common/recipes-kernel/linux/linux-yocto-4.9.21/0044-bpf-prevent-out-of-bounds-speculation.patch
diff options
context:
space:
mode:
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.patch274
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
+