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

x86/mm: Improve AMD Bulldozer ASLR workaround

The ASLR implementation needs to special-case AMD F15h processors by
clearing out bits [14:12] of the virtual address in order to avoid I$
cross invalidations and thus performance penalty for certain workloads.
For details, see:

dfb09f9b7ab0 ("x86, amd: Avoid cache aliasing penalties on AMD family 15h")

This special case reduces the mmapped file's entropy by 3 bits.

The following output is the run on an AMD Opteron 62xx class CPU
processor under x86_64 Linux 4.0.0:

$ for i in `seq 1 10`; do cat /proc/self/maps | grep "r-xp.*libc" ; done
b7588000-b7736000 r-xp 00000000 00:01 4924 /lib/i386-linux-gnu/libc.so.6
b7570000-b771e000 r-xp 00000000 00:01 4924 /lib/i386-linux-gnu/libc.so.6
b75d0000-b777e000 r-xp 00000000 00:01 4924 /lib/i386-linux-gnu/libc.so.6
b75b0000-b775e000 r-xp 00000000 00:01 4924 /lib/i386-linux-gnu/libc.so.6
b7578000-b7726000 r-xp 00000000 00:01 4924 /lib/i386-linux-gnu/libc.so.6
...

Bits [12:14] are always 0, i.e. the address always ends in 0x8000 or
0x0000.

32-bit systems, as in the example above, are especially sensitive
to this issue because 32-bit randomness for VA space is 8 bits (see
mmap_rnd()). With the Bulldozer special case, this diminishes to only 32
different slots of mmap virtual addresses.

This patch randomizes per boot the three affected bits rather than
setting them to zero. Since all the shared pages have the same value
at bits [12..14], there is no cache aliasing problems. This value gets
generated during system boot and it is thus not known to a potential
remote attacker. Therefore, the impact from the Bulldozer workaround
gets diminished and ASLR randomness increased.

More details at:

http://hmarco.org/bugs/AMD-Bulldozer-linux-ASLR-weakness-reducing-mmaped-files-by-eight.html

Original white paper by AMD dealing with the issue:

http://developer.amd.com/wordpress/media/2012/10/SharedL1InstructionCacheonAMD15hCPU.pdf

Mentored-by: Ismael Ripoll <iripoll@disca.upv.es>
Signed-off-by: Hector Marco-Gisbert <hecmargi@upv.es>
Signed-off-by: Borislav Petkov <bp@suse.de>
Acked-by: Kees Cook <keescook@chromium.org>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Jan-Simon <dl9pf@gmx.de>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: linux-fsdevel@vger.kernel.org
Link: http://lkml.kernel.org/r/1427456301-3764-1-git-send-email-hecmargi@upv.es
Signed-off-by: Ingo Molnar <mingo@kernel.org>

authored by

Hector Marco-Gisbert and committed by
Ingo Molnar
4e26d11f c709feda

+32 -3
+1
arch/x86/include/asm/elf.h
··· 365 365 struct va_alignment { 366 366 int flags; 367 367 unsigned long mask; 368 + unsigned long bits; 368 369 } ____cacheline_aligned; 369 370 370 371 extern struct va_alignment va_align;
+4
arch/x86/kernel/cpu/amd.c
··· 5 5 6 6 #include <linux/io.h> 7 7 #include <linux/sched.h> 8 + #include <linux/random.h> 8 9 #include <asm/processor.h> 9 10 #include <asm/apic.h> 10 11 #include <asm/cpu.h> ··· 489 488 490 489 va_align.mask = (upperbit - 1) & PAGE_MASK; 491 490 va_align.flags = ALIGN_VA_32 | ALIGN_VA_64; 491 + 492 + /* A random value per boot for bit slice [12:upper_bit) */ 493 + va_align.bits = get_random_int() & va_align.mask; 492 494 } 493 495 } 494 496
+27 -3
arch/x86/kernel/sys_x86_64.c
··· 34 34 return va_align.mask; 35 35 } 36 36 37 + /* 38 + * To avoid aliasing in the I$ on AMD F15h, the bits defined by the 39 + * va_align.bits, [12:upper_bit), are set to a random value instead of 40 + * zeroing them. This random value is computed once per boot. This form 41 + * of ASLR is known as "per-boot ASLR". 42 + * 43 + * To achieve this, the random value is added to the info.align_offset 44 + * value before calling vm_unmapped_area() or ORed directly to the 45 + * address. 46 + */ 47 + static unsigned long get_align_bits(void) 48 + { 49 + return va_align.bits & get_align_mask(); 50 + } 51 + 37 52 unsigned long align_vdso_addr(unsigned long addr) 38 53 { 39 54 unsigned long align_mask = get_align_mask(); 40 - return (addr + align_mask) & ~align_mask; 55 + addr = (addr + align_mask) & ~align_mask; 56 + return addr | get_align_bits(); 41 57 } 42 58 43 59 static int __init control_va_addr_alignment(char *str) ··· 151 135 info.length = len; 152 136 info.low_limit = begin; 153 137 info.high_limit = end; 154 - info.align_mask = filp ? get_align_mask() : 0; 138 + info.align_mask = 0; 155 139 info.align_offset = pgoff << PAGE_SHIFT; 140 + if (filp) { 141 + info.align_mask = get_align_mask(); 142 + info.align_offset += get_align_bits(); 143 + } 156 144 return vm_unmapped_area(&info); 157 145 } 158 146 ··· 194 174 info.length = len; 195 175 info.low_limit = PAGE_SIZE; 196 176 info.high_limit = mm->mmap_base; 197 - info.align_mask = filp ? get_align_mask() : 0; 177 + info.align_mask = 0; 198 178 info.align_offset = pgoff << PAGE_SHIFT; 179 + if (filp) { 180 + info.align_mask = get_align_mask(); 181 + info.align_offset += get_align_bits(); 182 + } 199 183 addr = vm_unmapped_area(&info); 200 184 if (!(addr & ~PAGE_MASK)) 201 185 return addr;