FAILED: patch "[PATCH] bpf: Fix toctou on read-only map's constant scalar tracking" failed to apply to 5.10-stable tree
Subsystems (3)
[email protected]
maintainerThe patch below does not apply to the 5.10-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 353050be4c19e102178ccc05988101887c25ae53 Mon Sep 17 00:00:00 2001
From: Daniel Borkmann <[email protected]>
Date: Tue, 9 Nov 2021 18:48:08 +0000
Subject: [PATCH] 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.
include/linux/bpf.h +2 -1
@@ -193,7 +193,7 @@ struct bpf_map {atomic64_t usercnt;struct work_struct work;struct mutex freeze_mutex; -u64 writecnt;/* writable mmap cnt; protected by freeze_mutex */ +atomic64_t writecnt; };static inline bool map_value_has_spin_lock (const struct bpf_map *map)
@@ -1419,6 +1419,7 @@ void bpf_map_put(struct bpf_map *map);void *bpf_map_area_alloc (u64 size,int numa_node);void *bpf_map_area_mmapable_alloc (u64 size,int numa_node);void bpf_map_area_free (void *base); +bool bpf_map_write_active (const struct bpf_map *map);void bpf_map_init_from_attr (struct bpf_map *map,union bpf_attr *attr); int generic_map_lookup_batch(struct bpf_map *map, const union bpf_attr *attr,
kernel/bpf/syscall.c +36 -21
145 lines not on this page · raw message
kernel/bpf/verifier.c +16 -1
@@ -4056,7 +4056,22 @@ static void coerce_reg_to_size(struct bpf_reg_state *reg, int size)static bool bpf_map_is_rdonly (const struct bpf_map *map) { -return (map->map_flags &BPF_F_RDONLY_PROG ) && map->frozen; +/* A map is considered read-only if the following condition are true: +* +* 1) BPF program side cannot change any of the map content. The +* BPF_F_RDONLY_PROG flag is throughout the lifetime of a map +* and was set at map creation time. +* 2) The map value(s) have been initialized from user space by a +* loader and then "frozen", such that no new map update/delete +* operations from syscall side are possible for the rest of +* the map's lifetime from that point onwards. +* 3) Any parallel/pending map update/delete operations from syscall +* side have been completed. Only after that point, it's safe to +* assume that map value(s) are immutable. +*/ +return (map->map_flags &BPF_F_RDONLY_PROG ) && +READ_ONCE (map->frozen) && + !bpf_map_write_active (map); }static int bpf_map_direct_read (struct bpf_map *map,int off,int size,u64 *val)
PATCH 1/1 bpf: Fix toctou on read-only map's constant scalar tracking
From: Daniel Borkmann <[email protected]>
commit 353050be4c19e102178ccc05988101887c25ae53 upstream
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.
[fix conflict to call bpf_map_write_active_dec() in err_put block. fix conflict to insert new functions after find_and_alloc_map().]
Reference: CVE-2021-4001
--- include/linux/bpf.h | 3 ++- kernel/bpf/syscall.c | 57 +++++++++++++++++++++++++++---------------- kernel/bpf/verifier.c | 17 ++++++++++++- 3 files changed, 54 insertions(+), 23 deletions(-)
include/linux/bpf.h +2 -1
@@ -173,7 +173,7 @@ struct bpf_map {atomic64_t usercnt;struct work_struct work;struct mutex freeze_mutex; -u64 writecnt;/* writable mmap cnt; protected by freeze_mutex */ +atomic64_t writecnt; };static inline bool map_value_has_spin_lock (const struct bpf_map *map)
@@ -1252,6 +1252,7 @@ void bpf_map_charge_move(struct bpf_map_memory *dst,void *bpf_map_area_alloc (u64 size,int numa_node);void *bpf_map_area_mmapable_alloc (u64 size,int numa_node);void bpf_map_area_free (void *base); +bool bpf_map_write_active (const struct bpf_map *map);void bpf_map_init_from_attr (struct bpf_map *map,union bpf_attr *attr); int generic_map_lookup_batch(struct bpf_map *map, const union bpf_attr *attr,
kernel/bpf/syscall.c +36 -21
145 lines not on this page · raw message
kernel/bpf/verifier.c +16 -1
@@ -3486,7 +3486,22 @@ static void coerce_reg_to_size(struct bpf_reg_state *reg, int size)static bool bpf_map_is_rdonly (const struct bpf_map *map) { -return (map->map_flags &BPF_F_RDONLY_PROG ) && map->frozen; +/* A map is considered read-only if the following condition are true: +* +* 1) BPF program side cannot change any of the map content. The +* BPF_F_RDONLY_PROG flag is throughout the lifetime of a map +* and was set at map creation time. +* 2) The map value(s) have been initialized from user space by a +* loader and then "frozen", such that no new map update/delete +* operations from syscall side are possible for the rest of +* the map's lifetime from that point onwards. +* 3) Any parallel/pending map update/delete operations from syscall +* side have been completed. Only after that point, it's safe to +* assume that map value(s) are immutable. +*/ +return (map->map_flags &BPF_F_RDONLY_PROG ) && +READ_ONCE (map->frozen) && + !bpf_map_write_active (map); }static int bpf_map_direct_read (struct bpf_map *map,int off,int size,u64 *val)
Signature · 1 line
2.33.1
Greg KH
maintainerRe: [PATCH] bpf: Fix toctou on read-only map's constant scalar tracking
Quoted 86 lines · Masami Ichikawa(CIP)
From: Daniel Borkmann <[email protected]>
commit
353050be4c19e102178ccc05988101887c25ae53upstreamCommit
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 viamap->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. Beforea23740ec43ba, 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_PROGmap flag is always fixed and required to be specified at map creation time, themap->frozenproperty 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 successfulBPF_MAP_FREEZEcmd return. Meaning, any new BPF_* cmd calls which would update/delete map entries will be rejected with-EPERMsincemap_get_sys_perms()removes theFMODE_CAN_WRITEpermission. 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 thatmap_update_elem()can modify the contents of the map aftermap_freeze()andbpf_prog_load()were executed. This works, because userfaultfd halts the parallel thread which triggered amap_update_elem()at the time where we copy key/value from the user buffer and this already passed theFMODE_CAN_WRITEcapability test given at that time the map was not "frozen". Then, the main thread performs themap_freeze()andbpf_prog_load(), and once that had completed successfully, the other thread is woken up to complete the pendingmap_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 executingmap_freeze()andbpf_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 infc9702273e2e("bpf: Addmmap()support forBPF_MAP_TYPE_ARRAY") and further refined in1f6cb19be2e2("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 writablemmap()mappings and then prevents/fails the freeze operation. Its semantics can be expanded beyond justmmap()by generally indicating ongoing write phases. This would essentially span any parallel regular and batched flavor of update/delete operation and then also havemap_freeze()fail with-EBUSY. For thecheck_mem_access()in the verifier we expand upon thebpf_map_is_rdonly()check ensuring that all last pending writes have completed viabpf_map_write_active()test. Once themap->frozenis set andbpf_map_write_active()indicates amap->writecntof 0 only then we are really guaranteed to use the map's data as known constants. Formap->frozenbeing 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->writecnthas been converted into a atomic64 in the fix in order to avoid a doublefreeze_mutexmutex_{un,}lock() pair when updatingmap->writecntin the various map update/delete BPF_* cmd flavors. Spanning thefreeze_mutexover entire map update/delete operations in syscall side would not be possible due to then causing everything to be serialized. Similarly, something likesynchronize_rcu()after settingmap->frozento 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 breakd66562fba1ce("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.Fixes a23740ec43ba ("bpf: Track contents of read-only maps as scalars")Reported-by [email protected]Signed-off-by Daniel Borkmann <[email protected]>Acked-by Andrii Nakryiko <[email protected]>Signed-off-by Alexei Starovoitov <[email protected]>[fix conflict to call
bpf_map_write_active_dec()inerr_putblock. fix conflict to insert new functions afterfind_and_alloc_map().]
Reference: CVE-2021-4001
Quoted 6 lines
Signed-off-by Masami Ichikawa(CIP) <[email protected]>--- include/linux/bpf.h | 3 ++- kernel/bpf/syscall.c | 57 +++++++++++++++++++++++++++---------------- kernel/bpf/verifier.c | 17 ++++++++++++- 3 files changed, 54 insertions(+), 23 deletions(-)
What stable tree(s) is this for?
thanks,
greg k-h
Re: [PATCH] bpf: Fix toctou on read-only map's constant scalar tracking
Quoted 95 lines · Greg KH
Quoted 86 lines · Masami Ichikawa(CIP)
From: Daniel Borkmann <[email protected]>
commit
353050be4c19e102178ccc05988101887c25ae53upstreamCommit
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 viamap->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. Beforea23740ec43ba, 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_PROGmap flag is always fixed and required to be specified at map creation time, themap->frozenproperty 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 successfulBPF_MAP_FREEZEcmd return. Meaning, any new BPF_* cmd calls which would update/delete map entries will be rejected with-EPERMsincemap_get_sys_perms()removes theFMODE_CAN_WRITEpermission. 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 thatmap_update_elem()can modify the contents of the map aftermap_freeze()andbpf_prog_load()were executed. This works, because userfaultfd halts the parallel thread which triggered amap_update_elem()at the time where we copy key/value from the user buffer and this already passed theFMODE_CAN_WRITEcapability test given at that time the map was not "frozen". Then, the main thread performs themap_freeze()andbpf_prog_load(), and once that had completed successfully, the other thread is woken up to complete the pendingmap_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 executingmap_freeze()andbpf_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 infc9702273e2e("bpf: Addmmap()support forBPF_MAP_TYPE_ARRAY") and further refined in1f6cb19be2e2("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 writablemmap()mappings and then prevents/fails the freeze operation. Its semantics can be expanded beyond justmmap()by generally indicating ongoing write phases. This would essentially span any parallel regular and batched flavor of update/delete operation and then also havemap_freeze()fail with-EBUSY. For thecheck_mem_access()in the verifier we expand upon thebpf_map_is_rdonly()check ensuring that all last pending writes have completed viabpf_map_write_active()test. Once themap->frozenis set andbpf_map_write_active()indicates amap->writecntof 0 only then we are really guaranteed to use the map's data as known constants. Formap->frozenbeing 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->writecnthas been converted into a atomic64 in the fix in order to avoid a doublefreeze_mutexmutex_{un,}lock() pair when updatingmap->writecntin the various map update/delete BPF_* cmd flavors. Spanning thefreeze_mutexover entire map update/delete operations in syscall side would not be possible due to then causing everything to be serialized. Similarly, something likesynchronize_rcu()after settingmap->frozento 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 breakd66562fba1ce("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.Fixes a23740ec43ba ("bpf: Track contents of read-only maps as scalars")Reported-by [email protected]Signed-off-by Daniel Borkmann <[email protected]>Acked-by Andrii Nakryiko <[email protected]>Signed-off-by Alexei Starovoitov <[email protected]>[fix conflict to call
bpf_map_write_active_dec()inerr_putblock. fix conflict to insert new functions afterfind_and_alloc_map().]
Reference: CVE-2021-4001Quoted 6 lines
Signed-off-by Masami Ichikawa(CIP) <[email protected]>--- include/linux/bpf.h | 3 ++- kernel/bpf/syscall.c | 57 +++++++++++++++++++++++++++---------------- kernel/bpf/verifier.c | 17 ++++++++++++- 3 files changed, 54 insertions(+), 23 deletions(-)
Quoted 2 lines
What stable tree(s) is this for?
I'm sorry that I forgot to specify stable tree name. This patch is for 5.10.y branch.
Quoted 3 lines
thanks,
greg k-h
Regrads,
Signature · 5 lines
/** * Masami Ichikawa * personal: [email protected] * fedora project: [email protected] */
Greg KH
maintainerRe: [PATCH] bpf: Fix toctou on read-only map's constant scalar tracking
Quoted 96 lines · Masami Ichikawa
Quoted 95 lines · Greg KH
Quoted 92 lines · Masami Ichikawa(CIP)
From: Daniel Borkmann <[email protected]>
commit
353050be4c19e102178ccc05988101887c25ae53upstreamCommit
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 viamap->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. Beforea23740ec43ba, 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_PROGmap flag is always fixed and required to be specified at map creation time, themap->frozenproperty 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 successfulBPF_MAP_FREEZEcmd return. Meaning, any new BPF_* cmd calls which would update/delete map entries will be rejected with-EPERMsincemap_get_sys_perms()removes theFMODE_CAN_WRITEpermission. 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 thatmap_update_elem()can modify the contents of the map aftermap_freeze()andbpf_prog_load()were executed. This works, because userfaultfd halts the parallel thread which triggered amap_update_elem()at the time where we copy key/value from the user buffer and this already passed theFMODE_CAN_WRITEcapability test given at that time the map was not "frozen". Then, the main thread performs themap_freeze()andbpf_prog_load(), and once that had completed successfully, the other thread is woken up to complete the pendingmap_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 executingmap_freeze()andbpf_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 infc9702273e2e("bpf: Addmmap()support forBPF_MAP_TYPE_ARRAY") and further refined in1f6cb19be2e2("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 writablemmap()mappings and then prevents/fails the freeze operation. Its semantics can be expanded beyond justmmap()by generally indicating ongoing write phases. This would essentially span any parallel regular and batched flavor of update/delete operation and then also havemap_freeze()fail with-EBUSY. For thecheck_mem_access()in the verifier we expand upon thebpf_map_is_rdonly()check ensuring that all last pending writes have completed viabpf_map_write_active()test. Once themap->frozenis set andbpf_map_write_active()indicates amap->writecntof 0 only then we are really guaranteed to use the map's data as known constants. Formap->frozenbeing 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->writecnthas been converted into a atomic64 in the fix in order to avoid a doublefreeze_mutexmutex_{un,}lock() pair when updatingmap->writecntin the various map update/delete BPF_* cmd flavors. Spanning thefreeze_mutexover entire map update/delete operations in syscall side would not be possible due to then causing everything to be serialized. Similarly, something likesynchronize_rcu()after settingmap->frozento 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 breakd66562fba1ce("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.Fixes a23740ec43ba ("bpf: Track contents of read-only maps as scalars")Reported-by [email protected]Signed-off-by Daniel Borkmann <[email protected]>Acked-by Andrii Nakryiko <[email protected]>Signed-off-by Alexei Starovoitov <[email protected]>[fix conflict to call
bpf_map_write_active_dec()inerr_putblock. fix conflict to insert new functions afterfind_and_alloc_map().]
Reference: CVE-2021-4001Signed-off-by Masami Ichikawa(CIP) <[email protected]>--- include/linux/bpf.h | 3 ++- kernel/bpf/syscall.c | 57 +++++++++++++++++++++++++++---------------- kernel/bpf/verifier.c | 17 ++++++++++++- 3 files changed, 54 insertions(+), 23 deletions(-)
Quoted 5 lines
Quoted 2 lines
What stable tree(s) is this for?
I'm sorry that I forgot to specify stable tree name. This patch is for 5.10.y branch.
Wonderful, I will go queue it up now.
Can you also provide a version for 5.4? It is needed there as well.
thanks,
greg k-h
Re: [PATCH] bpf: Fix toctou on read-only map's constant scalar tracking
Quoted 98 lines · Greg KH
Quoted 96 lines · Masami Ichikawa
Quoted 95 lines · Greg KH
Quoted 92 lines · Masami Ichikawa(CIP)
From: Daniel Borkmann <[email protected]>
commit
353050be4c19e102178ccc05988101887c25ae53upstreamCommit
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 viamap->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. Beforea23740ec43ba, 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_PROGmap flag is always fixed and required to be specified at map creation time, themap->frozenproperty 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 successfulBPF_MAP_FREEZEcmd return. Meaning, any new BPF_* cmd calls which would update/delete map entries will be rejected with-EPERMsincemap_get_sys_perms()removes theFMODE_CAN_WRITEpermission. 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 thatmap_update_elem()can modify the contents of the map aftermap_freeze()andbpf_prog_load()were executed. This works, because userfaultfd halts the parallel thread which triggered amap_update_elem()at the time where we copy key/value from the user buffer and this already passed theFMODE_CAN_WRITEcapability test given at that time the map was not "frozen". Then, the main thread performs themap_freeze()andbpf_prog_load(), and once that had completed successfully, the other thread is woken up to complete the pendingmap_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 executingmap_freeze()andbpf_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 infc9702273e2e("bpf: Addmmap()support forBPF_MAP_TYPE_ARRAY") and further refined in1f6cb19be2e2("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 writablemmap()mappings and then prevents/fails the freeze operation. Its semantics can be expanded beyond justmmap()by generally indicating ongoing write phases. This would essentially span any parallel regular and batched flavor of update/delete operation and then also havemap_freeze()fail with-EBUSY. For thecheck_mem_access()in the verifier we expand upon thebpf_map_is_rdonly()check ensuring that all last pending writes have completed viabpf_map_write_active()test. Once themap->frozenis set andbpf_map_write_active()indicates amap->writecntof 0 only then we are really guaranteed to use the map's data as known constants. Formap->frozenbeing 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->writecnthas been converted into a atomic64 in the fix in order to avoid a doublefreeze_mutexmutex_{un,}lock() pair when updatingmap->writecntin the various map update/delete BPF_* cmd flavors. Spanning thefreeze_mutexover entire map update/delete operations in syscall side would not be possible due to then causing everything to be serialized. Similarly, something likesynchronize_rcu()after settingmap->frozento 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 breakd66562fba1ce("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.Fixes a23740ec43ba ("bpf: Track contents of read-only maps as scalars")Reported-by [email protected]Signed-off-by Daniel Borkmann <[email protected]>Acked-by Andrii Nakryiko <[email protected]>Signed-off-by Alexei Starovoitov <[email protected]>[fix conflict to call
bpf_map_write_active_dec()inerr_putblock. fix conflict to insert new functions afterfind_and_alloc_map().]
Reference: CVE-2021-4001Signed-off-by Masami Ichikawa(CIP) <[email protected]>--- include/linux/bpf.h | 3 ++- kernel/bpf/syscall.c | 57 +++++++++++++++++++++++++++---------------- kernel/bpf/verifier.c | 17 ++++++++++++- 3 files changed, 54 insertions(+), 23 deletions(-)
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
What stable tree(s) is this for?
I'm sorry that I forgot to specify stable tree name. This patch is for 5.10.y branch.
Wonderful, I will go queue it up now.
Thank you.
Quoted 2 lines
Can you also provide a version for 5.4? It is needed there as well.
I tried backporting to 5.4.y branch but 353050 ("bpf: Fix toctou on read-only map's constant scalar tracking ") is based on several commits that don't exist in the stable/linux-5.4.y branch. At least I found following commits are needed.
- fc97022 ("bpf: Add mmap() support for BPF_MAP_TYPE_ARRAY")
- 1f6cb19 ("bpf: Prevent re-mmap()'ing BPF map as writable for initially r/o mapping")
- 196e8ca ("bpf: Switch bpf_map_{area_alloc,area_mmapable_alloc}() to u64 size ")
- cb4d03a ("bpf: Add generic support for lookup batch op")
So I couldn't simply apply 353050 to 5.4 .y branch. Sorry for inconvenient.
Quoted 3 lines
thanks,
greg k-h
Regards,
Signature · 5 lines
/** * Masami Ichikawa * personal: [email protected] * fedora project: [email protected] */
Greg KH
maintainerRe: [PATCH] bpf: Fix toctou on read-only map's constant scalar tracking
Quoted 125 lines · Masami Ichikawa
Quoted 98 lines · Greg KH
Quoted 96 lines · Masami Ichikawa
Quoted 95 lines · Greg KH
Quoted 92 lines · Masami Ichikawa(CIP)
From: Daniel Borkmann <[email protected]>
commit
353050be4c19e102178ccc05988101887c25ae53upstreamCommit
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 viamap->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. Beforea23740ec43ba, 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_PROGmap flag is always fixed and required to be specified at map creation time, themap->frozenproperty 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 successfulBPF_MAP_FREEZEcmd return. Meaning, any new BPF_* cmd calls which would update/delete map entries will be rejected with-EPERMsincemap_get_sys_perms()removes theFMODE_CAN_WRITEpermission. 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 thatmap_update_elem()can modify the contents of the map aftermap_freeze()andbpf_prog_load()were executed. This works, because userfaultfd halts the parallel thread which triggered amap_update_elem()at the time where we copy key/value from the user buffer and this already passed theFMODE_CAN_WRITEcapability test given at that time the map was not "frozen". Then, the main thread performs themap_freeze()andbpf_prog_load(), and once that had completed successfully, the other thread is woken up to complete the pendingmap_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 executingmap_freeze()andbpf_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 infc9702273e2e("bpf: Addmmap()support forBPF_MAP_TYPE_ARRAY") and further refined in1f6cb19be2e2("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 writablemmap()mappings and then prevents/fails the freeze operation. Its semantics can be expanded beyond justmmap()by generally indicating ongoing write phases. This would essentially span any parallel regular and batched flavor of update/delete operation and then also havemap_freeze()fail with-EBUSY. For thecheck_mem_access()in the verifier we expand upon thebpf_map_is_rdonly()check ensuring that all last pending writes have completed viabpf_map_write_active()test. Once themap->frozenis set andbpf_map_write_active()indicates amap->writecntof 0 only then we are really guaranteed to use the map's data as known constants. Formap->frozenbeing 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->writecnthas been converted into a atomic64 in the fix in order to avoid a doublefreeze_mutexmutex_{un,}lock() pair when updatingmap->writecntin the various map update/delete BPF_* cmd flavors. Spanning thefreeze_mutexover entire map update/delete operations in syscall side would not be possible due to then causing everything to be serialized. Similarly, something likesynchronize_rcu()after settingmap->frozento 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 breakd66562fba1ce("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.Fixes a23740ec43ba ("bpf: Track contents of read-only maps as scalars")Reported-by [email protected]Signed-off-by Daniel Borkmann <[email protected]>Acked-by Andrii Nakryiko <[email protected]>Signed-off-by Alexei Starovoitov <[email protected]>[fix conflict to call
bpf_map_write_active_dec()inerr_putblock. fix conflict to insert new functions afterfind_and_alloc_map().]
Reference: CVE-2021-4001Signed-off-by Masami Ichikawa(CIP) <[email protected]>--- include/linux/bpf.h | 3 ++- kernel/bpf/syscall.c | 57 +++++++++++++++++++++++++++---------------- kernel/bpf/verifier.c | 17 ++++++++++++- 3 files changed, 54 insertions(+), 23 deletions(-)Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
What stable tree(s) is this for?
I'm sorry that I forgot to specify stable tree name. This patch is for 5.10.y branch.
Wonderful, I will go queue it up now.
Thank you.
Quoted 2 lines
Can you also provide a version for 5.4? It is needed there as well.
I tried backporting to 5.4.y branch but 353050 ("bpf: Fix toctou on read-only map's constant scalar tracking ") is based on several commits that don't exist in the stable/linux-5.4.y branch. At least I found following commits are needed.
- fc97022 ("bpf: Add
mmap()support forBPF_MAP_TYPE_ARRAY")
- 1f6cb19 ("bpf: Prevent re-mmap()'ing BPF map as writable for initially r/o mapping")
- 196e8ca ("bpf: Switch bpf_map_{area_alloc,area_mmapable_alloc}() to u64 size ")
- cb4d03a ("bpf: Add generic support for lookup batch op")
Quoted 2 lines
So I couldn't simply apply 353050 to 5.4 .y branch. Sorry for inconvenient.
No worries, thanks for trying. If anyone really cares about this for the old 5.4 kernel tree, I'm sure they will provide a backported series.
thanks,
greg k-h
Re: [PATCH] bpf: Fix toctou on read-only map's constant scalar tracking
Quoted 127 lines · Greg KH
Quoted 125 lines · Masami Ichikawa
Quoted 98 lines · Greg KH
Quoted 96 lines · Masami Ichikawa
Quoted 95 lines · Greg KH
Quoted 92 lines · Masami Ichikawa(CIP)
From: Daniel Borkmann <[email protected]>
commit
353050be4c19e102178ccc05988101887c25ae53upstreamCommit
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 viamap->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. Beforea23740ec43ba, 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_PROGmap flag is always fixed and required to be specified at map creation time, themap->frozenproperty 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 successfulBPF_MAP_FREEZEcmd return. Meaning, any new BPF_* cmd calls which would update/delete map entries will be rejected with-EPERMsincemap_get_sys_perms()removes theFMODE_CAN_WRITEpermission. 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 thatmap_update_elem()can modify the contents of the map aftermap_freeze()andbpf_prog_load()were executed. This works, because userfaultfd halts the parallel thread which triggered amap_update_elem()at the time where we copy key/value from the user buffer and this already passed theFMODE_CAN_WRITEcapability test given at that time the map was not "frozen". Then, the main thread performs themap_freeze()andbpf_prog_load(), and once that had completed successfully, the other thread is woken up to complete the pendingmap_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 executingmap_freeze()andbpf_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 infc9702273e2e("bpf: Addmmap()support forBPF_MAP_TYPE_ARRAY") and further refined in1f6cb19be2e2("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 writablemmap()mappings and then prevents/fails the freeze operation. Its semantics can be expanded beyond justmmap()by generally indicating ongoing write phases. This would essentially span any parallel regular and batched flavor of update/delete operation and then also havemap_freeze()fail with-EBUSY. For thecheck_mem_access()in the verifier we expand upon thebpf_map_is_rdonly()check ensuring that all last pending writes have completed viabpf_map_write_active()test. Once themap->frozenis set andbpf_map_write_active()indicates amap->writecntof 0 only then we are really guaranteed to use the map's data as known constants. Formap->frozenbeing 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->writecnthas been converted into a atomic64 in the fix in order to avoid a doublefreeze_mutexmutex_{un,}lock() pair when updatingmap->writecntin the various map update/delete BPF_* cmd flavors. Spanning thefreeze_mutexover entire map update/delete operations in syscall side would not be possible due to then causing everything to be serialized. Similarly, something likesynchronize_rcu()after settingmap->frozento 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 breakd66562fba1ce("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.Fixes a23740ec43ba ("bpf: Track contents of read-only maps as scalars")Reported-by [email protected]Signed-off-by Daniel Borkmann <[email protected]>Acked-by Andrii Nakryiko <[email protected]>Signed-off-by Alexei Starovoitov <[email protected]>[fix conflict to call
bpf_map_write_active_dec()inerr_putblock. fix conflict to insert new functions afterfind_and_alloc_map().]
Reference: CVE-2021-4001Signed-off-by Masami Ichikawa(CIP) <[email protected]>--- include/linux/bpf.h | 3 ++- kernel/bpf/syscall.c | 57 +++++++++++++++++++++++++++---------------- kernel/bpf/verifier.c | 17 ++++++++++++- 3 files changed, 54 insertions(+), 23 deletions(-)Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
What stable tree(s) is this for?
I'm sorry that I forgot to specify stable tree name. This patch is for 5.10.y branch.
Wonderful, I will go queue it up now.
Thank you.
Quoted 2 lines
Can you also provide a version for 5.4? It is needed there as well.
I tried backporting to 5.4.y branch but 353050 ("bpf: Fix toctou on read-only map's constant scalar tracking ") is based on several commits that don't exist in the stable/linux-5.4.y branch. At least I found following commits are needed.
- fc97022 ("bpf: Add
mmap()support forBPF_MAP_TYPE_ARRAY")
- 1f6cb19 ("bpf: Prevent re-mmap()'ing BPF map as writable for initially r/o mapping")
- 196e8ca ("bpf: Switch bpf_map_{area_alloc,area_mmapable_alloc}() to u64 size ")
- cb4d03a ("bpf: Add generic support for lookup batch op")
Quoted 6 lines
Quoted 2 lines
So I couldn't simply apply 353050 to 5.4 .y branch. Sorry for inconvenient.
No worries, thanks for trying. If anyone really cares about this for the old 5.4 kernel tree, I'm sure they will provide a backported series.
I see. thanks.
Quoted 3 lines
thanks,
greg k-h
Regards,
Signature · 5 lines
/** * Masami Ichikawa * personal: [email protected] * fedora project: [email protected] */