[patch] 2.4.0-test4: mmap() limitations

From: Maciej W. Rozycki (macro@ds2.pg.gda.pl)
Date: Tue Jul 25 2000 - 02:40:11 EST


Hi,

 I am observing the following problem: when a program requests a mmap()
and provides a suggested address, Linux only checks addresses above it if
the given address does not fit. As a result, the higher address a program
specifies, the smaller chance mmap() succeeds even if there is still space
available below. If a program specifies an addres above TASK_SIZE, mmap()
will never succeed.

 Failures due to the described limitation may be observed with the
following program (using a kernel compiled for an i386 with the
CONFIG_NOHIGHMEM option set; depending on the TASK_SIZE definition this
may also be observed for other platforms):

#define _GNU_SOURCE

#include <stdio.h>
#include <sys/mman.h>

int main(void)
{
        void *a0, *a1, *a2;

        a0 = mmap((void *)0xbfffc000, 0x1000, PROT_READ | PROT_WRITE,
                  MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
        a1 = mmap((void *)0xbfffd000, 0x1000, PROT_READ | PROT_WRITE,
                  MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
        a2 = mmap((void *)0xbfffc000, 0x4000, PROT_READ | PROT_WRITE,
                  MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);

        fprintf(stderr, "a0: %p, a1: %p, a2: %p\n", a0, a1, a2);

        if (a0 != (void *)-1)
                munmap(a0, 0x1000);
        if (a1 != (void *)-1)
                munmap(a1, 0x1000);
        if (a2 != (void *)-1)
                munmap(a2, 0x2000);

        return 0;
}

This limitation makes glibc's runtime loading routines fail from time to
time as they always provide a suggested address and the address is
computed in run time. Depending on the addresses in an ELF header and
other objects currently mmap()ed this may succeed or fail. This is also
the reason of a failure of dlopen()ing ld.so (which often happens when
calling dlopen() from a statically linked executable) on MIPS/Linux.

 The following patch removes this limitation, allowing checking of
addresses below the suggested one in the second pass.

 This might not be serious enough issue for 2.4.0, but if deferred
it should go into 2.4.1 or so rather than into 2.5.

  Maciej

-- 
+  Maciej W. Rozycki, Technical University of Gdansk, Poland   +
+--------------------------------------------------------------+
+        e-mail: macro@ds2.pg.gda.pl, PGP key available        +

diff -u --recursive --new-file linux-2.4.0-test4.macro/mm/mmap.c linux-2.4.0-test4/mm/mmap.c --- linux-2.4.0-test4.macro/mm/mmap.c Sun Jul 16 22:27:29 2000 +++ linux-2.4.0-test4/mm/mmap.c Tue Jul 25 05:06:21 2000 @@ -175,7 +175,7 @@ if ((len = PAGE_ALIGN(len)) == 0) return addr; - if (len > TASK_SIZE || addr > TASK_SIZE-len) + if (len > TASK_SIZE || ((flags & MAP_FIXED) && (addr > TASK_SIZE - len))) return -EINVAL; /* offset overflow? */ @@ -356,20 +356,31 @@ unsigned long get_unmapped_area(unsigned long addr, unsigned long len) { struct vm_area_struct * vmm; + int pass = 0; if (len > TASK_SIZE) return 0; - if (!addr) - addr = TASK_UNMAPPED_BASE; - addr = PAGE_ALIGN(addr); - - for (vmm = find_vma(current->mm, addr); ; vmm = vmm->vm_next) { - /* At this point: (!vmm || addr < vmm->vm_end). */ - if (TASK_SIZE - len < addr) - return 0; - if (!vmm || addr + len <= vmm->vm_start) - return addr; - addr = vmm->vm_end; + + while (1) { + if (!addr) + addr = TASK_UNMAPPED_BASE; + addr = PAGE_ALIGN(addr); + + for (vmm = find_vma(current->mm, addr); ; vmm = vmm->vm_next) { + /* At this point: (!vmm || addr < vmm->vm_end). */ + if (TASK_SIZE - len < addr) { + if (pass > 0) + return 0; + else { + pass = 1; + addr = 0; + break; + } + } + if (!vmm || addr + len <= vmm->vm_start) + return addr; + addr = vmm->vm_end; + } } } #endif

- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.rutgers.edu Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Mon Jul 31 2000 - 21:00:18 EST