From: Andi Kleen <ak@suse.de>,
      Benjamin Herrenschmidt <benh@kernel.crashing.org>

Andi change to get_unmapped_area() is triggering that interesting scenario:

- bash tries to load

- ld.so tries to map libc somewhere below the executable at a location
  provided by the prelink informations.  However, probably due to outdated
  prelink informations (I didn't re-run prelink since I updated glibc), it
  won't fit.

- Andi change cause do_mmap() to actually do a search of a free space from
  the address...  when ends up beeing right after the brk point of the just
  loaded bash

- something (glibc) is now mapped right after brk point of bash, preventing
  it from malloc'ing, so it dies.

paulus suggested to modify the code so that for a non MAP_FIXED map, it still
search from the passed-in address, but avoids the spare between the current
mm->brk and TASK_UNMAPPED_BASE, thus the algorithm would still work for
things outside of these areas.

It also fixes another issue (don't use free_area_cache when the user gave an
address hint).



---

 mm/mmap.c |   30 ++++++++++++++++++++++--------
 1 files changed, 22 insertions(+), 8 deletions(-)

diff -puN mm/mmap.c~get_unmapped_area-fix mm/mmap.c
--- 25/mm/mmap.c~get_unmapped_area-fix	2004-02-11 21:09:19.000000000 -0800
+++ 25-akpm/mm/mmap.c	2004-02-11 21:09:19.000000000 -0800
@@ -727,18 +727,20 @@ EXPORT_SYMBOL(do_mmap_pgoff);
  */
 #ifndef HAVE_ARCH_UNMAPPED_AREA
 static inline unsigned long
-arch_get_unmapped_area(struct file *filp, unsigned long addr,
+arch_get_unmapped_area(struct file *filp, unsigned long uaddr,
 		unsigned long len, unsigned long pgoff, unsigned long flags)
 {
 	struct mm_struct *mm = current->mm;
 	struct vm_area_struct *vma;
 	unsigned long start_addr;
+	unsigned long unmapped_base;
+	unsigned long addr;
 
 	if (len > TASK_SIZE)
 		return -ENOMEM;
 
-	if (addr) {
-		addr = PAGE_ALIGN(addr);
+	if (uaddr) {
+		addr = PAGE_ALIGN(uaddr);
 		vma = find_vma(mm, addr);
 		if (TASK_SIZE - len >= addr &&
 		    (!vma || addr + len <= vma->vm_start))
@@ -746,28 +748,40 @@ arch_get_unmapped_area(struct file *filp
 	}
 	start_addr = addr = mm->free_area_cache;
 
+	unmapped_base = TASK_UNMAPPED_BASE;
 full_search:
-	for (vma = find_vma(mm, addr); ; vma = vma->vm_next) {
+	for (vma = find_vma(mm, addr); ; ) {
 		/* At this point:  (!vma || addr < vma->vm_end). */
 		if (TASK_SIZE - len < addr) {
 			/*
 			 * Start a new search - just in case we missed
 			 * some holes.
 			 */
-			if (start_addr != TASK_UNMAPPED_BASE) {
-				start_addr = addr = TASK_UNMAPPED_BASE;
+			if (start_addr > unmapped_base) {
+				start_addr = addr = unmapped_base;
 				goto full_search;
 			}
 			return -ENOMEM;
 		}
+		/* On the first pass always skip the brk gap to not
+		   confuse glibc malloc.  This can happen with user
+		   address hints < TASK_UNMAPPED_BASE. */
+		if (addr >= mm->brk && addr < unmapped_base) {
+			vma = find_vma(mm, unmapped_base);
+			addr = unmapped_base;
+			continue;
+		}
 		if (!vma || addr + len <= vma->vm_start) {
 			/*
-			 * Remember the place where we stopped the search:
+			 * Remember the place where we stopped the search,
+			 * but only if the user didn't give hints.
 			 */
-			mm->free_area_cache = addr + len;
+			if (uaddr == 0)
+				mm->free_area_cache = addr + len;
 			return addr;
 		}
 		addr = vma->vm_end;
+		vma = vma->vm_next;
 	}
 }
 #else

_