lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:	Fri, 12 Aug 2011 18:19:40 -0500
From:	"H. Peter Anvin" <hpa@...or.com>
To:	Vasiliy Kulikov <segoon@...nwall.com>,
	Thomas Gleixner <tglx@...utronix.de>,
	Ingo Molnar <mingo@...hat.com>
CC:	kernel-hardening@...ts.openwall.com,
	Peter Zijlstra <peterz@...radead.org>,
	Andrew Morton <akpm@...ux-foundation.org>, x86@...nel.org,
	linux-kernel@...r.kernel.org, linux-mm@...ck.org
Subject: Re: [RFC] x86, mm: start mmap allocation for libs from low addresses

Vasiliy Kulikov <segoon@...nwall.com> wrote:

>This patch changes mmap base address allocator logic to incline to
>allocate addresses from the first 16 Mbs of address space.  These
>addresses start from zero byte (0x00AABBCC).  Using such addresses
>breaks ret2libc exploits abusing string buffer overflows.  As library
>addresses contain zero byte, addresses of library functions may not
>present in the string, which is used to overflow the buffer.  As a
>result, it makes it impossible to change the return address on the
>stack
>to the address of some library function (e.g. system(3)).
>
>The logic is applied to 32 bit tasks, both for 32 bit kernels and for
>32
>bit tasks running on 64 bit kernels.  64 bit tasks already have zero
>bytes in addresses of library functions.  Other architectures may reuse
>the logic.
>
>The first Mb is excluded from the range because of the compatibility
>with
>programs like Wine and Dosemu.
>
>If the sum of libraries sizes plus executable size doesn't exceed 15
>Mb,
>the only pages out of ASCII-protected range are VDSO and vsyscall.
>However, they don't provide enough material for obtaining arbitrary
>code
>execution and are not dangerous without using other executable pages.
>
>If 16 Mbs are over, we fallback to the old allocation algorithm.
>
>Without the patch:
>
>$ ldd /bin/ls
>	linux-gate.so.1 =>  (0xf779c000)
>        librt.so.1 => /lib/librt.so.1 (0xb7fcf000)
>        libtermcap.so.2 => /lib/libtermcap.so.2 (0xb7fca000)
>        libc.so.6 => /lib/libc.so.6 (0xb7eae000)
>        libpthread.so.0 => /lib/libpthread.so.0 (0xb7e5b000)
>        /lib/ld-linux.so.2 (0xb7fe6000)
>
>With the patch:
>
>$ ldd /bin/ls
>	linux-gate.so.1 =>  (0xf772a000)
>	librt.so.1 => /lib/librt.so.1 (0x0014a000)
>	libtermcap.so.2 => /lib/libtermcap.so.2 (0x0015e000)
>	libc.so.6 => /lib/libc.so.6 (0x00162000)
>	libpthread.so.0 => /lib/libpthread.so.0 (0x00283000)
>	/lib/ld-linux.so.2 (0x00131000)
>
>The same logic was used in -ow patch for 2.0-2.4 kernels and in
>exec-shield for 2.6.x kernels.  Parts of the code were taken from RHEL6
>version of exec-shield.
>
>Signed-off-by: Vasiliy Kulikov <segoon@...nwall.com>
>--
> arch/x86/mm/mmap.c       |    5 +++
> include/linux/mm_types.h |    4 +++
> include/linux/sched.h    |    3 ++
>mm/mmap.c                |   66
>++++++++++++++++++++++++++++++++++++++++++---
> 4 files changed, 73 insertions(+), 5 deletions(-)
>
>diff --git a/arch/x86/mm/mmap.c b/arch/x86/mm/mmap.c
>index 1dab519..0c82a94 100644
>--- a/arch/x86/mm/mmap.c
>+++ b/arch/x86/mm/mmap.c
>@@ -131,6 +131,11 @@ void arch_pick_mmap_layout(struct mm_struct *mm)
> 	} else {
> 		mm->mmap_base = mmap_base();
> 		mm->get_unmapped_area = arch_get_unmapped_area_topdown;
>+		if (mmap_is_ia32()) {
>+			mm->get_unmapped_exec_area =
>+				arch_get_unmapped_exec_area;
>+			mm->lib_mmap_base = 0x00110000 + mmap_rnd();
>+		}
> 		mm->unmap_area = arch_unmap_area_topdown;
> 	}
> }
>diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
>index 027935c..5f2dca9 100644
>--- a/include/linux/mm_types.h
>+++ b/include/linux/mm_types.h
>@@ -225,9 +225,13 @@ struct mm_struct {
> 	unsigned long (*get_unmapped_area) (struct file *filp,
> 				unsigned long addr, unsigned long len,
> 				unsigned long pgoff, unsigned long flags);
>+	unsigned long (*get_unmapped_exec_area) (struct file *filp,
>+				unsigned long addr, unsigned long len,
>+				unsigned long pgoff, unsigned long flags);
> 	void (*unmap_area) (struct mm_struct *mm, unsigned long addr);
> #endif
> 	unsigned long mmap_base;		/* base of mmap area */
>+	unsigned long lib_mmap_base;		/* base of mmap libraries area
>(includes zero symbol) */
> 	unsigned long task_size;		/* size of task vm space */
>	unsigned long cached_hole_size; 	/* if non-zero, the largest hole
>below free_area_cache */
>	unsigned long free_area_cache;		/* first hole of size cached_hole_size
>or larger */
>diff --git a/include/linux/sched.h b/include/linux/sched.h
>index f024c63..8feaba9 100644
>--- a/include/linux/sched.h
>+++ b/include/linux/sched.h
>@@ -394,6 +394,9 @@ arch_get_unmapped_area_topdown(struct file *filp,
>unsigned long addr,
> 			  unsigned long flags);
> extern void arch_unmap_area(struct mm_struct *, unsigned long);
>extern void arch_unmap_area_topdown(struct mm_struct *, unsigned long);
>+extern unsigned long
>+arch_get_unmapped_exec_area(struct file *, unsigned long,
>+		unsigned long, unsigned long, unsigned long);
> #else
> static inline void arch_pick_mmap_layout(struct mm_struct *mm) {}
> #endif
>diff --git a/mm/mmap.c b/mm/mmap.c
>index d49736f..489510c 100644
>--- a/mm/mmap.c
>+++ b/mm/mmap.c
>@@ -50,6 +50,10 @@ static void unmap_region(struct mm_struct *mm,
> 		struct vm_area_struct *vma, struct vm_area_struct *prev,
> 		unsigned long start, unsigned long end);
> 
>+static unsigned long
>+get_unmapped_area_prot(struct file *file, unsigned long addr, unsigned
>long len,
>+		unsigned long pgoff, unsigned long flags, bool exec);
>+
> /*
>* WARNING: the debugging will use recursive algorithms so never enable
>this
>  * unless you know what you are doing.
>@@ -989,7 +993,8 @@ unsigned long do_mmap_pgoff(struct file *file,
>unsigned long addr,
> 	/* Obtain the address to map to. we verify (or select) it and ensure
> 	 * that it represents a valid section of the address space.
> 	 */
>-	addr = get_unmapped_area(file, addr, len, pgoff, flags);
>+	addr = get_unmapped_area_prot(file, addr, len, pgoff, flags,
>+			prot & PROT_EXEC);
> 	if (addr & ~PAGE_MASK)
> 		return addr;
> 
>@@ -1528,6 +1533,46 @@ bottomup:
> }
> #endif
> 
>+unsigned long
>+arch_get_unmapped_exec_area(struct file *filp, unsigned long addr0,
>+		unsigned long len, unsigned long pgoff, unsigned long flags)
>+{
>+	unsigned long addr = addr0;
>+	struct mm_struct *mm = current->mm;
>+	struct vm_area_struct *vma;
>+
>+	if (len > TASK_SIZE)
>+		return -ENOMEM;
>+
>+	if (flags & MAP_FIXED)
>+		return addr;
>+
>+	/* We ALWAYS start from the beginning as base addresses
>+	 * with zero high bits is a valued resource */
>+	addr = max_t(unsigned long, mm->lib_mmap_base, mmap_min_addr);
>+
>+	for (vma = find_vma(mm, addr); ; vma = vma->vm_next) {
>+		/* At this point:  (!vma || addr < vma->vm_end). */
>+		if (TASK_SIZE - len < addr)
>+			return -ENOMEM;
>+
>+		/* We don't want to touch brk of not DYNAMIC elf binaries */
>+		if (mm->brk && addr > mm->brk)
>+			goto failed;
>+
>+		if (!vma || addr + len <= vma->vm_start)
>+			return addr;
>+
>+		addr = vma->vm_end;
>+		/* If 0x01000000 is touched, the algo gives up */
>+		if (addr >= 0x01000000)
>+			goto failed;
>+	}
>+
>+failed:
>+	return current->mm->get_unmapped_area(filp, addr0, len, pgoff,
>flags);
>+}
>+
> void arch_unmap_area_topdown(struct mm_struct *mm, unsigned long addr)
> {
> 	/*
>@@ -1541,9 +1586,9 @@ void arch_unmap_area_topdown(struct mm_struct
>*mm, unsigned long addr)
> 		mm->free_area_cache = mm->mmap_base;
> }
> 
>-unsigned long
>-get_unmapped_area(struct file *file, unsigned long addr, unsigned long
>len,
>-		unsigned long pgoff, unsigned long flags)
>+static unsigned long
>+get_unmapped_area_prot(struct file *file, unsigned long addr, unsigned
>long len,
>+		unsigned long pgoff, unsigned long flags, bool exec)
> {
> 	unsigned long (*get_area)(struct file *, unsigned long,
> 				  unsigned long, unsigned long, unsigned long);
>@@ -1556,7 +1601,11 @@ get_unmapped_area(struct file *file, unsigned
>long addr, unsigned long len,
> 	if (len > TASK_SIZE)
> 		return -ENOMEM;
> 
>-	get_area = current->mm->get_unmapped_area;
>+	if (exec && current->mm->get_unmapped_exec_area)
>+		get_area = current->mm->get_unmapped_exec_area;
>+	else
>+		get_area = current->mm->get_unmapped_area;
>+
> 	if (file && file->f_op && file->f_op->get_unmapped_area)
> 		get_area = file->f_op->get_unmapped_area;
> 	addr = get_area(file, addr, len, pgoff, flags);
>@@ -1571,6 +1620,13 @@ get_unmapped_area(struct file *file, unsigned
>long addr, unsigned long len,
> 	return arch_rebalance_pgtables(addr, len);
> }
> 
>+unsigned long
>+get_unmapped_area(struct file *file, unsigned long addr, unsigned long
>len,
>+		unsigned long pgoff, unsigned long flags)
>+{
>+	return get_unmapped_area_prot(file, addr, len, pgoff, flags, false);
>+}
>+
> EXPORT_SYMBOL(get_unmapped_area);
> 
>/* Look up the first VMA which satisfies  addr < vm_end,  NULL if none.
>*/
>---

This also greatly reduces the address space available for randomization, and may get in the way of the default brk.  Is this a net win or lose?  Also, this zero byte is going to be at the last address, which means it might not help.  How about addresses of the form 0xAA00B000 instead?  The last bits are always 000 for a page address, of course...
-- 
Sent from my mobile phone. Please excuse my brevity and lack of formatting.
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ