From: Andi Kleen , Benjamin Herrenschmidt 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 _