diff options
author | Daniel Borkmann <daniel@iogearbox.net> | 2019-04-09 17:20:05 -0400 |
---|---|---|
committer | Alexei Starovoitov <ast@kernel.org> | 2019-04-09 20:05:46 -0400 |
commit | 591fe9888d7809d9ee5c828020b6c6ae27c37229 (patch) | |
tree | 7032fc4e23d63b9cd0b301338e1bc49965eb175b /kernel | |
parent | be70bcd53de66e86f2726e576307cbdaebd3b1a5 (diff) |
bpf: add program side {rd, wr}only support for maps
This work adds two new map creation flags BPF_F_RDONLY_PROG
and BPF_F_WRONLY_PROG in order to allow for read-only or
write-only BPF maps from a BPF program side.
Today we have BPF_F_RDONLY and BPF_F_WRONLY, but this only
applies to system call side, meaning the BPF program has full
read/write access to the map as usual while bpf(2) calls with
map fd can either only read or write into the map depending
on the flags. BPF_F_RDONLY_PROG and BPF_F_WRONLY_PROG allows
for the exact opposite such that verifier is going to reject
program loads if write into a read-only map or a read into a
write-only map is detected. For read-only map case also some
helpers are forbidden for programs that would alter the map
state such as map deletion, update, etc. As opposed to the two
BPF_F_RDONLY / BPF_F_WRONLY flags, BPF_F_RDONLY_PROG as well
as BPF_F_WRONLY_PROG really do correspond to the map lifetime.
We've enabled this generic map extension to various non-special
maps holding normal user data: array, hash, lru, lpm, local
storage, queue and stack. Further generic map types could be
followed up in future depending on use-case. Main use case
here is to forbid writes into .rodata map values from verifier
side.
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Martin KaFai Lau <kafai@fb.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Diffstat (limited to 'kernel')
-rw-r--r-- | kernel/bpf/arraymap.c | 6 | ||||
-rw-r--r-- | kernel/bpf/hashtab.c | 6 | ||||
-rw-r--r-- | kernel/bpf/local_storage.c | 6 | ||||
-rw-r--r-- | kernel/bpf/lpm_trie.c | 3 | ||||
-rw-r--r-- | kernel/bpf/queue_stack_maps.c | 6 | ||||
-rw-r--r-- | kernel/bpf/syscall.c | 2 | ||||
-rw-r--r-- | kernel/bpf/verifier.c | 46 |
7 files changed, 62 insertions, 13 deletions
diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c index 1a6e9861d554..217b10bd9f48 100644 --- a/kernel/bpf/arraymap.c +++ b/kernel/bpf/arraymap.c | |||
@@ -22,7 +22,7 @@ | |||
22 | #include "map_in_map.h" | 22 | #include "map_in_map.h" |
23 | 23 | ||
24 | #define ARRAY_CREATE_FLAG_MASK \ | 24 | #define ARRAY_CREATE_FLAG_MASK \ |
25 | (BPF_F_NUMA_NODE | BPF_F_RDONLY | BPF_F_WRONLY) | 25 | (BPF_F_NUMA_NODE | BPF_F_ACCESS_MASK) |
26 | 26 | ||
27 | static void bpf_array_free_percpu(struct bpf_array *array) | 27 | static void bpf_array_free_percpu(struct bpf_array *array) |
28 | { | 28 | { |
@@ -63,6 +63,7 @@ int array_map_alloc_check(union bpf_attr *attr) | |||
63 | if (attr->max_entries == 0 || attr->key_size != 4 || | 63 | if (attr->max_entries == 0 || attr->key_size != 4 || |
64 | attr->value_size == 0 || | 64 | attr->value_size == 0 || |
65 | attr->map_flags & ~ARRAY_CREATE_FLAG_MASK || | 65 | attr->map_flags & ~ARRAY_CREATE_FLAG_MASK || |
66 | !bpf_map_flags_access_ok(attr->map_flags) || | ||
66 | (percpu && numa_node != NUMA_NO_NODE)) | 67 | (percpu && numa_node != NUMA_NO_NODE)) |
67 | return -EINVAL; | 68 | return -EINVAL; |
68 | 69 | ||
@@ -472,6 +473,9 @@ static int fd_array_map_alloc_check(union bpf_attr *attr) | |||
472 | /* only file descriptors can be stored in this type of map */ | 473 | /* only file descriptors can be stored in this type of map */ |
473 | if (attr->value_size != sizeof(u32)) | 474 | if (attr->value_size != sizeof(u32)) |
474 | return -EINVAL; | 475 | return -EINVAL; |
476 | /* Program read-only/write-only not supported for special maps yet. */ | ||
477 | if (attr->map_flags & (BPF_F_RDONLY_PROG | BPF_F_WRONLY_PROG)) | ||
478 | return -EINVAL; | ||
475 | return array_map_alloc_check(attr); | 479 | return array_map_alloc_check(attr); |
476 | } | 480 | } |
477 | 481 | ||
diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index fed15cf94dca..192d32e77db3 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c | |||
@@ -23,7 +23,7 @@ | |||
23 | 23 | ||
24 | #define HTAB_CREATE_FLAG_MASK \ | 24 | #define HTAB_CREATE_FLAG_MASK \ |
25 | (BPF_F_NO_PREALLOC | BPF_F_NO_COMMON_LRU | BPF_F_NUMA_NODE | \ | 25 | (BPF_F_NO_PREALLOC | BPF_F_NO_COMMON_LRU | BPF_F_NUMA_NODE | \ |
26 | BPF_F_RDONLY | BPF_F_WRONLY | BPF_F_ZERO_SEED) | 26 | BPF_F_ACCESS_MASK | BPF_F_ZERO_SEED) |
27 | 27 | ||
28 | struct bucket { | 28 | struct bucket { |
29 | struct hlist_nulls_head head; | 29 | struct hlist_nulls_head head; |
@@ -262,8 +262,8 @@ static int htab_map_alloc_check(union bpf_attr *attr) | |||
262 | /* Guard against local DoS, and discourage production use. */ | 262 | /* Guard against local DoS, and discourage production use. */ |
263 | return -EPERM; | 263 | return -EPERM; |
264 | 264 | ||
265 | if (attr->map_flags & ~HTAB_CREATE_FLAG_MASK) | 265 | if (attr->map_flags & ~HTAB_CREATE_FLAG_MASK || |
266 | /* reserved bits should not be used */ | 266 | !bpf_map_flags_access_ok(attr->map_flags)) |
267 | return -EINVAL; | 267 | return -EINVAL; |
268 | 268 | ||
269 | if (!lru && percpu_lru) | 269 | if (!lru && percpu_lru) |
diff --git a/kernel/bpf/local_storage.c b/kernel/bpf/local_storage.c index 6b572e2de7fb..980e8f1f6cb5 100644 --- a/kernel/bpf/local_storage.c +++ b/kernel/bpf/local_storage.c | |||
@@ -14,7 +14,7 @@ DEFINE_PER_CPU(struct bpf_cgroup_storage*, bpf_cgroup_storage[MAX_BPF_CGROUP_STO | |||
14 | #ifdef CONFIG_CGROUP_BPF | 14 | #ifdef CONFIG_CGROUP_BPF |
15 | 15 | ||
16 | #define LOCAL_STORAGE_CREATE_FLAG_MASK \ | 16 | #define LOCAL_STORAGE_CREATE_FLAG_MASK \ |
17 | (BPF_F_NUMA_NODE | BPF_F_RDONLY | BPF_F_WRONLY) | 17 | (BPF_F_NUMA_NODE | BPF_F_ACCESS_MASK) |
18 | 18 | ||
19 | struct bpf_cgroup_storage_map { | 19 | struct bpf_cgroup_storage_map { |
20 | struct bpf_map map; | 20 | struct bpf_map map; |
@@ -282,8 +282,8 @@ static struct bpf_map *cgroup_storage_map_alloc(union bpf_attr *attr) | |||
282 | if (attr->value_size > PAGE_SIZE) | 282 | if (attr->value_size > PAGE_SIZE) |
283 | return ERR_PTR(-E2BIG); | 283 | return ERR_PTR(-E2BIG); |
284 | 284 | ||
285 | if (attr->map_flags & ~LOCAL_STORAGE_CREATE_FLAG_MASK) | 285 | if (attr->map_flags & ~LOCAL_STORAGE_CREATE_FLAG_MASK || |
286 | /* reserved bits should not be used */ | 286 | !bpf_map_flags_access_ok(attr->map_flags)) |
287 | return ERR_PTR(-EINVAL); | 287 | return ERR_PTR(-EINVAL); |
288 | 288 | ||
289 | if (attr->max_entries) | 289 | if (attr->max_entries) |
diff --git a/kernel/bpf/lpm_trie.c b/kernel/bpf/lpm_trie.c index 93a5cbbde421..e61630c2e50b 100644 --- a/kernel/bpf/lpm_trie.c +++ b/kernel/bpf/lpm_trie.c | |||
@@ -538,7 +538,7 @@ out: | |||
538 | #define LPM_KEY_SIZE_MIN LPM_KEY_SIZE(LPM_DATA_SIZE_MIN) | 538 | #define LPM_KEY_SIZE_MIN LPM_KEY_SIZE(LPM_DATA_SIZE_MIN) |
539 | 539 | ||
540 | #define LPM_CREATE_FLAG_MASK (BPF_F_NO_PREALLOC | BPF_F_NUMA_NODE | \ | 540 | #define LPM_CREATE_FLAG_MASK (BPF_F_NO_PREALLOC | BPF_F_NUMA_NODE | \ |
541 | BPF_F_RDONLY | BPF_F_WRONLY) | 541 | BPF_F_ACCESS_MASK) |
542 | 542 | ||
543 | static struct bpf_map *trie_alloc(union bpf_attr *attr) | 543 | static struct bpf_map *trie_alloc(union bpf_attr *attr) |
544 | { | 544 | { |
@@ -553,6 +553,7 @@ static struct bpf_map *trie_alloc(union bpf_attr *attr) | |||
553 | if (attr->max_entries == 0 || | 553 | if (attr->max_entries == 0 || |
554 | !(attr->map_flags & BPF_F_NO_PREALLOC) || | 554 | !(attr->map_flags & BPF_F_NO_PREALLOC) || |
555 | attr->map_flags & ~LPM_CREATE_FLAG_MASK || | 555 | attr->map_flags & ~LPM_CREATE_FLAG_MASK || |
556 | !bpf_map_flags_access_ok(attr->map_flags) || | ||
556 | attr->key_size < LPM_KEY_SIZE_MIN || | 557 | attr->key_size < LPM_KEY_SIZE_MIN || |
557 | attr->key_size > LPM_KEY_SIZE_MAX || | 558 | attr->key_size > LPM_KEY_SIZE_MAX || |
558 | attr->value_size < LPM_VAL_SIZE_MIN || | 559 | attr->value_size < LPM_VAL_SIZE_MIN || |
diff --git a/kernel/bpf/queue_stack_maps.c b/kernel/bpf/queue_stack_maps.c index b384ea9f3254..0b140d236889 100644 --- a/kernel/bpf/queue_stack_maps.c +++ b/kernel/bpf/queue_stack_maps.c | |||
@@ -11,8 +11,7 @@ | |||
11 | #include "percpu_freelist.h" | 11 | #include "percpu_freelist.h" |
12 | 12 | ||
13 | #define QUEUE_STACK_CREATE_FLAG_MASK \ | 13 | #define QUEUE_STACK_CREATE_FLAG_MASK \ |
14 | (BPF_F_NUMA_NODE | BPF_F_RDONLY | BPF_F_WRONLY) | 14 | (BPF_F_NUMA_NODE | BPF_F_ACCESS_MASK) |
15 | |||
16 | 15 | ||
17 | struct bpf_queue_stack { | 16 | struct bpf_queue_stack { |
18 | struct bpf_map map; | 17 | struct bpf_map map; |
@@ -52,7 +51,8 @@ static int queue_stack_map_alloc_check(union bpf_attr *attr) | |||
52 | /* check sanity of attributes */ | 51 | /* check sanity of attributes */ |
53 | if (attr->max_entries == 0 || attr->key_size != 0 || | 52 | if (attr->max_entries == 0 || attr->key_size != 0 || |
54 | attr->value_size == 0 || | 53 | attr->value_size == 0 || |
55 | attr->map_flags & ~QUEUE_STACK_CREATE_FLAG_MASK) | 54 | attr->map_flags & ~QUEUE_STACK_CREATE_FLAG_MASK || |
55 | !bpf_map_flags_access_ok(attr->map_flags)) | ||
56 | return -EINVAL; | 56 | return -EINVAL; |
57 | 57 | ||
58 | if (attr->value_size > KMALLOC_MAX_SIZE) | 58 | if (attr->value_size > KMALLOC_MAX_SIZE) |
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 56b4b0e08b3b..0c9276b54c88 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c | |||
@@ -501,6 +501,8 @@ static int map_check_btf(struct bpf_map *map, const struct btf *btf, | |||
501 | map->spin_lock_off = btf_find_spin_lock(btf, value_type); | 501 | map->spin_lock_off = btf_find_spin_lock(btf, value_type); |
502 | 502 | ||
503 | if (map_value_has_spin_lock(map)) { | 503 | if (map_value_has_spin_lock(map)) { |
504 | if (map->map_flags & BPF_F_RDONLY_PROG) | ||
505 | return -EACCES; | ||
504 | if (map->map_type != BPF_MAP_TYPE_HASH && | 506 | if (map->map_type != BPF_MAP_TYPE_HASH && |
505 | map->map_type != BPF_MAP_TYPE_ARRAY && | 507 | map->map_type != BPF_MAP_TYPE_ARRAY && |
506 | map->map_type != BPF_MAP_TYPE_CGROUP_STORAGE) | 508 | map->map_type != BPF_MAP_TYPE_CGROUP_STORAGE) |
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 6ab7a23fc924..b747434df89c 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c | |||
@@ -1439,6 +1439,28 @@ static int check_stack_access(struct bpf_verifier_env *env, | |||
1439 | return 0; | 1439 | return 0; |
1440 | } | 1440 | } |
1441 | 1441 | ||
1442 | static int check_map_access_type(struct bpf_verifier_env *env, u32 regno, | ||
1443 | int off, int size, enum bpf_access_type type) | ||
1444 | { | ||
1445 | struct bpf_reg_state *regs = cur_regs(env); | ||
1446 | struct bpf_map *map = regs[regno].map_ptr; | ||
1447 | u32 cap = bpf_map_flags_to_cap(map); | ||
1448 | |||
1449 | if (type == BPF_WRITE && !(cap & BPF_MAP_CAN_WRITE)) { | ||
1450 | verbose(env, "write into map forbidden, value_size=%d off=%d size=%d\n", | ||
1451 | map->value_size, off, size); | ||
1452 | return -EACCES; | ||
1453 | } | ||
1454 | |||
1455 | if (type == BPF_READ && !(cap & BPF_MAP_CAN_READ)) { | ||
1456 | verbose(env, "read from map forbidden, value_size=%d off=%d size=%d\n", | ||
1457 | map->value_size, off, size); | ||
1458 | return -EACCES; | ||
1459 | } | ||
1460 | |||
1461 | return 0; | ||
1462 | } | ||
1463 | |||
1442 | /* check read/write into map element returned by bpf_map_lookup_elem() */ | 1464 | /* check read/write into map element returned by bpf_map_lookup_elem() */ |
1443 | static int __check_map_access(struct bpf_verifier_env *env, u32 regno, int off, | 1465 | static int __check_map_access(struct bpf_verifier_env *env, u32 regno, int off, |
1444 | int size, bool zero_size_allowed) | 1466 | int size, bool zero_size_allowed) |
@@ -2024,7 +2046,9 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn | |||
2024 | verbose(env, "R%d leaks addr into map\n", value_regno); | 2046 | verbose(env, "R%d leaks addr into map\n", value_regno); |
2025 | return -EACCES; | 2047 | return -EACCES; |
2026 | } | 2048 | } |
2027 | 2049 | err = check_map_access_type(env, regno, off, size, t); | |
2050 | if (err) | ||
2051 | return err; | ||
2028 | err = check_map_access(env, regno, off, size, false); | 2052 | err = check_map_access(env, regno, off, size, false); |
2029 | if (!err && t == BPF_READ && value_regno >= 0) | 2053 | if (!err && t == BPF_READ && value_regno >= 0) |
2030 | mark_reg_unknown(env, regs, value_regno); | 2054 | mark_reg_unknown(env, regs, value_regno); |
@@ -2327,6 +2351,10 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, int regno, | |||
2327 | return check_packet_access(env, regno, reg->off, access_size, | 2351 | return check_packet_access(env, regno, reg->off, access_size, |
2328 | zero_size_allowed); | 2352 | zero_size_allowed); |
2329 | case PTR_TO_MAP_VALUE: | 2353 | case PTR_TO_MAP_VALUE: |
2354 | if (check_map_access_type(env, regno, reg->off, access_size, | ||
2355 | meta && meta->raw_mode ? BPF_WRITE : | ||
2356 | BPF_READ)) | ||
2357 | return -EACCES; | ||
2330 | return check_map_access(env, regno, reg->off, access_size, | 2358 | return check_map_access(env, regno, reg->off, access_size, |
2331 | zero_size_allowed); | 2359 | zero_size_allowed); |
2332 | default: /* scalar_value|ptr_to_stack or invalid ptr */ | 2360 | default: /* scalar_value|ptr_to_stack or invalid ptr */ |
@@ -3059,6 +3087,7 @@ record_func_map(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta, | |||
3059 | int func_id, int insn_idx) | 3087 | int func_id, int insn_idx) |
3060 | { | 3088 | { |
3061 | struct bpf_insn_aux_data *aux = &env->insn_aux_data[insn_idx]; | 3089 | struct bpf_insn_aux_data *aux = &env->insn_aux_data[insn_idx]; |
3090 | struct bpf_map *map = meta->map_ptr; | ||
3062 | 3091 | ||
3063 | if (func_id != BPF_FUNC_tail_call && | 3092 | if (func_id != BPF_FUNC_tail_call && |
3064 | func_id != BPF_FUNC_map_lookup_elem && | 3093 | func_id != BPF_FUNC_map_lookup_elem && |
@@ -3069,11 +3098,24 @@ record_func_map(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta, | |||
3069 | func_id != BPF_FUNC_map_peek_elem) | 3098 | func_id != BPF_FUNC_map_peek_elem) |
3070 | return 0; | 3099 | return 0; |
3071 | 3100 | ||
3072 | if (meta->map_ptr == NULL) { | 3101 | if (map == NULL) { |
3073 | verbose(env, "kernel subsystem misconfigured verifier\n"); | 3102 | verbose(env, "kernel subsystem misconfigured verifier\n"); |
3074 | return -EINVAL; | 3103 | return -EINVAL; |
3075 | } | 3104 | } |
3076 | 3105 | ||
3106 | /* In case of read-only, some additional restrictions | ||
3107 | * need to be applied in order to prevent altering the | ||
3108 | * state of the map from program side. | ||
3109 | */ | ||
3110 | if ((map->map_flags & BPF_F_RDONLY_PROG) && | ||
3111 | (func_id == BPF_FUNC_map_delete_elem || | ||
3112 | func_id == BPF_FUNC_map_update_elem || | ||
3113 | func_id == BPF_FUNC_map_push_elem || | ||
3114 | func_id == BPF_FUNC_map_pop_elem)) { | ||
3115 | verbose(env, "write into map forbidden\n"); | ||
3116 | return -EACCES; | ||
3117 | } | ||
3118 | |||
3077 | if (!BPF_MAP_PTR(aux->map_state)) | 3119 | if (!BPF_MAP_PTR(aux->map_state)) |
3078 | bpf_map_ptr_store(aux, meta->map_ptr, | 3120 | bpf_map_ptr_store(aux, meta->map_ptr, |
3079 | meta->map_ptr->unpriv_array); | 3121 | meta->map_ptr->unpriv_array); |