Re: serious performance regression due to NX patch

Daniel McNeil
2004-07-13T16:51:23Z

On Tue, 2004-07-13 at 09:05, Mark Haverkamp wrote:
> I think that there is a problem with this piece of code in
> binfmt_elf.c:
>
> if (i == elf_ex.e_phnum)
> def_flags |= VM_EXEC | VM_MAYEXEC;
>
> I've seen that if this code is executed that a mmap with PROT_NONE will
> have the x flag set on the page. because of code in mmap.c for
> do_mmp_pgoff:
>
> vm_flags = calc_vm_prot_bits(prot) | calc_vm_flag_bits(flags) |
> mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;
>
> or possibly here in do_brk:
>
> flags = VM_DATA_DEFAULT_FLAGS | VM_ACCOUNT | mm->def_flags;
>
>
> the mm->def_flags have VM_EXEC and VM_MAYEXEC which means they are set all the time.
>
> Mark.

This was my email to Andrew yesterday that did not seem
to make it to LKML (probably because I attached some elf
binaries -- I made them links now). The below program shows
the old binaries without the PT_GNU_STACK do not get PROT_NONE
mmaps. See info below.

Daniel

On Fri, 2004-07-09 at 23:45, Andrew Morton wrote:

> #include <unistd.h>
> #include <errno.h>
> #include <sys/mman.h>
>
>
> main()
> {
> char *p0 = 0;
> char *p1 = (char *)-1;
> char *p2;
> int err;
>
> p2 = mmap(0, 4096, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
>
> printf("p2=%p\n", p2);
> printf("pid=%d\n", getpid());
>
> getchar();
>
> errno = 0;
> err = access(p0, R_OK);
> printf("access 0 ptr %p return code %d errno %d\n", p0, err,
errno);
> perror("access result:");
> errno = 0;
> err = access(p1, R_OK);
> printf("access 1 ptr %p return code %d errno %d\n", p1, err,
errno);
> perror("access result:");
> errno = 0;
> err = access(p2, R_OK);
> printf("access 2 ptr %p return code %d errno %d\n", p2, err,
errno);
> perror("access result:");
> }
>
> vmm:/home/akpm> ./x
> p2=0x40000000
> pid=2038
>
> access 0 ptr (nil) return code -1 errno 14
> access result:: Bad address
> access 1 ptr 0xffffffff return code -1 errno 14
> access result:: Bad address
> access 2 ptr 0x40000000 return code -1 errno 14
> access result:: Bad address
>
> I get this result on both p4 xeon and p3 xeon.

It is the elf file that makes the difference. I was using
gcc 3.2.2 and ld (GNU ld version 2.13.90.0.18 20030206).
This produces elf binary without the PT_GNU_STACK program
section (from objdump -p x.gcc322)

Program Header:
PHDR off 0x00000034 vaddr 0x08048034 paddr 0x08048034 align 2**2
filesz 0x000000c0 memsz 0x000000c0 flags r-x
INTERP off 0x000000f4 vaddr 0x080480f4 paddr 0x080480f4 align 2**0
filesz 0x00000013 memsz 0x00000013 flags r--
LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
filesz 0x00000750 memsz 0x00000750 flags r-x
LOAD off 0x00000750 vaddr 0x08049750 paddr 0x08049750 align 2**12
filesz 0x00000118 memsz 0x0000011c flags rw-
DYNAMIC off 0x0000075c vaddr 0x0804975c paddr 0x0804975c align 2**2
filesz 0x000000c8 memsz 0x000000c8 flags rw-
NOTE off 0x00000108 vaddr 0x08048108 paddr 0x08048108 align 2**2
filesz 0x00000020 memsz 0x00000020 flags r--

With gcc 3.4.1 and GNU ld version 2.14.90.0.6 20030820 and
compile with cc -o x.rw -z noexecstack x.c produces a
elf binary without execute permission on the stack.
(from objdump -p x.rw)

Program Header:
PHDR off 0x00000034 vaddr 0x08048034 paddr 0x08048034 align 2**2
filesz 0x000000e0 memsz 0x000000e0 flags r-x
INTERP off 0x00000114 vaddr 0x08048114 paddr 0x08048114 align 2**0
filesz 0x00000013 memsz 0x00000013 flags r--
LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
filesz 0x00000735 memsz 0x00000735 flags r-x
LOAD off 0x00000738 vaddr 0x08049738 paddr 0x08049738 align 2**12
filesz 0x0000011c memsz 0x00000120 flags rw-
DYNAMIC off 0x00000748 vaddr 0x08049748 paddr 0x08049748 align 2**2
filesz 0x000000c8 memsz 0x000000c8 flags rw-
NOTE off 0x00000128 vaddr 0x08048128 paddr 0x08048128 align 2**2
filesz 0x00000020 memsz 0x00000020 flags r--
STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
filesz 0x00000000 memsz 0x00000000 flags rw-

With gcc 3.4.1 and GNU ld version 2.14.90.0.6 20030820 and
compiled with cc -o x.rwx -z execstack x.c. This produces
the same binary as with out the "-z execstack".

Program Header:
PHDR off 0x00000034 vaddr 0x08048034 paddr 0x08048034 align 2**2
filesz 0x000000e0 memsz 0x000000e0 flags r-x
INTERP off 0x00000114 vaddr 0x08048114 paddr 0x08048114 align 2**0
filesz 0x00000013 memsz 0x00000013 flags r--
LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
filesz 0x00000735 memsz 0x00000735 flags r-x
LOAD off 0x00000738 vaddr 0x08049738 paddr 0x08049738 align 2**12
filesz 0x0000011c memsz 0x00000120 flags rw-
DYNAMIC off 0x00000748 vaddr 0x08049748 paddr 0x08049748 align 2**2
filesz 0x000000c8 memsz 0x000000c8 flags rw-
NOTE off 0x00000128 vaddr 0x08048128 paddr 0x08048128 align 2**2
filesz 0x00000020 memsz 0x00000020 flags r--
0x6474e551 off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
filesz 0x00000000 memsz 0x00000000 flags rwx

Here's the corresponding /proc/pid/maps output from the
3 different binaries:

x.gcc322: 40013000-40014000 --xp 40013000 00:00 0
x.rw: 40013000-40014000 ---p 40013000 00:00 0
x.rwx: 40013000-40014000 ---p 40013000 00:00 0

So binaries compiled with an old compiler are not being
handled correctly with the nx-update.patch changes.

In load_elf_binary()

for (i = 0; i < elf_ex.e_phnum; i++, elf_ppnt++)
if (elf_ppnt->p_type == PT_GNU_STACK) {
if (elf_ppnt->p_flags & PF_X)
executable_stack = EXSTACK_ENABLE_X;
else
executable_stack = EXSTACK_DISABLE_X;
break;
}
if (i == elf_ex.e_phnum)
def_flags |= VM_EXEC | VM_MAYEXEC;

This setting of def_flags if the PT_GNU_STACK is NOT found
is causing the old binaries to get exec on PROT_NONE mmaps.

The 3 elf binaries can be found here:
http://developer.osdl.org/daniel/mmap.PROT_NONE.bug/

Daniel