History

bpf: Fix toctou on read-only map's constant scalar tracking

Commit a23740ec43ba ("bpf: Track contents of read-only maps as scalars") is checking whether maps are read-only both from BPF program side and user space side, and then, given their content is constant, reading out their data via map->ops->map_direct_value_addr() which is then subsequently used as known scalar value for the register, that is, it is marked as __mark_reg_known() with the read value at verification time. Before a23740ec43ba, the register content was marked as an unknown scalar so the verifier could not make any assumptions about the map content. The current implementation however is prone to a TOCTOU race, meaning, the value read as known scalar for the register is not guaranteed to be exactly the same at a later point when the program is executed, and as such, the prior made assumptions of the verifier with regards to the program will be invalid which can cause issues such as OOB access, etc. While the BPF_F_RDONLY_PROG map flag is always fixed and required to be specified at map creation time, the map->frozen property is initially set to false for the map given the map value needs to be populated, e.g. for global data sections. Once complete, the loader "freezes" the map from user space such that no subsequent updates/deletes are possible anymore. For the rest of the lifetime of the map, this freeze one-time trigger cannot be undone anymore after a successful BPF_MAP_FREEZE cmd return. Meaning, any new BPF_* cmd calls which would update/delete map entries will be rejected with -EPERM since map_get_sys_perms() removes the FMODE_CAN_WRITE permission. This also means that pending update/delete map entries must still complete before this guarantee is given. This corner case is not an issue for loaders since they create and prepare such program private map in successive steps. However, a malicious user is able to trigger this TOCTOU race in two different ways: i) via userfaultfd, and ii) via batched updates. For i) userfaultfd is used to expand the competition interval, so that map_update_elem() can modify the contents of the map after map_freeze() and bpf_prog_load() were executed. This works, because userfaultfd halts the parallel thread which triggered a map_update_elem() at the time where we copy key/value from the user buffer and this already passed the FMODE_CAN_WRITE capability test given at that time the map was not "frozen". Then, the main thread performs the map_freeze() and bpf_prog_load(), and once that had completed successfully, the other thread is woken up to complete the pending map_update_elem() which then changes the map content. For ii) the idea of the batched update is similar, meaning, when there are a large number of updates to be processed, it can increase the competition interval between the two. It is therefore possible in practice to modify the contents of the map after executing map_freeze() and bpf_prog_load(). One way to fix both i) and ii) at the same time is to expand the use of the map's map->writecnt. The latter was introduced in fc9702273e2e ("bpf: Add mmap() support for BPF_MAP_TYPE_ARRAY") and further refined in 1f6cb19be2e2 ("bpf: Prevent re-mmap()'ing BPF map as writable for initially r/o mapping") with the rationale to make a writable mmap()'ing of a map mutually exclusive with read-only freezing. The counter indicates writable mmap() mappings and then prevents/fails the freeze operation. Its semantics can be expanded beyond just mmap() by generally indicating ongoing write phases. This would essentially span any parallel regular and batched flavor of update/delete operation and then also have map_freeze() fail with -EBUSY. For the check_mem_access() in the verifier we expand upon the bpf_map_is_rdonly() check ensuring that all last pending writes have completed via bpf_map_write_active() test. Once the map->frozen is set and bpf_map_write_active() indicates a map->writecnt of 0 only then we are really guaranteed to use the map's data as known constants. For map->frozen being set and pending writes in process of still being completed we fall back to marking that register as unknown scalar so we don't end up making assumptions about it. With this, both TOCTOU reproducers from i) and ii) are fixed. Note that the map->writecnt has been converted into a atomic64 in the fix in order to avoid a double freeze_mutex mutex_{un,}lock() pair when updating map->writecnt in the various map update/delete BPF_* cmd flavors. Spanning the freeze_mutex over entire map update/delete operations in syscall side would not be possible due to then causing everything to be serialized. Similarly, something like synchronize_rcu() after setting map->frozen to wait for update/deletes to complete is not possible either since it would also have to span the user copy which can sleep. On the libbpf side, this won't break d66562fba1ce ("libbpf: Add BPF object skeleton support") as the anonymous mmap()-ed "map initialization image" is remapped as a BPF map-backed mmap()-ed memory where for .rodata it's non-writable.

Daniel Borkmann committed 5 years ago

Commit 353050b1 parent 6060a6c3 files · +54 −23Browse files

Showing the changes to kernel (2 of 3 files). Show all

Unified · Split

2 files
  1. kernel/bpf/syscall.c +36 −21
  2. kernel/bpf/verifier.c +16 −1
kernel/bpf/syscall.c+36−21
@@ -132,6 +132,21 @@ static struct bpf_map *find_and_alloc_map(union bpf_attr *attr) · 131 unmodified lines
132 return map;132 return map;
133}133}
134 134
135static void bpf_map_write_active_inc(struct bpf_map *map)
136{
137 atomic64_inc(&map->writecnt);
138}
139
140static void bpf_map_write_active_dec(struct bpf_map *map)
141{
142 atomic64_dec(&map->writecnt);
143}
144
145bool bpf_map_write_active(const struct bpf_map *map)
146{
147 return atomic64_read(&map->writecnt) != 0;
148}
149
135static u32 bpf_map_value_size(const struct bpf_map *map)150static u32 bpf_map_value_size(const struct bpf_map *map)
136{151{
137 if (map->map_type == BPF_MAP_TYPE_PERCPU_HASH ||152 if (map->map_type == BPF_MAP_TYPE_PERCPU_HASH ||
@@ -601,11 +616,8 @@ static void bpf_map_mmap_open(struct vm_area_struct *vma) · 463 unmodified lines
601{616{
602 struct bpf_map *map = vma->vm_file->private_data;617 struct bpf_map *map = vma->vm_file->private_data;
603 618
604 if (vma->vm_flags & VM_MAYWRITE) {619 if (vma->vm_flags & VM_MAYWRITE)
605 mutex_lock(&map->freeze_mutex);620 bpf_map_write_active_inc(map);
606 map->writecnt++;
607 mutex_unlock(&map->freeze_mutex);
608 }
609}621}
610 622
611/* called for all unmapped memory region (including initial) */623/* called for all unmapped memory region (including initial) */
@@ -613,11 +625,8 @@ static void bpf_map_mmap_close(struct vm_area_struct *vma) · 1 unmodified line
613{625{
614 struct bpf_map *map = vma->vm_file->private_data;626 struct bpf_map *map = vma->vm_file->private_data;
615 627
616 if (vma->vm_flags & VM_MAYWRITE) {628 if (vma->vm_flags & VM_MAYWRITE)
617 mutex_lock(&map->freeze_mutex);629 bpf_map_write_active_dec(map);
618 map->writecnt--;
619 mutex_unlock(&map->freeze_mutex);
620 }
621}630}
622 631
623static const struct vm_operations_struct bpf_map_default_vmops = {632static const struct vm_operations_struct bpf_map_default_vmops = {
@@ -668,7 +677,7 @@ static int bpf_map_mmap(struct file *filp, struct vm_area_struct *vma) · 44 unmodified lines
668 goto out;677 goto out;
669 678
670 if (vma->vm_flags & VM_MAYWRITE)679 if (vma->vm_flags & VM_MAYWRITE)
671 map->writecnt++;680 bpf_map_write_active_inc(map);
672out:681out:
673 mutex_unlock(&map->freeze_mutex);682 mutex_unlock(&map->freeze_mutex);
674 return err;683 return err;
@@ -1139,6 +1148,7 @@ static int map_update_elem(union bpf_attr *attr, bpfptr_t uattr) · 464 unmodified lines
1139 map = __bpf_map_get(f);1148 map = __bpf_map_get(f);
1140 if (IS_ERR(map))1149 if (IS_ERR(map))
1141 return PTR_ERR(map);1150 return PTR_ERR(map);
1151 bpf_map_write_active_inc(map);
1142 if (!(map_get_sys_perms(map, f) & FMODE_CAN_WRITE)) {1152 if (!(map_get_sys_perms(map, f) & FMODE_CAN_WRITE)) {
1143 err = -EPERM;1153 err = -EPERM;
1144 goto err_put;1154 goto err_put;
@@ -1174,6 +1184,7 @@ free_value: · 29 unmodified lines
1174free_key:1184free_key:
1175 kvfree(key);1185 kvfree(key);
1176err_put:1186err_put:
1187 bpf_map_write_active_dec(map);
1177 fdput(f);1188 fdput(f);
1178 return err;1189 return err;
1179}1190}
@@ -1196,6 +1207,7 @@ static int map_delete_elem(union bpf_attr *attr) · 16 unmodified lines
1196 map = __bpf_map_get(f);1207 map = __bpf_map_get(f);
1197 if (IS_ERR(map))1208 if (IS_ERR(map))
1198 return PTR_ERR(map);1209 return PTR_ERR(map);
1210 bpf_map_write_active_inc(map);
1199 if (!(map_get_sys_perms(map, f) & FMODE_CAN_WRITE)) {1211 if (!(map_get_sys_perms(map, f) & FMODE_CAN_WRITE)) {
1200 err = -EPERM;1212 err = -EPERM;
1201 goto err_put;1213 goto err_put;
@@ -1226,6 +1238,7 @@ static int map_delete_elem(union bpf_attr *attr) · 24 unmodified lines
1226out:1238out:
1227 kvfree(key);1239 kvfree(key);
1228err_put:1240err_put:
1241 bpf_map_write_active_dec(map);
1229 fdput(f);1242 fdput(f);
1230 return err;1243 return err;
1231}1244}
@@ -1533,6 +1546,7 @@ static int map_lookup_and_delete_elem(union bpf_attr *attr) · 301 unmodified lines
1533 map = __bpf_map_get(f);1546 map = __bpf_map_get(f);
1534 if (IS_ERR(map))1547 if (IS_ERR(map))
1535 return PTR_ERR(map);1548 return PTR_ERR(map);
1549 bpf_map_write_active_inc(map);
1536 if (!(map_get_sys_perms(map, f) & FMODE_CAN_READ) ||1550 if (!(map_get_sys_perms(map, f) & FMODE_CAN_READ) ||
1537 !(map_get_sys_perms(map, f) & FMODE_CAN_WRITE)) {1551 !(map_get_sys_perms(map, f) & FMODE_CAN_WRITE)) {
1538 err = -EPERM;1552 err = -EPERM;
@@ -1597,6 +1611,7 @@ free_value: · 58 unmodified lines
1597free_key:1611free_key:
1598 kvfree(key);1612 kvfree(key);
1599err_put:1613err_put:
1614 bpf_map_write_active_dec(map);
1600 fdput(f);1615 fdput(f);
1601 return err;1616 return err;
1602}1617}
@@ -1624,8 +1639,7 @@ static int map_freeze(const union bpf_attr *attr) · 21 unmodified lines
1624 }1639 }
1625 1640
1626 mutex_lock(&map->freeze_mutex);1641 mutex_lock(&map->freeze_mutex);
1627 1642 if (bpf_map_write_active(map)) {
1628 if (map->writecnt) {
1629 err = -EBUSY;1643 err = -EBUSY;
1630 goto err_put;1644 goto err_put;
1631 }1645 }
@@ -4171,6 +4185,9 @@ static int bpf_map_do_batch(const union bpf_attr *attr, · 2539 unmodified lines
4171 union bpf_attr __user *uattr,4185 union bpf_attr __user *uattr,
4172 int cmd)4186 int cmd)
4173{4187{
4188 bool has_read = cmd == BPF_MAP_LOOKUP_BATCH ||
4189 cmd == BPF_MAP_LOOKUP_AND_DELETE_BATCH;
4190 bool has_write = cmd != BPF_MAP_LOOKUP_BATCH;
4174 struct bpf_map *map;4191 struct bpf_map *map;
4175 int err, ufd;4192 int err, ufd;
4176 struct fd f;4193 struct fd f;
@@ -4183,16 +4200,13 @@ static int bpf_map_do_batch(const union bpf_attr *attr, · 6 unmodified lines
4183 map = __bpf_map_get(f);4200 map = __bpf_map_get(f);
4184 if (IS_ERR(map))4201 if (IS_ERR(map))
4185 return PTR_ERR(map);4202 return PTR_ERR(map);
4186 4203 if (has_write)
4187 if ((cmd == BPF_MAP_LOOKUP_BATCH ||4204 bpf_map_write_active_inc(map);
4188 cmd == BPF_MAP_LOOKUP_AND_DELETE_BATCH) &&4205 if (has_read && !(map_get_sys_perms(map, f) & FMODE_CAN_READ)) {
4189 !(map_get_sys_perms(map, f) & FMODE_CAN_READ)) {
4190 err = -EPERM;4206 err = -EPERM;
4191 goto err_put;4207 goto err_put;
4192 }4208 }
4193 4209 if (has_write && !(map_get_sys_perms(map, f) & FMODE_CAN_WRITE)) {
4194 if (cmd != BPF_MAP_LOOKUP_BATCH &&
4195 !(map_get_sys_perms(map, f) & FMODE_CAN_WRITE)) {
4196 err = -EPERM;4210 err = -EPERM;
4197 goto err_put;4211 goto err_put;
4198 }4212 }
@@ -4205,8 +4219,9 @@ static int bpf_map_do_batch(const union bpf_attr *attr, · 6 unmodified lines
4205 BPF_DO_BATCH(map->ops->map_update_batch);4219 BPF_DO_BATCH(map->ops->map_update_batch);
4206 else4220 else
4207 BPF_DO_BATCH(map->ops->map_delete_batch);4221 BPF_DO_BATCH(map->ops->map_delete_batch);
4208
4209err_put:4222err_put:
4223 if (has_write)
4224 bpf_map_write_active_dec(map);
4210 fdput(f);4225 fdput(f);
4211 return err;4226 return err;
4212}4227}
kernel/bpf/verifier.c+16−1
@@ -4056,7 +4056,22 @@ static void coerce_reg_to_size(struct bpf_reg_state *reg, int size) · 4055 unmodified lines
4056 4056
4057static bool bpf_map_is_rdonly(const struct bpf_map *map)4057static bool bpf_map_is_rdonly(const struct bpf_map *map)
4058{4058{
4059 return (map->map_flags & BPF_F_RDONLY_PROG) && map->frozen;4059 /* A map is considered read-only if the following condition are true:
4060 *
4061 * 1) BPF program side cannot change any of the map content. The
4062 * BPF_F_RDONLY_PROG flag is throughout the lifetime of a map
4063 * and was set at map creation time.
4064 * 2) The map value(s) have been initialized from user space by a
4065 * loader and then "frozen", such that no new map update/delete
4066 * operations from syscall side are possible for the rest of
4067 * the map's lifetime from that point onwards.
4068 * 3) Any parallel/pending map update/delete operations from syscall
4069 * side have been completed. Only after that point, it's safe to
4070 * assume that map value(s) are immutable.
4071 */
4072 return (map->map_flags & BPF_F_RDONLY_PROG) &&
4073 READ_ONCE(map->frozen) &&
4074 !bpf_map_write_active(map);
4060}4075}
4061 4076
4062static int bpf_map_direct_read(struct bpf_map *map, int off, int size, u64 *val)4077static int bpf_map_direct_read(struct bpf_map *map, int off, int size, u64 *val)