diff mbox

mm/x86: AMD Bulldozer ASLR fix

Message ID 1427220048-6618-1-git-send-email-hecmargi@upv.es (mailing list archive)
State New, archived
Headers show

Commit Message

Hector Marco-Gisbert March 24, 2015, 6 p.m. UTC
A bug in Linux ASLR implementation which affects some AMD processors has been
found. The issue affects to all Linux process even if they are not using
shared libraries (statically compiled).  
 
The problem appears because some mmapped objects (VDSO, libraries, etc.)
are poorly randomized in an attempt to avoid cache aliasing penalties for
AMD Bulldozer (Family 15h) processors.

Affected systems have reduced the mmapped files entropy by eight. 

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


As shown in the previous output, the bits 12, 13 and 14 are always 0. The address
always ends in 0x8000 or 0x0000.

The bug is caused by a hack to improve performance by avoiding cache aliasing
penalties in the Family 15h of AMD Bulldozer processors (commit: dfb09f9b).

32-bit systems are specially sensitive to this issue because the entropy for
libraries is reduced from 2^8 to 2^5, which means that libraries only have 32
different places where they can be loaded.

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 the bits
[12..14], there is no cache aliasing problems (which is supposed to be the
cause of performance loss). On the other hand, since the value is not
known by a potential remote attacker, the ASLR preserves its effectiveness. 

More details at:

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


Signed-off-by: Hector Marco-Gisbert <hecmargi@upv.es>
Signed-off-by: Ismael Ripoll <iripoll@disca.upv.es>
---
 arch/x86/include/asm/amd_15h.h |  6 ++++++
 arch/x86/kernel/cpu/amd.c      |  5 +++++
 arch/x86/kernel/sys_x86_64.c   | 16 +++++++++++++---
 3 files changed, 24 insertions(+), 3 deletions(-)
 create mode 100644 arch/x86/include/asm/amd_15h.h

Comments

Borislav Petkov March 24, 2015, 7:15 p.m. UTC | #1
On Tue, Mar 24, 2015 at 07:00:48PM +0100, Hector Marco-Gisbert wrote:
> diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c
> index 15c5df9..a693d54 100644
> --- a/arch/x86/kernel/cpu/amd.c
> +++ b/arch/x86/kernel/cpu/amd.c
> @@ -5,6 +5,7 @@
>  
>  #include <linux/io.h>
>  #include <linux/sched.h>
> +#include <linux/random.h>
>  #include <asm/processor.h>
>  #include <asm/apic.h>
>  #include <asm/cpu.h>
> @@ -18,6 +19,8 @@
>  
>  #include "cpu.h"
>  
> +unsigned long rnd_bulldozer_bits = 0;
> +
>  static inline int rdmsrl_amd_safe(unsigned msr, unsigned long long *p)
>  {
>  	u32 gprs[8] = { 0 };
> @@ -488,6 +491,8 @@ static void bsp_init_amd(struct cpuinfo_x86 *c)
>  
>  		va_align.mask	  = (upperbit - 1) & PAGE_MASK;
>  		va_align.flags    = ALIGN_VA_32 | ALIGN_VA_64;
> +		/* A random value per boot for bits 12,13 and 14 */
> +		rnd_bulldozer_bits = get_random_int() & va_align.mask;

Hmm, this should be done differently:

va_align should have a ->bits member which gets ORed in into the hole
made my va_align.mask...

>  	}
>  }
>  
> diff --git a/arch/x86/kernel/sys_x86_64.c b/arch/x86/kernel/sys_x86_64.c
> index 30277e2..5b8ad01 100644
> --- a/arch/x86/kernel/sys_x86_64.c
> +++ b/arch/x86/kernel/sys_x86_64.c
> @@ -18,6 +18,7 @@
>  
>  #include <asm/ia32.h>
>  #include <asm/syscalls.h>
> +#include <asm/amd_15h.h>
>  
>  /*
>   * Align a virtual address to avoid aliasing in the I$ on AMD F15h.
> @@ -34,10 +35,16 @@ static unsigned long get_align_mask(void)
>  	return va_align.mask;
>  }
>  
> +static unsigned long get_bulldozer_bits(void){
> +
> +	return rnd_bulldozer_bits & get_align_mask();
> +}
> +
>  unsigned long align_vdso_addr(unsigned long addr)
>  {
>  	unsigned long align_mask = get_align_mask();
> -	return (addr + align_mask) & ~align_mask;
> +	addr = (addr + align_mask) & ~align_mask;
> +	return addr | get_bulldozer_bits();
>  }
>  
>  static int __init control_va_addr_alignment(char *str)
> @@ -137,7 +144,10 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr,
>  	info.high_limit = end;
>  	info.align_mask = filp ? get_align_mask() : 0;

	info.align_bits = get_align_bits() : 0;

>  	info.align_offset = pgoff << PAGE_SHIFT;
> -	return vm_unmapped_area(&info);
> +	addr = vm_unmapped_area(&info);
> +	if (!(addr & ~PAGE_MASK))
> +		return filp ? (addr|get_bulldozer_bits()) : addr;
> +	return addr;
>  }
>  
>  unsigned long
> @@ -178,7 +188,7 @@ arch_get_unmapped_area_topdown(struct file *filp, const unsigned long addr0,
>  	info.align_offset = pgoff << PAGE_SHIFT;
>  	addr = vm_unmapped_area(&info);
>  	if (!(addr & ~PAGE_MASK))
> -		return addr;
> +		return filp ? (addr|get_bulldozer_bits()) : addr;

Ditto.
Hector Marco-Gisbert March 25, 2015, 6:29 p.m. UTC | #2
El 24/03/15 a las 20:15, Borislav Petkov escribió:
> On Tue, Mar 24, 2015 at 07:00:48PM +0100, Hector Marco-Gisbert wrote:
>> diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c
>> index 15c5df9..a693d54 100644
>> --- a/arch/x86/kernel/cpu/amd.c
>> +++ b/arch/x86/kernel/cpu/amd.c
>> @@ -5,6 +5,7 @@
>>
>>   #include <linux/io.h>
>>   #include <linux/sched.h>
>> +#include <linux/random.h>
>>   #include <asm/processor.h>
>>   #include <asm/apic.h>
>>   #include <asm/cpu.h>
>> @@ -18,6 +19,8 @@
>>
>>   #include "cpu.h"
>>
>> +unsigned long rnd_bulldozer_bits = 0;
>> +
>>   static inline int rdmsrl_amd_safe(unsigned msr, unsigned long long *p)
>>   {
>>   	u32 gprs[8] = { 0 };
>> @@ -488,6 +491,8 @@ static void bsp_init_amd(struct cpuinfo_x86 *c)
>>
>>   		va_align.mask	  = (upperbit - 1) & PAGE_MASK;
>>   		va_align.flags    = ALIGN_VA_32 | ALIGN_VA_64;
>> +		/* A random value per boot for bits 12,13 and 14 */
>> +		rnd_bulldozer_bits = get_random_int() & va_align.mask;
>
> Hmm, this should be done differently:
>
> va_align should have a ->bits member which gets ORed in into the hole
> made my va_align.mask...
>

Yes, It looks better using va_align.

>>   	}
>>   }
>>
>> diff --git a/arch/x86/kernel/sys_x86_64.c b/arch/x86/kernel/sys_x86_64.c
>> index 30277e2..5b8ad01 100644
>> --- a/arch/x86/kernel/sys_x86_64.c
>> +++ b/arch/x86/kernel/sys_x86_64.c
>> @@ -18,6 +18,7 @@
>>
>>   #include <asm/ia32.h>
>>   #include <asm/syscalls.h>
>> +#include <asm/amd_15h.h>
>>
>>   /*
>>    * Align a virtual address to avoid aliasing in the I$ on AMD F15h.
>> @@ -34,10 +35,16 @@ static unsigned long get_align_mask(void)
>>   	return va_align.mask;
>>   }
>>
>> +static unsigned long get_bulldozer_bits(void){
>> +
>> +	return rnd_bulldozer_bits & get_align_mask();
>> +}
>> +
>>   unsigned long align_vdso_addr(unsigned long addr)
>>   {
>>   	unsigned long align_mask = get_align_mask();
>> -	return (addr + align_mask) & ~align_mask;
>> +	addr = (addr + align_mask) & ~align_mask;
>> +	return addr | get_bulldozer_bits();
>>   }
>>
>>   static int __init control_va_addr_alignment(char *str)
>> @@ -137,7 +144,10 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr,
>>   	info.high_limit = end;
>>   	info.align_mask = filp ? get_align_mask() : 0;
>
> 	info.align_bits = get_align_bits() : 0;
>

I see your point. The drawback of adding a new field (align_bits) to the info 
struct is that all architectures need to initialize the info.align_bits. In 
addition, the generic functions unmapped_area()/unmapped_area_topdown() in file 
mm/mmap.c, need to be modified to set the bits [12..14] using the new field 
info.align_bits.

A possible alternative which does not add a new field, is to use the 
"align_offset" variable. By adding the "get_align_bits()" value to the 
"align_offset" the bits [12..14] are randomized per boot.

Patch coming.
Hector.

>>   	info.align_offset = pgoff << PAGE_SHIFT;
>> -	return vm_unmapped_area(&info);
>> +	addr = vm_unmapped_area(&info);
>> +	if (!(addr & ~PAGE_MASK))
>> +		return filp ? (addr|get_bulldozer_bits()) : addr;
>> +	return addr;
>>   }
>>
>>   unsigned long
>> @@ -178,7 +188,7 @@ arch_get_unmapped_area_topdown(struct file *filp, const unsigned long addr0,
>>   	info.align_offset = pgoff << PAGE_SHIFT;
>>   	addr = vm_unmapped_area(&info);
>>   	if (!(addr & ~PAGE_MASK))
>> -		return addr;
>> +		return filp ? (addr|get_bulldozer_bits()) : addr;
>
> Ditto.
>
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/arch/x86/include/asm/amd_15h.h b/arch/x86/include/asm/amd_15h.h
new file mode 100644
index 0000000..3e6fb6e
--- /dev/null
+++ b/arch/x86/include/asm/amd_15h.h
@@ -0,0 +1,6 @@ 
+#ifndef _ASM_X86_AMD_15H_H
+#define _ASM_X86_AMD_15H_H
+
+extern unsigned long rnd_bulldozer_bits __read_mostly;
+
+#endif /* _ASM_X86_AMD_15H_H */
diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c
index 15c5df9..a693d54 100644
--- a/arch/x86/kernel/cpu/amd.c
+++ b/arch/x86/kernel/cpu/amd.c
@@ -5,6 +5,7 @@ 
 
 #include <linux/io.h>
 #include <linux/sched.h>
+#include <linux/random.h>
 #include <asm/processor.h>
 #include <asm/apic.h>
 #include <asm/cpu.h>
@@ -18,6 +19,8 @@ 
 
 #include "cpu.h"
 
+unsigned long rnd_bulldozer_bits = 0;
+
 static inline int rdmsrl_amd_safe(unsigned msr, unsigned long long *p)
 {
 	u32 gprs[8] = { 0 };
@@ -488,6 +491,8 @@  static void bsp_init_amd(struct cpuinfo_x86 *c)
 
 		va_align.mask	  = (upperbit - 1) & PAGE_MASK;
 		va_align.flags    = ALIGN_VA_32 | ALIGN_VA_64;
+		/* A random value per boot for bits 12,13 and 14 */
+		rnd_bulldozer_bits = get_random_int() & va_align.mask;
 	}
 }
 
diff --git a/arch/x86/kernel/sys_x86_64.c b/arch/x86/kernel/sys_x86_64.c
index 30277e2..5b8ad01 100644
--- a/arch/x86/kernel/sys_x86_64.c
+++ b/arch/x86/kernel/sys_x86_64.c
@@ -18,6 +18,7 @@ 
 
 #include <asm/ia32.h>
 #include <asm/syscalls.h>
+#include <asm/amd_15h.h>
 
 /*
  * Align a virtual address to avoid aliasing in the I$ on AMD F15h.
@@ -34,10 +35,16 @@  static unsigned long get_align_mask(void)
 	return va_align.mask;
 }
 
+static unsigned long get_bulldozer_bits(void){
+
+	return rnd_bulldozer_bits & get_align_mask();
+}
+
 unsigned long align_vdso_addr(unsigned long addr)
 {
 	unsigned long align_mask = get_align_mask();
-	return (addr + align_mask) & ~align_mask;
+	addr = (addr + align_mask) & ~align_mask;
+	return addr | get_bulldozer_bits();
 }
 
 static int __init control_va_addr_alignment(char *str)
@@ -137,7 +144,10 @@  arch_get_unmapped_area(struct file *filp, unsigned long addr,
 	info.high_limit = end;
 	info.align_mask = filp ? get_align_mask() : 0;
 	info.align_offset = pgoff << PAGE_SHIFT;
-	return vm_unmapped_area(&info);
+	addr = vm_unmapped_area(&info);
+	if (!(addr & ~PAGE_MASK))
+		return filp ? (addr|get_bulldozer_bits()) : addr;
+	return addr;
 }
 
 unsigned long
@@ -178,7 +188,7 @@  arch_get_unmapped_area_topdown(struct file *filp, const unsigned long addr0,
 	info.align_offset = pgoff << PAGE_SHIFT;
 	addr = vm_unmapped_area(&info);
 	if (!(addr & ~PAGE_MASK))
-		return addr;
+		return filp ? (addr|get_bulldozer_bits()) : addr;
 	VM_BUG_ON(addr != -ENOMEM);
 
 bottomup: