]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
bpf: Prevent mmap()'ing read-only maps as writable
authorAndrii Nakryiko <andriin@fb.com>
Tue, 19 May 2020 05:38:24 +0000 (22:38 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 27 May 2020 15:48:30 +0000 (17:48 +0200)
[ Upstream commit dfeb376dd4cb2c5004aeb625e2475f58a5ff2ea7 ]

As discussed in [0], it's dangerous to allow mapping BPF map, that's meant to
be frozen and is read-only on BPF program side, because that allows user-space
to actually store a writable view to the page even after it is frozen. This is
exacerbated by BPF verifier making a strong assumption that contents of such
frozen map will remain unchanged. To prevent this, disallow mapping
BPF_F_RDONLY_PROG mmap()'able BPF maps as writable, ever.

  [0] https://lore.kernel.org/bpf/CAEf4BzYGWYhXdp6BJ7_=9OQPJxQpgug080MMjdSB72i9R+5c6g@mail.gmail.com/

Fixes: fc9702273e2e ("bpf: Add mmap() support for BPF_MAP_TYPE_ARRAY")
Suggested-by: Jann Horn <jannh@google.com>
Signed-off-by: Andrii Nakryiko <andriin@fb.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Reviewed-by: Jann Horn <jannh@google.com>
Link: https://lore.kernel.org/bpf/20200519053824.1089415-1-andriin@fb.com
Signed-off-by: Sasha Levin <sashal@kernel.org>
kernel/bpf/syscall.c
tools/testing/selftests/bpf/prog_tests/mmap.c
tools/testing/selftests/bpf/progs/test_mmap.c

index e04ea4c8f9358073d2d6b5aabbde8c9513928f9c..c0ab9bfdf28aa087623c9c6ce899899fe3e74ab3 100644 (file)
@@ -629,9 +629,20 @@ static int bpf_map_mmap(struct file *filp, struct vm_area_struct *vma)
 
        mutex_lock(&map->freeze_mutex);
 
-       if ((vma->vm_flags & VM_WRITE) && map->frozen) {
-               err = -EPERM;
-               goto out;
+       if (vma->vm_flags & VM_WRITE) {
+               if (map->frozen) {
+                       err = -EPERM;
+                       goto out;
+               }
+               /* map is meant to be read-only, so do not allow mapping as
+                * writable, because it's possible to leak a writable page
+                * reference and allows user-space to still modify it after
+                * freezing, while verifier will assume contents do not change
+                */
+               if (map->map_flags & BPF_F_RDONLY_PROG) {
+                       err = -EACCES;
+                       goto out;
+               }
        }
 
        /* set default open/close callbacks */
index b0e789678aa46cfdbddae2684f33a77da11efb15..5495b669fccc1de73d457f81c34e98602433cbeb 100644 (file)
@@ -19,7 +19,7 @@ void test_mmap(void)
        const size_t map_sz = roundup_page(sizeof(struct map_data));
        const int zero = 0, one = 1, two = 2, far = 1500;
        const long page_size = sysconf(_SC_PAGE_SIZE);
-       int err, duration = 0, i, data_map_fd;
+       int err, duration = 0, i, data_map_fd, rdmap_fd;
        struct bpf_map *data_map, *bss_map;
        void *bss_mmaped = NULL, *map_mmaped = NULL, *tmp1, *tmp2;
        struct test_mmap__bss *bss_data;
@@ -36,6 +36,17 @@ void test_mmap(void)
        data_map = skel->maps.data_map;
        data_map_fd = bpf_map__fd(data_map);
 
+       rdmap_fd = bpf_map__fd(skel->maps.rdonly_map);
+       tmp1 = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, rdmap_fd, 0);
+       if (CHECK(tmp1 != MAP_FAILED, "rdonly_write_mmap", "unexpected success\n")) {
+               munmap(tmp1, 4096);
+               goto cleanup;
+       }
+       /* now double-check if it's mmap()'able at all */
+       tmp1 = mmap(NULL, 4096, PROT_READ, MAP_SHARED, rdmap_fd, 0);
+       if (CHECK(tmp1 == MAP_FAILED, "rdonly_read_mmap", "failed: %d\n", errno))
+               goto cleanup;
+
        bss_mmaped = mmap(NULL, bss_sz, PROT_READ | PROT_WRITE, MAP_SHARED,
                          bpf_map__fd(bss_map), 0);
        if (CHECK(bss_mmaped == MAP_FAILED, "bss_mmap",
index 6239596cd14e635866829ff7f9668191b3b3f714..4eb42cff5fe966e8405770672daa6aaaf82457f4 100644 (file)
@@ -7,6 +7,14 @@
 
 char _license[] SEC("license") = "GPL";
 
+struct {
+       __uint(type, BPF_MAP_TYPE_ARRAY);
+       __uint(max_entries, 4096);
+       __uint(map_flags, BPF_F_MMAPABLE | BPF_F_RDONLY_PROG);
+       __type(key, __u32);
+       __type(value, char);
+} rdonly_map SEC(".maps");
+
 struct {
        __uint(type, BPF_MAP_TYPE_ARRAY);
        __uint(max_entries, 512 * 4); /* at least 4 pages of data */