mirror of
https://github.com/torvalds/linux.git
synced 2024-11-26 06:02:05 +00:00
86f48f922b
Stefan reported a test case fail in libc. The test runs with
randomize_va_space set to zero, i.e. disabled randomization. Additionally,
it runs the program with the dynamic loader. Looking at the failure showed
that the heap was placed right before some pages mapped from the binary.
This made memory allocation fail after a few allocations.
Normally, when address randomization is switched off and the binary is
loaded from the dynamic loader, the kernel places the binary below the
128MB top gap. So the address map would look like this:
3fff7fd1000-3fff7fd2000 r--p 00000000 5e:01 1447115 /lib/ld64.so.1
3fff7fd2000-3fff7ff2000 r-xp 00001000 5e:01 1447115 /lib/ld64.so.1
3fff7ff2000-3fff7ffc000 r--p 00021000 5e:01 1447115 /lib/ld64.so.1
3fff7ffc000-3fff7ffe000 r--p 0002a000 5e:01 1447115 /lib/ld64.so.1
3fff7ffe000-3fff8000000 rw-p 0002c000 5e:01 1447115 /lib/ld64.so.1
3fff8000000-3fff8021000 rw-p 00000000 00:00 0 [heap]
3fffffda000-3ffffffb000 rw-p 00000000 00:00 0 [stack]
3ffffffc000-3ffffffe000 r--p 00000000 00:00 0 [vvar]
3ffffffe000-40000000000 r-xp 00000000 00:00 0 [vdso]
However, commit 1f6b83e5e4
("s390: avoid z13 cache aliasing") introduced
a mmap alignment mask of 8MB. With this commit, the memory map now
looks like this:
3fff7f80000-3fff7f81000 r--p 00000000 5e:01 1447115 /lib/ld64.so.1
3fff7f81000-3fff7fa1000 r-xp 00001000 5e:01 1447115 /lib/ld64.so.1
3fff7fa1000-3fff7fab000 r--p 00021000 5e:01 1447115 /lib/ld64.so.1
3fff7fab000-3fff7fad000 r--p 0002a000 5e:01 1447115 /lib/ld64.so.1
3fff7fad000-3fff7faf000 rw-p 0002c000 5e:01 1447115 /lib/ld64.so.1
3fff7faf000-3fff7fd0000 rw-p 00000000 00:00 0 [heap]
3fff7fdc000-3fff8000000 rw-p 00000000 00:00 0
3fffffda000-3ffffffb000 rw-p 00000000 00:00 0 [stack]
3ffffffc000-3ffffffe000 r--p 00000000 00:00 0 [vvar]
3ffffffe000-40000000000 r-xp 00000000 00:00 0 [vdso]
The reason for this placement is that the elf loader loads the binary to
end at mmap_base (0x3fff8000000 on s390). This would result in a start
address of 0x3fff7fd1000, but due to the alignment requirement of 8MB,
mmap chooses 0x3fff7f80000. This causes a gap between the end of the
mapped binary and mmap_base. When the next non-shared and non-file pages
are mapped, mmap searches from top to bottom and the first free space it
finds is the gap which is now present. This leaves only a few pages for
the heap. With enabled address space randomization this doesn't happen
because the binary is mapped to a completely different memory area.
Fix this by disabling the mmap alignment when address space randomization
is disabled. This is in line with what other architectures are doing.
Reported-by: Stefan Liebler <stli@linux.ibm.com>
Tested-by: Stefan Liebler <stli@linux.ibm.com>
Signed-off-by: Sven Schnelle <svens@linux.ibm.com>
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
214 lines
5.4 KiB
C
214 lines
5.4 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* flexible mmap layout support
|
|
*
|
|
* Copyright 2003-2004 Red Hat Inc., Durham, North Carolina.
|
|
* All Rights Reserved.
|
|
*
|
|
* Started by Ingo Molnar <mingo@elte.hu>
|
|
*/
|
|
|
|
#include <linux/elf-randomize.h>
|
|
#include <linux/personality.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/mman.h>
|
|
#include <linux/sched/signal.h>
|
|
#include <linux/sched/mm.h>
|
|
#include <linux/random.h>
|
|
#include <linux/compat.h>
|
|
#include <linux/security.h>
|
|
#include <asm/elf.h>
|
|
|
|
static unsigned long stack_maxrandom_size(void)
|
|
{
|
|
if (!(current->flags & PF_RANDOMIZE))
|
|
return 0;
|
|
return STACK_RND_MASK << PAGE_SHIFT;
|
|
}
|
|
|
|
static inline int mmap_is_legacy(struct rlimit *rlim_stack)
|
|
{
|
|
if (current->personality & ADDR_COMPAT_LAYOUT)
|
|
return 1;
|
|
if (rlim_stack->rlim_cur == RLIM_INFINITY)
|
|
return 1;
|
|
return sysctl_legacy_va_layout;
|
|
}
|
|
|
|
unsigned long arch_mmap_rnd(void)
|
|
{
|
|
return (get_random_u32() & MMAP_RND_MASK) << PAGE_SHIFT;
|
|
}
|
|
|
|
static unsigned long mmap_base_legacy(unsigned long rnd)
|
|
{
|
|
return TASK_UNMAPPED_BASE + rnd;
|
|
}
|
|
|
|
static inline unsigned long mmap_base(unsigned long rnd,
|
|
struct rlimit *rlim_stack)
|
|
{
|
|
unsigned long gap = rlim_stack->rlim_cur;
|
|
unsigned long pad = stack_maxrandom_size() + stack_guard_gap;
|
|
unsigned long gap_min, gap_max;
|
|
|
|
/* Values close to RLIM_INFINITY can overflow. */
|
|
if (gap + pad > gap)
|
|
gap += pad;
|
|
|
|
/*
|
|
* Top of mmap area (just below the process stack).
|
|
* Leave at least a ~128 MB hole.
|
|
*/
|
|
gap_min = SZ_128M;
|
|
gap_max = (STACK_TOP / 6) * 5;
|
|
|
|
if (gap < gap_min)
|
|
gap = gap_min;
|
|
else if (gap > gap_max)
|
|
gap = gap_max;
|
|
|
|
return PAGE_ALIGN(STACK_TOP - gap - rnd);
|
|
}
|
|
|
|
static int get_align_mask(struct file *filp, unsigned long flags)
|
|
{
|
|
if (!(current->flags & PF_RANDOMIZE))
|
|
return 0;
|
|
if (filp || (flags & MAP_SHARED))
|
|
return MMAP_ALIGN_MASK << PAGE_SHIFT;
|
|
return 0;
|
|
}
|
|
|
|
unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr,
|
|
unsigned long len, unsigned long pgoff,
|
|
unsigned long flags)
|
|
{
|
|
struct mm_struct *mm = current->mm;
|
|
struct vm_area_struct *vma;
|
|
struct vm_unmapped_area_info info;
|
|
|
|
if (len > TASK_SIZE - mmap_min_addr)
|
|
return -ENOMEM;
|
|
|
|
if (flags & MAP_FIXED)
|
|
goto check_asce_limit;
|
|
|
|
if (addr) {
|
|
addr = PAGE_ALIGN(addr);
|
|
vma = find_vma(mm, addr);
|
|
if (TASK_SIZE - len >= addr && addr >= mmap_min_addr &&
|
|
(!vma || addr + len <= vm_start_gap(vma)))
|
|
goto check_asce_limit;
|
|
}
|
|
|
|
info.flags = 0;
|
|
info.length = len;
|
|
info.low_limit = mm->mmap_base;
|
|
info.high_limit = TASK_SIZE;
|
|
info.align_mask = get_align_mask(filp, flags);
|
|
info.align_offset = pgoff << PAGE_SHIFT;
|
|
addr = vm_unmapped_area(&info);
|
|
if (offset_in_page(addr))
|
|
return addr;
|
|
|
|
check_asce_limit:
|
|
return check_asce_limit(mm, addr, len);
|
|
}
|
|
|
|
unsigned long arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr,
|
|
unsigned long len, unsigned long pgoff,
|
|
unsigned long flags)
|
|
{
|
|
struct vm_area_struct *vma;
|
|
struct mm_struct *mm = current->mm;
|
|
struct vm_unmapped_area_info info;
|
|
|
|
/* requested length too big for entire address space */
|
|
if (len > TASK_SIZE - mmap_min_addr)
|
|
return -ENOMEM;
|
|
|
|
if (flags & MAP_FIXED)
|
|
goto check_asce_limit;
|
|
|
|
/* requesting a specific address */
|
|
if (addr) {
|
|
addr = PAGE_ALIGN(addr);
|
|
vma = find_vma(mm, addr);
|
|
if (TASK_SIZE - len >= addr && addr >= mmap_min_addr &&
|
|
(!vma || addr + len <= vm_start_gap(vma)))
|
|
goto check_asce_limit;
|
|
}
|
|
|
|
info.flags = VM_UNMAPPED_AREA_TOPDOWN;
|
|
info.length = len;
|
|
info.low_limit = PAGE_SIZE;
|
|
info.high_limit = mm->mmap_base;
|
|
info.align_mask = get_align_mask(filp, flags);
|
|
info.align_offset = pgoff << PAGE_SHIFT;
|
|
addr = vm_unmapped_area(&info);
|
|
|
|
/*
|
|
* A failed mmap() very likely causes application failure,
|
|
* so fall back to the bottom-up function here. This scenario
|
|
* can happen with large stack limits and large mmap()
|
|
* allocations.
|
|
*/
|
|
if (offset_in_page(addr)) {
|
|
VM_BUG_ON(addr != -ENOMEM);
|
|
info.flags = 0;
|
|
info.low_limit = TASK_UNMAPPED_BASE;
|
|
info.high_limit = TASK_SIZE;
|
|
addr = vm_unmapped_area(&info);
|
|
if (offset_in_page(addr))
|
|
return addr;
|
|
}
|
|
|
|
check_asce_limit:
|
|
return check_asce_limit(mm, addr, len);
|
|
}
|
|
|
|
/*
|
|
* 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, struct rlimit *rlim_stack)
|
|
{
|
|
unsigned long random_factor = 0UL;
|
|
|
|
if (current->flags & PF_RANDOMIZE)
|
|
random_factor = arch_mmap_rnd();
|
|
|
|
/*
|
|
* Fall back to the standard layout if the personality
|
|
* bit is set, or if the expected stack growth is unlimited:
|
|
*/
|
|
if (mmap_is_legacy(rlim_stack)) {
|
|
mm->mmap_base = mmap_base_legacy(random_factor);
|
|
mm->get_unmapped_area = arch_get_unmapped_area;
|
|
} else {
|
|
mm->mmap_base = mmap_base(random_factor, rlim_stack);
|
|
mm->get_unmapped_area = arch_get_unmapped_area_topdown;
|
|
}
|
|
}
|
|
|
|
static const pgprot_t protection_map[16] = {
|
|
[VM_NONE] = PAGE_NONE,
|
|
[VM_READ] = PAGE_RO,
|
|
[VM_WRITE] = PAGE_RO,
|
|
[VM_WRITE | VM_READ] = PAGE_RO,
|
|
[VM_EXEC] = PAGE_RX,
|
|
[VM_EXEC | VM_READ] = PAGE_RX,
|
|
[VM_EXEC | VM_WRITE] = PAGE_RX,
|
|
[VM_EXEC | VM_WRITE | VM_READ] = PAGE_RX,
|
|
[VM_SHARED] = PAGE_NONE,
|
|
[VM_SHARED | VM_READ] = PAGE_RO,
|
|
[VM_SHARED | VM_WRITE] = PAGE_RW,
|
|
[VM_SHARED | VM_WRITE | VM_READ] = PAGE_RW,
|
|
[VM_SHARED | VM_EXEC] = PAGE_RX,
|
|
[VM_SHARED | VM_EXEC | VM_READ] = PAGE_RX,
|
|
[VM_SHARED | VM_EXEC | VM_WRITE] = PAGE_RWX,
|
|
[VM_SHARED | VM_EXEC | VM_WRITE | VM_READ] = PAGE_RWX
|
|
};
|
|
DECLARE_VM_GET_PAGE_PROT
|