T Topi Miettinen #
Message details and related content
Memory mappings inside kernel allocated with vmalloc() are in predictable order and packed tightly toward the low addresses, except for per-cpu areas which start from top of the vmalloc area. With new kernel boot parameter 'randomize_vmalloc=1', the entire area is used randomly to make the allocations less predictable and harder to guess for attackers. Also module and BPF code locations get randomized (within their dedicated and rather small area though) and if CONFIG_VMAP_STACK is enabled, also kernel thread stack locations. On 32 bit systems this may cause problems due to increased VM fragmentation if the address space gets crowded. On all systems, it will reduce performance and increase memory and cache usage due to less efficient use of page tables and inability to merge adjacent VMAs with compatible attributes. On x86_64 with 5 level page tables, in the worst case, additional page table entries of up to 4 pages are created for each mapping, so with small mappings there's considerable penalty. Without randomize_vmalloc=1: $ grep -v kernel_clone /proc/vmallocinfo 0xffffc90000000000-0xffffc90000009000 36864 irq_init_percpu_irqstack+0x176/0x1c0 vmap 0xffffc90000009000-0xffffc9000000b000 8192 acpi_os_map_iomem+0x2ac/0x2d0 phys=0x000000001ffe1000 ioremap 0xffffc9000000c000-0xffffc9000000f000 12288 acpi_os_map_iomem+0x2ac/0x2d0 phys=0x000000001ffe0000 ioremap 0xffffc9000000f000-0xffffc90000011000 8192 hpet_enable+0x31/0x4a4 phys=0x00000000fed00000 ioremap 0xffffc90000011000-0xffffc90000013000 8192 gen_pool_add_owner+0x49/0x130 pages=1 vmalloc 0xffffc90000013000-0xffffc90000015000 8192 gen_pool_add_owner+0x49/0x130 pages=1 vmalloc 0xffffc90000015000-0xffffc90000017000 8192 gen_pool_add_owner+0x49/0x130 pages=1 vmalloc 0xffffc90000021000-0xffffc90000023000 8192 gen_pool_add_owner+0x49/0x130 pages=1 vmalloc 0xffffc90000023000-0xffffc90000025000 8192 acpi_os_map_iomem+0x2ac/0x2d0 phys=0x00000000fed00000 ioremap 0xffffc90000025000-0xffffc90000027000 8192 memremap+0x19c/0x280 phys=0x00000000000f5000 ioremap 0xffffc90000031000-0xffffc90000036000 20480 pcpu_create_chunk+0xe8/0x260 pages=4 vmalloc 0xffffc90000043000-0xffffc90000047000 16384 n_tty_open+0x11/0xe0 pages=3 vmalloc 0xffffc90000211000-0xffffc90000232000 135168 crypto_scomp_init_tfm+0xc6/0xf0 pages=32 vmalloc 0xffffc90000232000-0xffffc90000253000 135168 crypto_scomp_init_tfm+0x67/0xf0 pages=32 vmalloc 0xffffc900005a9000-0xffffc900005ba000 69632 pcpu_create_chunk+0x7b/0x260 pages=16 vmalloc 0xffffc900005ba000-0xffffc900005cc000 73728 pcpu_create_chunk+0xb2/0x260 pages=17 vmalloc 0xffffe8ffffc00000-0xffffe8ffffe00000 2097152 pcpu_get_vm_areas+0x0/0x2290 vmalloc With randomize_vmalloc=1, the allocations are randomized:
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index c722ec19cd00..38d6b5728ccc 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt@@ -4024,6 +4024,29 @@ ramdisk_start= [RAM] RAM disk image start address + randomize_vmalloc= [KNL] Randomize vmalloc() allocations. With 1, + the entire vmalloc() area is used randomly to + make the allocations less predictable and + harder to guess for attackers. Also module and + BPF code locations get randomized (within + their dedicated and rather small area though) + and if CONFIG_VMAP_STACK is enabled, also + kernel thread stack locations. + + On 32 bit systems this may cause problems due + to increased VM fragmentation if the address + space gets crowded. + + On all systems, it will reduce performance and + increase memory and cache usage due to less + efficient use of page tables and inability to + merge adjacent VMAs with compatible + attributes. On x86_64 with 5 level page + tables, in the worst case, additional page + table entries of up to 4 pages are created for + each mapping, so with small mappings there's + considerable penalty. + random.trust_cpu={on,off} [KNL] Enable or disable trusting the use of the CPU's random number generator (if available) to
diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index 4d88fe5a277a..1e8e0ee1925f 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c@@ -34,6 +34,7 @@ #include <linux/bitops.h> #include <linux/rbtree_augmented.h> #include <linux/overflow.h> +#include <linux/random.h> #include <linux/uaccess.h> #include <asm/tlbflush.h>
@@ -1089,6 +1090,17 @@ adjust_va_to_fit_type(struct vmap_area *va, return 0; } +static int randomize_vmalloc = 0; + +static int __init set_randomize_vmalloc(char *str) +{ + if (!str) + return 0; + randomize_vmalloc = simple_strtoul(str, &str, 0); + return 1; +} +__setup("randomize_vmalloc=", set_randomize_vmalloc); + /* * Returns a start address of the newly allocated area, if success. * Otherwise a vend is returned that indicates failure.
@@ -1162,7 +1174,7 @@ static struct vmap_area *alloc_vmap_area(unsigned long size, int node, gfp_t gfp_mask) { struct vmap_area *va, *pva; - unsigned long addr; + unsigned long addr, voffset; int purged = 0; int ret;
@@ -1217,11 +1229,24 @@ static struct vmap_area *alloc_vmap_area(unsigned long size, if (pva && __this_cpu_cmpxchg(ne_fit_preload_node, NULL, pva)) kmem_cache_free(vmap_area_cachep, pva); + /* Randomize allocation */ + if (randomize_vmalloc) { + voffset = get_random_long() & (roundup_pow_of_two(vend - vstart) - 1); + voffset = PAGE_ALIGN(voffset); + if (voffset + size > vend - vstart) + voffset = vend - vstart - size; + } else + voffset = 0; + /* * If an allocation fails, the "vend" address is * returned. Therefore trigger the overflow path. */ - addr = __alloc_vmap_area(size, align, vstart, vend); + addr = __alloc_vmap_area(size, align, vstart + voffset, vend); + + if (unlikely(addr == vend) && voffset) + /* Retry randomization from other end */ + addr = __alloc_vmap_area(size, align, vstart, vstart + voffset + size); spin_unlock(&free_vmap_area_lock); if (unlikely(addr == vend))
@@ -3256,7 +3281,12 @@ struct vm_struct **pcpu_get_vm_areas(const unsigned long *offsets, start = offsets[area]; end = start + sizes[area]; - va = pvm_find_va_enclose_addr(vmalloc_end); + if (randomize_vmalloc) + va = pvm_find_va_enclose_addr(vmalloc_start + + (get_random_long() & + (roundup_pow_of_two(vmalloc_end - vmalloc_start) - 1))); + else + va = pvm_find_va_enclose_addr(vmalloc_end); base = pvm_determine_end_from_reverse(&va, align) - end; while (true) {
--
2.30.0