Skip to contentSkip to conversation

flexible-mmap-2.6.7-D5

PATCH In review Ingo Molnar 2 participants 7 messages Jun 18, 2004 – Jun 19, 2004

Subsystems (8)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 flexible-mmap-2.6.7-D5

the flexible-mmap-2.6.7-D5 patch (attached) splits the flexible-mmap layout stuff out of exec-shield. The patch is against 2.6.7 + my latest NX patch. (i've attached the NX patch too, for convenience.)

the goal of the patch is to change the way virtual memory is allocated, from:

0x08000000 ... binary image
0x08xxxxxx ... brk area, grows upwards 0x40000000 ... start of mmap, new mmaps go after old ones 0xbfxxxxxx ... stack

to a more flexible top-down mmap() method:

0x08000000 ... binary image
0x08xxxxxx ... brk area, grows upwards
0xbfxxxxxx ... _end_ of all mmaps, new mmaps go below old ones 0xbfyyyyyy ... stack

the new layout has a couple of advantages:

- primarily this layout enables both malloc(), mmap()/shmat() users to utilize the full address space: 3GB on stock x86, 4GB on x86 4:4 or x86-64 running x86 apps.

- the new layout is also in essence 'self-tuning' the mmap() and malloc() limits: no hacks like /proc/PID/mmap_base are needed - both malloc() and mmap() can grow until all the address space is full. With the old layout, malloc() space was limited to 900MB, mmap() space to ~2GB.

- The new layout also allows very large continuous mmap()s because the 'free space' is always a continuous hole (statistically).

- there's also a ~4K pagetable saved per typical process, because we dont allocate at 1GB anymore and dont fragment the VM that much - there are only 2 main chunks, the binary image + brk(), and the mmap() area + stack.

here are the kernel code changes the patch introduces:

- the patch is compatible with existing architectures that either make use of HAVE_ARCH_UNMAPPED_AREA or use the default mmap() allocator - there is no change in behavior.

- the patch gives a new mechanism to define alternate per-mm mmap() layouts via mm->get_unmapped_area(). On x86 i used this mechanism to implement a top-down allocator for PT_GNU_STACK applications.

- 64-bit architectures can use the same mechanism to clean up 32-bit compatibility layouts: by defining HAVE_ARCH_PICK_MMAP_LAYOUT and providing a arch_pick_mmap_layout() function - which can then decide between various mmap() layout functions.

- i also introduced a new personality bit (ADDR_COMPAT_LAYOUT) to signal older binaries that dont have PT_GNU_STACK. x86 uses this to revert back to the stock layout. I also changed x86 to not clear the personality bits upon exec(), like x86-64 already does.

- once every architecture that uses HAVE_ARCH_UNMAPPED_AREA has defined its arch_pick_mmap_layout() function, we can get rid of HAVE_ARCH_UNMAPPED_AREA altogether, as a final cleanup.

i tested the patch on two distributions: one was an older one without PT_GNU_STACK, the other had applications was with PT_GNU_STACK. On the old one we properly generate the compatible VM layout - the PT_GNU_STACK one got the new layout.

the new layout generation function (__get_unmapped_area()) got significant testing in Fedora Core 1/2 and RHEL3, so it's pretty robust.

Compatibility: there's one known public application that broke in Fedora, a serial terminal program. The application bug has been fixed.

Comments, suggestions are welcome,

Ingo

arch/i386/mm/mmap.c renamed from arch/i386/mm/mmap.c.orig +63 -0
@@ -0,0 +1,63 @@
+/*
+ *  linux/arch/i386/mm/mmap.c
+ *
+ *  flexible mmap layout support
+ *
+ * Copyright 2003-2004 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *
+ * Started by Ingo Molnar <[email protected]>
+ */
+
+#include <linux/personality.h>
+#include <linux/mm.h>
+
+/*
+ * Top of mmap area (just below the process stack).
+ *
+ * Leave an at least ~128 MB hole. Limit maximum floor to 5/6 of
+ * the total address space, in case the stack is unlimited.
+ */
+#define MIN_GAP (128*1024*1024)
+#define MAX_GAP (TASK_SIZE/6*5)
+
+/*
+ * This function, called very early during the creation of a new
+ * process VM image, sets up which VM layout function to use:
+ */
+void arch_pick_mmap_layout(struct mm_struct *mm)
+{
+	/*
+	 * If non-PT_GNU_STACK binary then fall back to the
+	 * standard layout:
+	 */
+	if (current->personality & ADDR_COMPAT_LAYOUT) {
+		mm->mmap_base = TASK_UNMAPPED_BASE;
+		mm->get_unmapped_area = arch_get_unmapped_area;
+	} else {
+		unsigned long gap = current->rlim[RLIMIT_STACK].rlim_cur;
+
+		if (gap < MIN_GAP)
+			gap = MIN_GAP;
+		else if (gap > MAX_GAP)
+			gap = MAX_GAP;
+		mm->mmap_base = TASK_SIZE - (gap & PAGE_MASK);
+		mm->get_unmapped_area = get_unmapped_area_topdown;
+	}
+}
+
arch/i386/mm/Makefile renamed from arch/i386/mm/Makefile.orig +1 -1
@@ -2,7 +2,7 @@
 # Makefile for the linux i386-specific parts of the memory manager.
 #
 
-obj-y	:= init.o pgtable.o fault.o ioremap.o extable.o pageattr.o 
+obj-y	:= init.o pgtable.o fault.o ioremap.o extable.o pageattr.o mmap.o
 
 obj-$(CONFIG_DISCONTIGMEM)	+= discontig.o
 obj-$(CONFIG_HUGETLB_PAGE) += hugetlbpage.o
include/linux/sched.h renamed from include/linux/sched.h.orig +22 -0
@@ -191,10 +191,32 @@ extern int sysctl_max_map_count;
 
 #include <linux/aio.h>
 
+extern unsigned long
+arch_get_unmapped_area(struct file *, unsigned long, unsigned long,
+		       unsigned long, unsigned long);
+extern unsigned long
+get_unmapped_area_topdown(struct file *filp, unsigned long addr,
+			  unsigned long len, unsigned long pgoff,
+			  unsigned long flags);
+
+#ifdef HAVE_ARCH_PICK_MMAP_LAYOUT
+extern void arch_pick_mmap_layout(struct mm_struct *mm);
+#else
+static inline void arch_pick_mmap_layout(struct mm_struct *mm)
+{
+	mm->mmap_base = TASK_UNMAPPED_BASE;
+	mm->get_unmapped_area = arch_get_unmapped_area;
+}
+#endif
+
 struct mm_struct {
 	struct vm_area_struct * mmap;		/* list of VMAs */
 	struct rb_root mm_rb;
 	struct vm_area_struct * mmap_cache;	/* last find_vma result */
+	unsigned long (*get_unmapped_area) (struct file *filp,
+				unsigned long addr, unsigned long len,
+				unsigned long pgoff, unsigned long flags);
+	unsigned long mmap_base;		/* base of mmap area */
 	unsigned long free_area_cache;		/* first hole */
 	pgd_t * pgd;
 	atomic_t mm_users;			/* How many users with user space? */
include/linux/personality.h renamed from include/linux/personality.h.orig +1 -0
@@ -30,6 +30,7 @@ extern int abi_fake_utsname;
  */
 enum {
 	MMAP_PAGE_ZERO =	0x0100000,
+	ADDR_COMPAT_LAYOUT =	0x0200000,
 	ADDR_LIMIT_32BIT =	0x0800000,
 	SHORT_INODE =		0x1000000,
 	WHOLE_SECONDS =		0x2000000,
include/asm-i386/processor.h renamed from include/asm-i386/processor.h.orig +2 -0
@@ -302,6 +302,8 @@ extern unsigned int mca_pentium_flag;
  */
 #define TASK_UNMAPPED_BASE	(PAGE_ALIGN(TASK_SIZE / 3))
 
+#define HAVE_ARCH_PICK_MMAP_LAYOUT
+
 /*
  * Size of io_bitmap, covering ports 0 to 0x3ff.
  */
include/asm-i386/elf.h renamed from include/asm-i386/elf.h.orig +1 -1
@@ -117,7 +117,7 @@ typedef struct user_fxsr_struct elf_fpxr
 #define AT_SYSINFO_EHDR		33
 
 #ifdef __KERNEL__
-#define SET_PERSONALITY(ex, ibcs2) set_personality((ibcs2)?PER_SVR4:PER_LINUX)
+#define SET_PERSONALITY(ex, ibcs2) do { } while (0)
 
 extern int dump_task_regs (struct task_struct *, elf_gregset_t *);
 extern int dump_task_fpu (struct task_struct *, elf_fpregset_t *);
fs/binfmt_aout.c renamed from fs/binfmt_aout.c.orig +1 -1
@@ -307,7 +307,7 @@ static int load_aout_binary(struct linux
 		(current->mm->start_data = N_DATADDR(ex));
 	current->mm->brk = ex.a_bss +
 		(current->mm->start_brk = N_BSSADDR(ex));
-	current->mm->free_area_cache = TASK_UNMAPPED_BASE;
+	current->mm->free_area_cache = current->mm->mmap_base;
 
 	current->mm->rss = 0;
 	current->mm->mmap = NULL;
fs/binfmt_elf.c renamed from fs/binfmt_elf.c.orig +4 -2
@@ -625,8 +625,10 @@ static int load_elf_binary(struct linux_
 				executable_stack = EXSTACK_DISABLE_X;
 			break;
 		}
-	if (i == elf_ex.e_phnum)
+	if (i == elf_ex.e_phnum) {
 		def_flags |= VM_EXEC | VM_MAYEXEC;
+		current->personality |= ADDR_COMPAT_LAYOUT;
+	}
 
 	/* Some simple consistency checks for the interpreter */
 	if (elf_interpreter) {
@@ -703,7 +705,7 @@ static int load_elf_binary(struct linux_
 	/* Do this so that we can load the interpreter, if need be.  We will
 	   change some of these later */
 	current->mm->rss = 0;
-	current->mm->free_area_cache = TASK_UNMAPPED_BASE;
+	current->mm->free_area_cache = current->mm->mmap_base;
 	retval = setup_arg_pages(bprm, executable_stack);
 	if (retval < 0) {
 		send_sig(SIGKILL, current, 0);
fs/exec.c renamed from fs/exec.c.orig +1 -0
@@ -547,6 +547,7 @@ static int exec_mmap(struct mm_struct *m
 	tsk->active_mm = mm;
 	activate_mm(active_mm, mm);
 	task_unlock(tsk);
+	arch_pick_mmap_layout(mm);
 	if (old_mm) {
 		if (active_mm != old_mm) BUG();
 		mmput(old_mm);
kernel/fork.c renamed from kernel/fork.c.orig +1 -1
@@ -279,7 +279,7 @@ static inline int dup_mmap(struct mm_str
 	mm->locked_vm = 0;
 	mm->mmap = NULL;
 	mm->mmap_cache = NULL;
-	mm->free_area_cache = TASK_UNMAPPED_BASE;
+	mm->free_area_cache = oldmm->mmap_base;
 	mm->map_count = 0;
 	mm->rss = 0;
 	cpus_clear(mm->cpu_vm_mask);
mm/mmap.c renamed from mm/mmap.c.orig +73 -6

99 lines not on this page · raw message

arch/i386/kernel/cpu/proc.c renamed from arch/i386/kernel/cpu/proc.c.orig +1 -1
@@ -27,7 +27,7 @@ static int show_cpuinfo(struct seq_file 
 		/* AMD-defined */
 		NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
 		NULL, NULL, NULL, "syscall", NULL, NULL, NULL, NULL,
-		NULL, NULL, NULL, "mp", NULL, NULL, "mmxext", NULL,
+		NULL, NULL, NULL, "mp", "nx", NULL, "mmxext", NULL,
 		NULL, NULL, NULL, NULL, NULL, "lm", "3dnowext", "3dnow",
 
 		/* Transmeta-defined */
arch/i386/kernel/head.S renamed from arch/i386/kernel/head.S.orig +26 -0
@@ -153,6 +153,32 @@ ENTRY(startup_32_smp)
 	orl %edx,%eax
 	movl %eax,%cr4
 
+	btl $5, %eax		# check if PAE is enabled
+	jnc 6f
+
+	/* Check if extended functions are implemented */
+	movl $0x80000000, %eax
+	cpuid
+	cmpl $0x80000000, %eax
+	jbe 6f
+	mov $0x80000001, %eax
+	cpuid
+	/* Execute Disable bit supported? */
+	btl $20, %edx
+	jnc 6f
+
+	/* Setup EFER (Extended Feature Enable Register) */
+	movl $0xc0000080, %ecx
+	rdmsr
+
+	btsl $11, %eax
+	/* Make changes effective */
+	wrmsr
+
+6:
+	/* cpuid clobbered ebx, set it up again: */
+	xorl %ebx,%ebx
+	incl %ebx
 3:
 #endif /* CONFIG_SMP */
 
arch/i386/kernel/sysenter.c renamed from arch/i386/kernel/sysenter.c.orig +1 -1
@@ -45,7 +45,7 @@ static int __init sysenter_setup(void)
 {
 	unsigned long page = get_zeroed_page(GFP_ATOMIC);
 
-	__set_fixmap(FIX_VSYSCALL, __pa(page), PAGE_READONLY);
+	__set_fixmap(FIX_VSYSCALL, __pa(page), PAGE_READONLY_EXEC);
 
 	if (!boot_cpu_has(X86_FEATURE_SEP)) {
 		memcpy((void *) page,
arch/i386/kernel/module.c renamed from arch/i386/kernel/module.c.orig +1 -1
@@ -32,7 +32,7 @@ void *module_alloc(unsigned long size)
 {
 	if (size == 0)
 		return NULL;
-	return vmalloc(size);
+	return vmalloc_exec(size);
 }
 
 
arch/i386/mm/init.c renamed from arch/i386/mm/init.c.orig +77 -5

126 lines not on this page · raw message

arch/i386/mm/fault.c renamed from arch/i386/mm/fault.c.orig +15 -0
@@ -406,6 +406,21 @@ no_context:
 
 	bust_spinlocks(1);
 
+#ifdef CONFIG_X86_PAE
+	{
+		pgd_t *pgd;
+		pmd_t *pmd;
+
+
+
+		pgd = init_mm.pgd + pgd_index(address);
+		if (pgd_present(*pgd)) {
+			pmd = pmd_offset(pgd, address);
+			if (pmd_val(*pmd) & _PAGE_NX)
+				printk(KERN_CRIT "kernel tried to access NX-protected page - exploit attempt? (uid: %d)\n", current->uid);
+		}
+	}
+#endif
 	if (address < PAGE_SIZE)
 		printk(KERN_ALERT "Unable to handle kernel NULL pointer dereference");
 	else
arch/x86_64/kernel/module.c renamed from arch/x86_64/kernel/module.c.orig +1 -1
@@ -121,7 +121,7 @@ void *module_alloc(unsigned long size)
 			goto fail;
 	}
 	
-	if (map_vm_area(area, PAGE_KERNEL_EXECUTABLE, &pages))
+	if (map_vm_area(area, PAGE_KERNEL_EXEC, &pages))
 		goto fail;
 	
 	memset(addr, 0, size);
arch/x86_64/mm/pageattr.c renamed from arch/x86_64/mm/pageattr.c.orig +1 -1
@@ -180,7 +180,7 @@ int change_page_attr(struct page *page, 
 			unsigned long addr2;
 			addr2 = __START_KERNEL_map + page_to_phys(page);
 			err = __change_page_attr(addr2, page, prot, 
-						 PAGE_KERNEL_EXECUTABLE);
+						 PAGE_KERNEL_EXEC);
 		} 
 	} 	
 	up_write(&init_mm.mmap_sem); 
include/asm-x86_64/pgtable.h renamed from include/asm-x86_64/pgtable.h.orig +2 -2
@@ -172,7 +172,7 @@ static inline void set_pml4(pml4_t *dst,
 #define PAGE_READONLY_EXEC __pgprot(_PAGE_PRESENT | _PAGE_USER | _PAGE_ACCESSED)
 #define __PAGE_KERNEL \
 	(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED | _PAGE_NX)
-#define __PAGE_KERNEL_EXECUTABLE \
+#define __PAGE_KERNEL_EXEC \
 	(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED)
 #define __PAGE_KERNEL_NOCACHE \
 	(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_PCD | _PAGE_ACCESSED | _PAGE_NX)
@@ -188,7 +188,7 @@ static inline void set_pml4(pml4_t *dst,
 #define MAKE_GLOBAL(x) __pgprot((x) | _PAGE_GLOBAL)
 
 #define PAGE_KERNEL MAKE_GLOBAL(__PAGE_KERNEL)
-#define PAGE_KERNEL_EXECUTABLE MAKE_GLOBAL(__PAGE_KERNEL_EXECUTABLE)
+#define PAGE_KERNEL_EXEC MAKE_GLOBAL(__PAGE_KERNEL_EXEC)
 #define PAGE_KERNEL_RO MAKE_GLOBAL(__PAGE_KERNEL_RO)
 #define PAGE_KERNEL_NOCACHE MAKE_GLOBAL(__PAGE_KERNEL_NOCACHE)
 #define PAGE_KERNEL_VSYSCALL MAKE_GLOBAL(__PAGE_KERNEL_VSYSCALL)
include/linux/vmalloc.h renamed from include/linux/vmalloc.h.orig +1 -0
@@ -23,6 +23,7 @@ struct vm_struct {
  *	Highlevel APIs for driver use
  */
 extern void *vmalloc(unsigned long size);
+extern void *vmalloc_exec(unsigned long size);
 extern void *vmalloc_32(unsigned long size);
 extern void *__vmalloc(unsigned long size, int gfp_mask, pgprot_t prot);
 extern void vfree(void *addr);
include/asm-i386/msr.h renamed from include/asm-i386/msr.h.orig +9 -0
@@ -217,6 +217,15 @@ static inline void wrmsrl (unsigned long
 #define MSR_K7_FID_VID_CTL		0xC0010041
 #define MSR_K7_FID_VID_STATUS		0xC0010042
 
+/* extended feature register */
+#define MSR_EFER 			0xc0000080
+
+/* EFER bits: */
+
+/* Execute Disable enable */
+#define _EFER_NX			11
+#define EFER_NX				(1<<_EFER_NX)
+
 /* Centaur-Hauls/IDT defined MSRs. */
 #define MSR_IDT_FCR1			0x107
 #define MSR_IDT_FCR2			0x108
include/asm-i386/cpufeature.h renamed from include/asm-i386/cpufeature.h.orig +2 -0
@@ -47,6 +47,7 @@
 /* Don't duplicate feature flags which are redundant with Intel! */
 #define X86_FEATURE_SYSCALL	(1*32+11) /* SYSCALL/SYSRET */
 #define X86_FEATURE_MP		(1*32+19) /* MP Capable. */
+#define X86_FEATURE_NX		(1*32+20) /* Execute Disable */
 #define X86_FEATURE_MMXEXT	(1*32+22) /* AMD MMX extensions */
 #define X86_FEATURE_LM		(1*32+29) /* Long Mode (x86-64) */
 #define X86_FEATURE_3DNOWEXT	(1*32+30) /* AMD 3DNow! extensions */
@@ -100,6 +101,7 @@
 #define cpu_has_xmm		boot_cpu_has(X86_FEATURE_XMM)
 #define cpu_has_ht		boot_cpu_has(X86_FEATURE_HT)
 #define cpu_has_mp		boot_cpu_has(X86_FEATURE_MP)
+#define cpu_has_nx		boot_cpu_has(X86_FEATURE_NX)
 #define cpu_has_k6_mtrr		boot_cpu_has(X86_FEATURE_K6_MTRR)
 #define cpu_has_cyrix_arr	boot_cpu_has(X86_FEATURE_CYRIX_ARR)
 #define cpu_has_centaur_mcr	boot_cpu_has(X86_FEATURE_CENTAUR_MCR)
include/asm-i386/page.h renamed from include/asm-i386/page.h.orig +4 -2
@@ -40,15 +40,18 @@
  * These are used to make use of C type-checking..
  */
 #ifdef CONFIG_X86_PAE
+extern unsigned long long __supported_pte_mask;
 typedef struct { unsigned long pte_low, pte_high; } pte_t;
 typedef struct { unsigned long long pmd; } pmd_t;
 typedef struct { unsigned long long pgd; } pgd_t;
+typedef struct { unsigned long long pgprot; } pgprot_t;
 #define pte_val(x)	((x).pte_low | ((unsigned long long)(x).pte_high << 32))
 #define HPAGE_SHIFT	21
 #else
 typedef struct { unsigned long pte_low; } pte_t;
 typedef struct { unsigned long pmd; } pmd_t;
 typedef struct { unsigned long pgd; } pgd_t;
+typedef struct { unsigned long pgprot; } pgprot_t;
 #define boot_pte_t pte_t /* or would you rather have a typedef */
 #define pte_val(x)	((x).pte_low)
 #define HPAGE_SHIFT	22
@@ -61,7 +64,6 @@ typedef struct { unsigned long pgd; } pg
 #define HUGETLB_PAGE_ORDER	(HPAGE_SHIFT - PAGE_SHIFT)
 #endif
 
-typedef struct { unsigned long pgprot; } pgprot_t;
 
 #define pmd_val(x)	((x).pmd)
 #define pgd_val(x)	((x).pgd)
@@ -136,7 +138,7 @@ static __inline__ int get_order(unsigned
 
 #define virt_addr_valid(kaddr)	pfn_valid(__pa(kaddr) >> PAGE_SHIFT)
 
-#define VM_DATA_DEFAULT_FLAGS	(VM_READ | VM_WRITE | VM_EXEC | \
+#define VM_DATA_DEFAULT_FLAGS	(VM_READ | VM_WRITE | \
 				 VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC)
 
 #endif /* __KERNEL__ */
include/asm-i386/pgtable-3level.h renamed from include/asm-i386/pgtable-3level.h.orig +9 -3
@@ -101,18 +101,24 @@ static inline unsigned long pte_pfn(pte_
 		(pte.pte_high << (32 - PAGE_SHIFT));
 }
 
+extern unsigned long long __supported_pte_mask;
+
 static inline pte_t pfn_pte(unsigned long page_nr, pgprot_t pgprot)
 {
 	pte_t pte;
 
-	pte.pte_high = page_nr >> (32 - PAGE_SHIFT);
-	pte.pte_low = (page_nr << PAGE_SHIFT) | pgprot_val(pgprot);
+	pte.pte_high = (page_nr >> (32 - PAGE_SHIFT)) | \
+					(pgprot_val(pgprot) >> 32);
+	pte.pte_high &= (__supported_pte_mask >> 32);
+	pte.pte_low = ((page_nr << PAGE_SHIFT) | pgprot_val(pgprot)) & \
+							__supported_pte_mask;
 	return pte;
 }
 
 static inline pmd_t pfn_pmd(unsigned long page_nr, pgprot_t pgprot)
 {
-	return __pmd(((unsigned long long)page_nr << PAGE_SHIFT) | pgprot_val(pgprot));
+	return __pmd((((unsigned long long)page_nr << PAGE_SHIFT) | \
+			pgprot_val(pgprot)) & __supported_pte_mask);
 }
 
 /*
include/asm-i386/pgtable.h renamed from include/asm-i386/pgtable.h.orig +49 -16

108 lines not on this page · raw message

fs/binfmt_elf.c renamed from fs/binfmt_elf.c.orig +5 -0
@@ -491,6 +491,7 @@ static int load_elf_binary(struct linux_
 	char passed_fileno[6];
 	struct files_struct *files;
 	int executable_stack = EXSTACK_DEFAULT;
+	unsigned long def_flags = 0;
 	
 	/* Get the exec-header */
 	elf_ex = *((struct elfhdr *) bprm->buf);
@@ -622,7 +623,10 @@ static int load_elf_binary(struct linux_
 				executable_stack = EXSTACK_ENABLE_X;
 			else
 				executable_stack = EXSTACK_DISABLE_X;
+			break;
 		}
+	if (i == elf_ex.e_phnum)
+		def_flags |= VM_EXEC | VM_MAYEXEC;
 
 	/* Some simple consistency checks for the interpreter */
 	if (elf_interpreter) {
@@ -690,6 +694,7 @@ static int load_elf_binary(struct linux_
 	current->mm->end_code = 0;
 	current->mm->mmap = NULL;
 	current->flags &= ~PF_FORKNOEXEC;
+	current->mm->def_flags = def_flags;
 
 	/* Do this immediately, since STACK_TOP as used in setup_arg_pages
 	   may depend on the personality.  */
fs/exec.c renamed from fs/exec.c.orig +1 -0
@@ -431,6 +431,7 @@ int setup_arg_pages(struct linux_binprm 
 			mpnt->vm_flags = VM_STACK_FLAGS & ~VM_EXEC;
 		else
 			mpnt->vm_flags = VM_STACK_FLAGS;
+		mpnt->vm_flags |= mm->def_flags;
 		mpnt->vm_page_prot = protection_map[mpnt->vm_flags & 0x7];
 		insert_vm_struct(mm, mpnt);
 		mm->total_vm = (mpnt->vm_end - mpnt->vm_start) >> PAGE_SHIFT;
mm/vmalloc.c renamed from mm/vmalloc.c.orig +22 -0
@@ -456,6 +456,28 @@ void *vmalloc(unsigned long size)
 EXPORT_SYMBOL(vmalloc);
 
 /**
+ *	vmalloc_exec  -  allocate virtually contiguous, executable memory
+ *
+ *	@size:		allocation size
+ *
+ *	Kernel-internal function to allocate enough pages to cover @size
+ *	the page level allocator and map them into contiguous and
+ *	executable kernel virtual space.
+ *
+ *	For tight cotrol over page level allocator and protection flags
+ *	use __vmalloc() instead.
+ */
+
+#ifndef PAGE_KERNEL_EXEC
+# define PAGE_KERNEL_EXEC PAGE_KERNEL
+#endif
+
+void *vmalloc_exec(unsigned long size)
+{
+	return __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL_EXEC);
+}
+
+/**
  *	vmalloc_32  -  allocate virtually contiguous memory (32bit addressable)
  *
  *	@size:		allocation size
#1 lore raw
Quoted 11 lines · Ingo Molnar

the flexible-mmap-2.6.7-D5 patch (attached) splits the flexible-mmap layout stuff out of exec-shield. The patch is against 2.6.7 + my latest NX patch. (i've attached the NX patch too, for convenience.) the goal of the patch is to change the way virtual memory is allocated, from:
0x08000000 ... binary image
0x08xxxxxx ... brk area, grows upwards
0x40000000 ... start of mmap, new mmaps go after old ones 0xbfxxxxxx ... stack
to a more flexible top-down mmap() method:

Quoted 4 lines

0x08000000 ... binary image
0x08xxxxxx ... brk area, grows upwards
0xbfxxxxxx ... _end_ of all mmaps, new mmaps go below old ones 0xbfyyyyyy ... stack

It may be useful to move STACK_TOP to 0x8040000 give the stack more head room and fit small statically-linked executables into one pagetable page. Otherwise the stack has very little headroom after the first mmap().

-- wli

#2 lore raw
Quoted 11 lines · William Lee Irwin III
Quoted 6 lines

to a more flexible top-down mmap() method:

0x08000000 ... binary image
0x08xxxxxx ... brk area, grows upwards
0xbfxxxxxx ... _end_ of all mmaps, new mmaps go below old ones 0xbfyyyyyy ... stack

It may be useful to move STACK_TOP to 0x8040000 give the stack more head room and fit small statically-linked executables into one pagetable page. Otherwise the stack has very little headroom after the first mmap().

the patch already takes care of this via two mechanisms:

- the top of the mmaps is moved down by the expected maximum size of the stack (the stack rlimit).

- we maintain a minimum 128 MB 'gap' between stack top and mmap top.

moving it to 0x08040000 is asking for trouble, there are people who need 1GB stacks (dont ask why). This approach has been in production use for ~2 years and it works fine for a wide range of applications and VM needs.

Ingo

#3 lore raw
Quoted 8 lines · Ingo Molnar

the patch already takes care of this via two mechanisms:
- the top of the mmaps is moved down by the expected maximum size of the stack (the stack rlimit).
- we maintain a minimum 128 MB 'gap' between stack top and mmap top. moving it to 0x08040000 is asking for trouble, there are people who need 1GB stacks (dont ask why). This approach has been in production use for ~2 years and it works fine for a wide range of applications and VM needs.

I'm not terribly convinced the stack's virtual placement helps in the case where 1GB stacks are needed as within rlimits, it's usually lazily expanded. I suppose the users in need of it adjust their rlimits, say, in the shell spawning the process wanting it to precisely what they need in this scheme, which is awkward, but doesn't require code modification for the offending apps.

Also, I suspect some more graceful fallback would make sense particularly for the case of RLIM_INFINITY, which would leave users that run with, say, all rlimits at RLIM_INFINITY in the interest of having full access to system resources with a mere 512MB of virtualspace for the heap, which IIRC glibc is intelligent enough to circumvent for malloc(), but not for mmap(NULL, ...). If it's been in production that long, I find it hard to believe that's never been tripped over. This is one of the reasons that when I thought about this and split off an isolated patch for top-down vma allocation from execshield, I gave up immediately on the rlimits and said "let them malloc() their own stacks", and shoved the default down low (also, that 128MB is currently wasted); the rlimits appeared to need too precise of tuning and be easy to mis-tune. But that was a matter of thinking about it, not experience.

These are questions whose answers largely do not affect me personally or anything I work on. If the answer is "it suffices as-is" I won't get worked up about it. I didn't raise the RLIM_INFINITY point in my prior post (b/c I missed the rlimit bit), so for the sake of completeness I droned on and on about it here.

Last and definitely least, the declaration in sched.h of functions only used in mmap.c set off some warning flags; it may make sense to just declare the things in mm/mmap.c, possibly with __attribute__((weak)) or some such (AIUI this is now allowed, the last buggy arches' toolchains were fixed, and it's already used now with cond_syscall() etc.). There is a bit of nastiness with sched.h being a "garbage can" header it'd be nice to avoid aggravating.

Thanks.

-- wli

#4 lore raw
Quoted 6 lines · William Lee Irwin III

Also, I suspect some more graceful fallback would make sense particularly for the case of RLIM_INFINITY, which would leave users that run with, say, all rlimits at RLIM_INFINITY in the interest of having full access to system resources with a mere 512MB of virtualspace for the heap, which IIRC glibc is intelligent enough to circumvent for malloc(), but not for mmap(NULL, ...). [...]

well, the 5/6=stack 1/6=malloc rule in the RLIM_INFINITY can be changed. What would make the most sense - 1/2 for both?

Quoted 2 lines

If it's been in production that long, I find it hard to believe that's never been tripped over. [...]

it's been tripped over and the 5/6 rule was a fix for such a bugreport. What happens more in practice frequently is that someone needs a big stack and sets the stack ulimit to RLIM_INFINITY.

Quoted 1 line

[...] (also, that 128MB is currently wasted); [...]

the 128MB is 'wasted' to give some flexibility to the stack rlimits changing runtime. But in practice it's far more important to have the mmap()/malloc() space maximized and flexible than to give the stack automatic flexibility.

Ingo

#5 lore raw
Quoted 6 lines · William Lee Irwin III
Quoted 6 lines

Also, I suspect some more graceful fallback would make sense particularly for the case of RLIM_INFINITY, which would leave users that run with, say, all rlimits at RLIM_INFINITY in the interest of having full access to system resources with a mere 512MB of virtualspace for the heap, which IIRC glibc is intelligent enough to circumvent for malloc(), but not for mmap(NULL, ...). [...]

Quoted 2 lines · Ingo Molnar

well, the 5/6=stack 1/6=malloc rule in the RLIM_INFINITY can be changed. What would make the most sense - 1/2 for both?

I had in mind fallback as opposed to a changed base, but a particular choice of the base may cover enough cases. The bugreport below seems to say there's no need for a change.

Quoted 2 lines · William Lee Irwin III
Quoted 2 lines

If it's been in production that long, I find it hard to believe that's never been tripped over. [...]

Quoted 3 lines · Ingo Molnar

it's been tripped over and the 5/6 rule was a fix for such a bugreport. What happens more in practice frequently is that someone needs a big stack and sets the stack ulimit to RLIM_INFINITY.

This sounds like nothing is needed, then.

Quoted 1 line · William Lee Irwin III
Quoted 1 line

[...] (also, that 128MB is currently wasted); [...]

Quoted 4 lines · Ingo Molnar

the 128MB is 'wasted' to give some flexibility to the stack rlimits changing runtime. But in practice it's far more important to have the mmap()/malloc() space maximized and flexible than to give the stack automatic flexibility.

Fishing around down there to utilize it for mmap() placement should happen anyway if things fall back far enough in the top-down scheme.

Answers like "I've thought about it" or "I've seen this and dealt with it" are good enough for me. There isn't much of a normative aspect to user virtualspace layout.

Thanks.

-- wli

#6 lore raw
In reply to Ingo Molnar
Quoted 3 lines · Ingo Molnar

- i also introduced a new personality bit (ADDR_COMPAT_LAYOUT) to signal older binaries that dont have PT_GNU_STACK. x86 uses this to revert back to the stock layout. [...]

i've also attached a patch below against setarch that adds the -L option which turns on the old layout. Thus if there's any old broken app that relies on mmap()s being below 2GB then the following can be used:

setarch -L oldapp <params>

and 'oldapp' (and all its children) will run with the old layout. But this should be an extremely rare occurance, i've added it more for completeness and paranoia reasons than necessity.

Ingo

setarch.c renamed from setarch.c.orig +9 -0
@@ -118,6 +118,15 @@ int main(int argc, char *argv[])
       if(got_arch)
 	p = argv[0];
     }
+  if(!strcmp(p, "-L"))
+    {
+      argv++;
+      argc--;
+      options |= 0x0200000 /* ADDR_COMPAT_LAYOUT */;
+      if(got_arch)
+	p = argv[0];
+    }
+
 
   if(set_arch(p, options))
     {