linux/arch/x86/um/os-Linux/task_size.c
Johannes Berg bfc58e2b98 um: remove process stub VMA
This mostly reverts the old commit 3963333fe6 ("uml: cover stubs
with a VMA") which had added a VMA to the existing PTEs. However,
there's no real reason to have the PTEs in the first place and the
VMA cannot be 'fixed' in place, which leads to bugs that userspace
could try to unmap them and be forcefully killed, or such. Also,
there's a bit of an ugly hole in userspace's address space.

Simplify all this: just install the stub code/page at the top of
the (inner) address space, i.e. put it just above TASK_SIZE. The
pages are simply hard-coded to be mapped in the userspace process
we use to implement an mm context, and they're out of reach of the
inner mmap/munmap/mprotect etc. since they're above TASK_SIZE.

Getting rid of the VMA also makes vma_merge() no longer hit one of
the VM_WARN_ON()s there because we installed a VMA while the code
assumes the stack VMA is the first one.

It also removes a lockdep warning about mmap_sem usage since we no
longer have uml_setup_stubs() and thus no longer need to do any
manipulation that would require mmap_sem in activate_mm().

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Richard Weinberger <richard@nod.at>
2021-02-12 21:37:38 +01:00

152 lines
3.3 KiB
C

// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/mman.h>
#include <longjmp.h>
#ifdef __i386__
static jmp_buf buf;
static void segfault(int sig)
{
longjmp(buf, 1);
}
static int page_ok(unsigned long page)
{
unsigned long *address = (unsigned long *) (page << UM_KERN_PAGE_SHIFT);
unsigned long n = ~0UL;
void *mapped = NULL;
int ok = 0;
/*
* First see if the page is readable. If it is, it may still
* be a VDSO, so we go on to see if it's writable. If not
* then try mapping memory there. If that fails, then we're
* still in the kernel area. As a sanity check, we'll fail if
* the mmap succeeds, but gives us an address different from
* what we wanted.
*/
if (setjmp(buf) == 0)
n = *address;
else {
mapped = mmap(address, UM_KERN_PAGE_SIZE,
PROT_READ | PROT_WRITE,
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mapped == MAP_FAILED)
return 0;
if (mapped != address)
goto out;
}
/*
* Now, is it writeable? If so, then we're in user address
* space. If not, then try mprotecting it and try the write
* again.
*/
if (setjmp(buf) == 0) {
*address = n;
ok = 1;
goto out;
} else if (mprotect(address, UM_KERN_PAGE_SIZE,
PROT_READ | PROT_WRITE) != 0)
goto out;
if (setjmp(buf) == 0) {
*address = n;
ok = 1;
}
out:
if (mapped != NULL)
munmap(mapped, UM_KERN_PAGE_SIZE);
return ok;
}
unsigned long os_get_top_address(void)
{
struct sigaction sa, old;
unsigned long bottom = 0;
/*
* A 32-bit UML on a 64-bit host gets confused about the VDSO at
* 0xffffe000. It is mapped, is readable, can be reprotected writeable
* and written. However, exec discovers later that it can't be
* unmapped. So, just set the highest address to be checked to just
* below it. This might waste some address space on 4G/4G 32-bit
* hosts, but shouldn't hurt otherwise.
*/
unsigned long top = 0xffffd000 >> UM_KERN_PAGE_SHIFT;
unsigned long test, original;
printf("Locating the bottom of the address space ... ");
fflush(stdout);
/*
* We're going to be longjmping out of the signal handler, so
* SA_DEFER needs to be set.
*/
sa.sa_handler = segfault;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_NODEFER;
if (sigaction(SIGSEGV, &sa, &old)) {
perror("os_get_top_address");
exit(1);
}
/* Manually scan the address space, bottom-up, until we find
* the first valid page (or run out of them).
*/
for (bottom = 0; bottom < top; bottom++) {
if (page_ok(bottom))
break;
}
/* If we've got this far, we ran out of pages. */
if (bottom == top) {
fprintf(stderr, "Unable to determine bottom of address "
"space.\n");
exit(1);
}
printf("0x%lx\n", bottom << UM_KERN_PAGE_SHIFT);
printf("Locating the top of the address space ... ");
fflush(stdout);
original = bottom;
/* This could happen with a 4G/4G split */
if (page_ok(top))
goto out;
do {
test = bottom + (top - bottom) / 2;
if (page_ok(test))
bottom = test;
else
top = test;
} while (top - bottom > 1);
out:
/* Restore the old SIGSEGV handling */
if (sigaction(SIGSEGV, &old, NULL)) {
perror("os_get_top_address");
exit(1);
}
top <<= UM_KERN_PAGE_SHIFT;
printf("0x%lx\n", top);
return top;
}
#else
unsigned long os_get_top_address(void)
{
/* The old value of CONFIG_TOP_ADDR */
return 0x7fc0002000;
}
#endif