Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Merge tag 'powerpc-5.2-6' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux

Pull powerpc fixes from Michael Ellerman:
"One fix for a bug in our context id handling on 64-bit hash CPUs,
which can lead to unrelated processes being able to read/write to each
other's virtual memory. See the commit for full details.

That is the fix for CVE-2019-12817.

This also adds a kernel selftest for the bug"

* tag 'powerpc-5.2-6' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux:
selftests/powerpc: Add test of fork with mapping above 512TB
powerpc/mm/64s/hash: Reallocate context ids on fork

+138 -9
+47 -8
arch/powerpc/mm/book3s64/mmu_context.c
··· 50 50 51 51 void slb_setup_new_exec(void); 52 52 53 + static int realloc_context_ids(mm_context_t *ctx) 54 + { 55 + int i, id; 56 + 57 + /* 58 + * id 0 (aka. ctx->id) is special, we always allocate a new one, even if 59 + * there wasn't one allocated previously (which happens in the exec 60 + * case where ctx is newly allocated). 61 + * 62 + * We have to be a bit careful here. We must keep the existing ids in 63 + * the array, so that we can test if they're non-zero to decide if we 64 + * need to allocate a new one. However in case of error we must free the 65 + * ids we've allocated but *not* any of the existing ones (or risk a 66 + * UAF). That's why we decrement i at the start of the error handling 67 + * loop, to skip the id that we just tested but couldn't reallocate. 68 + */ 69 + for (i = 0; i < ARRAY_SIZE(ctx->extended_id); i++) { 70 + if (i == 0 || ctx->extended_id[i]) { 71 + id = hash__alloc_context_id(); 72 + if (id < 0) 73 + goto error; 74 + 75 + ctx->extended_id[i] = id; 76 + } 77 + } 78 + 79 + /* The caller expects us to return id */ 80 + return ctx->id; 81 + 82 + error: 83 + for (i--; i >= 0; i--) { 84 + if (ctx->extended_id[i]) 85 + ida_free(&mmu_context_ida, ctx->extended_id[i]); 86 + } 87 + 88 + return id; 89 + } 90 + 53 91 static int hash__init_new_context(struct mm_struct *mm) 54 92 { 55 93 int index; 56 94 57 - index = hash__alloc_context_id(); 58 - if (index < 0) 59 - return index; 60 - 61 95 mm->context.hash_context = kmalloc(sizeof(struct hash_mm_context), 62 96 GFP_KERNEL); 63 - if (!mm->context.hash_context) { 64 - ida_free(&mmu_context_ida, index); 97 + if (!mm->context.hash_context) 65 98 return -ENOMEM; 66 - } 67 99 68 100 /* 69 101 * The old code would re-promote on fork, we don't do that when using ··· 123 91 mm->context.hash_context->spt = kmalloc(sizeof(struct subpage_prot_table), 124 92 GFP_KERNEL); 125 93 if (!mm->context.hash_context->spt) { 126 - ida_free(&mmu_context_ida, index); 127 94 kfree(mm->context.hash_context); 128 95 return -ENOMEM; 129 96 } 130 97 } 131 98 #endif 99 + } 132 100 101 + index = realloc_context_ids(&mm->context); 102 + if (index < 0) { 103 + #ifdef CONFIG_PPC_SUBPAGE_PROT 104 + kfree(mm->context.hash_context->spt); 105 + #endif 106 + kfree(mm->context.hash_context); 107 + return index; 133 108 } 134 109 135 110 pkey_mm_init(mm);
+1
tools/testing/selftests/powerpc/mm/.gitignore
··· 4 4 prot_sao 5 5 segv_errors 6 6 wild_bctr 7 + large_vm_fork_separation
+3 -1
tools/testing/selftests/powerpc/mm/Makefile
··· 2 2 noarg: 3 3 $(MAKE) -C ../ 4 4 5 - TEST_GEN_PROGS := hugetlb_vs_thp_test subpage_prot prot_sao segv_errors wild_bctr 5 + TEST_GEN_PROGS := hugetlb_vs_thp_test subpage_prot prot_sao segv_errors wild_bctr \ 6 + large_vm_fork_separation 6 7 TEST_GEN_FILES := tempfile 7 8 8 9 top_srcdir = ../../../../.. ··· 14 13 $(OUTPUT)/prot_sao: ../utils.c 15 14 16 15 $(OUTPUT)/wild_bctr: CFLAGS += -m64 16 + $(OUTPUT)/large_vm_fork_separation: CFLAGS += -m64 17 17 18 18 $(OUTPUT)/tempfile: 19 19 dd if=/dev/zero of=$@ bs=64k count=1
+87
tools/testing/selftests/powerpc/mm/large_vm_fork_separation.c
··· 1 + // SPDX-License-Identifier: GPL-2.0+ 2 + // 3 + // Copyright 2019, Michael Ellerman, IBM Corp. 4 + // 5 + // Test that allocating memory beyond the memory limit and then forking is 6 + // handled correctly, ie. the child is able to access the mappings beyond the 7 + // memory limit and the child's writes are not visible to the parent. 8 + 9 + #include <stdio.h> 10 + #include <stdlib.h> 11 + #include <sys/mman.h> 12 + #include <sys/types.h> 13 + #include <sys/wait.h> 14 + #include <unistd.h> 15 + 16 + #include "utils.h" 17 + 18 + 19 + #ifndef MAP_FIXED_NOREPLACE 20 + #define MAP_FIXED_NOREPLACE MAP_FIXED // "Should be safe" above 512TB 21 + #endif 22 + 23 + 24 + static int test(void) 25 + { 26 + int p2c[2], c2p[2], rc, status, c, *p; 27 + unsigned long page_size; 28 + pid_t pid; 29 + 30 + page_size = sysconf(_SC_PAGESIZE); 31 + SKIP_IF(page_size != 65536); 32 + 33 + // Create a mapping at 512TB to allocate an extended_id 34 + p = mmap((void *)(512ul << 40), page_size, PROT_READ | PROT_WRITE, 35 + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0); 36 + if (p == MAP_FAILED) { 37 + perror("mmap"); 38 + printf("Error: couldn't mmap(), confirm kernel has 4TB support?\n"); 39 + return 1; 40 + } 41 + 42 + printf("parent writing %p = 1\n", p); 43 + *p = 1; 44 + 45 + FAIL_IF(pipe(p2c) == -1 || pipe(c2p) == -1); 46 + 47 + pid = fork(); 48 + if (pid == 0) { 49 + FAIL_IF(read(p2c[0], &c, 1) != 1); 50 + 51 + pid = getpid(); 52 + printf("child writing %p = %d\n", p, pid); 53 + *p = pid; 54 + 55 + FAIL_IF(write(c2p[1], &c, 1) != 1); 56 + FAIL_IF(read(p2c[0], &c, 1) != 1); 57 + exit(0); 58 + } 59 + 60 + c = 0; 61 + FAIL_IF(write(p2c[1], &c, 1) != 1); 62 + FAIL_IF(read(c2p[0], &c, 1) != 1); 63 + 64 + // Prevent compiler optimisation 65 + barrier(); 66 + 67 + rc = 0; 68 + printf("parent reading %p = %d\n", p, *p); 69 + if (*p != 1) { 70 + printf("Error: BUG! parent saw child's write! *p = %d\n", *p); 71 + rc = 1; 72 + } 73 + 74 + FAIL_IF(write(p2c[1], &c, 1) != 1); 75 + FAIL_IF(waitpid(pid, &status, 0) == -1); 76 + FAIL_IF(!WIFEXITED(status) || WEXITSTATUS(status)); 77 + 78 + if (rc == 0) 79 + printf("success: test completed OK\n"); 80 + 81 + return rc; 82 + } 83 + 84 + int main(void) 85 + { 86 + return test_harness(test, "large_vm_fork_separation"); 87 + }