6b008640db
Instead of having callers care about the mmap_min_addr logic for the
lowest valid mapping address (and some of them getting it wrong), just
move the logic into vm_unmapped_area() itself. One less thing for various
architecture cases (and generic helpers) to worry about.
We should really try to make much more of this be common code, but baby
steps..
Without this, vm_unmapped_area() could return an address below
mmap_min_addr (because some caller forgot about that). That then causes
the mmap machinery to think it has found a workable address, but then
later security_mmap_addr(addr) is unhappy about it and the mmap() returns
with a nonsensical error (EPERM).
The proper action is to either return ENOMEM (if the virtual address space
is exhausted), or try to find another address (ie do a bottom-up search
for free addresses after the top-down one failed).
See commit 2afc745f3e
("mm: ensure get_unmapped_area() returns higher
address than mmap_min_addr"), which fixed this for one call site (the
generic arch_get_unmapped_area_topdown() fallback) but left other cases
alone.
Link: https://lkml.kernel.org/r/20230418214009.1142926-1-Liam.Howlett@oracle.com
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Liam R. Howlett <Liam.Howlett@oracle.com>
Cc: Russell King <linux@armlinux.org.uk>
Cc: Liam Howlett <liam.howlett@oracle.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
211 lines
5.3 KiB
C
211 lines
5.3 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);
|
|
}
|
|
|
|
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;
|
|
if (filp || (flags & MAP_SHARED))
|
|
info.align_mask = MMAP_ALIGN_MASK << PAGE_SHIFT;
|
|
else
|
|
info.align_mask = 0;
|
|
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;
|
|
if (filp || (flags & MAP_SHARED))
|
|
info.align_mask = MMAP_ALIGN_MASK << PAGE_SHIFT;
|
|
else
|
|
info.align_mask = 0;
|
|
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
|