linux-user: Fix mmap_find_vma returning invalid addresses.

Don't return addresses that aren't properly aligned for the guest,
e.g. when the guest has a larger page size than the host.  Don't
return addresses that are outside the virtual address space for the
target, by paying proper attention to the h2g/g2h macros.

At the same time, place the default mapping base for 64-bit guests
(on 64-bit hosts) outside the low 4G.  Consistently interpret
mmap_next_start in the guest address space.

Signed-off-by: Richard Henderson <rth@twiddle.net>
This commit is contained in:
Richard Henderson 2010-03-10 15:39:07 -08:00 committed by Paul Brook
parent 7bc7b099df
commit 14f24e1465
3 changed files with 82 additions and 33 deletions

View file

@ -39,8 +39,8 @@
char *exec_path;
int singlestep;
#if defined(CONFIG_USE_GUEST_BASE)
unsigned long mmap_min_addr;
#if defined(CONFIG_USE_GUEST_BASE)
unsigned long guest_base;
int have_guest_base;
#endif
@ -2812,16 +2812,14 @@ int main(int argc, char **argv, char **envp)
* proper page alignment for guest_base.
*/
guest_base = HOST_PAGE_ALIGN(guest_base);
#endif /* CONFIG_USE_GUEST_BASE */
/*
* Read in mmap_min_addr kernel parameter. This value is used
* When loading the ELF image to determine whether guest_base
* is needed.
*
* When user has explicitly set the quest base, we skip this
* test.
* is needed. It is also used in mmap_find_vma.
*/
if (!have_guest_base) {
{
FILE *fp;
if ((fp = fopen("/proc/sys/vm/mmap_min_addr", "r")) != NULL) {
@ -2833,7 +2831,6 @@ int main(int argc, char **argv, char **envp)
fclose(fp);
}
}
#endif /* CONFIG_USE_GUEST_BASE */
/*
* Prepare copy of argv vector for target.

View file

@ -264,12 +264,15 @@ static int mmap_frag(abi_ulong real_start,
return 0;
}
#if defined(__CYGWIN__)
#if HOST_LONG_BITS == 64 && TARGET_ABI_BITS == 64
# define TASK_UNMAPPED_BASE (1ul << 38)
#elif defined(__CYGWIN__)
/* Cygwin doesn't have a whole lot of address space. */
static abi_ulong mmap_next_start = 0x18000000;
# define TASK_UNMAPPED_BASE 0x18000000
#else
static abi_ulong mmap_next_start = 0x40000000;
# define TASK_UNMAPPED_BASE 0x40000000
#endif
static abi_ulong mmap_next_start = TASK_UNMAPPED_BASE;
unsigned long last_brk;
@ -281,19 +284,24 @@ unsigned long last_brk;
*/
abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)
{
void *ptr;
void *ptr, *prev;
abi_ulong addr;
size = HOST_PAGE_ALIGN(size);
start &= qemu_host_page_mask;
int wrapped, repeat;
/* If 'start' == 0, then a default start address is used. */
if (start == 0)
if (start == 0) {
start = mmap_next_start;
} else {
start &= qemu_host_page_mask;
}
size = HOST_PAGE_ALIGN(size);
addr = start;
wrapped = repeat = 0;
prev = 0;
for(;;) {
for (;; prev = ptr) {
/*
* Reserve needed memory area to avoid a race.
* It should be discarded using:
@ -301,31 +309,77 @@ abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)
* - mremap() with MREMAP_FIXED flag
* - shmat() with SHM_REMAP flag
*/
ptr = mmap((void *)(unsigned long)addr, size, PROT_NONE,
ptr = mmap(g2h(addr), size, PROT_NONE,
MAP_ANONYMOUS|MAP_PRIVATE|MAP_NORESERVE, -1, 0);
/* ENOMEM, if host address space has no memory */
if (ptr == MAP_FAILED)
if (ptr == MAP_FAILED) {
return (abi_ulong)-1;
}
/* If address fits target address space we've found what we need */
if ((unsigned long)ptr + size - 1 <= (abi_ulong)-1)
break;
/* Count the number of sequential returns of the same address.
This is used to modify the search algorithm below. */
repeat = (ptr == prev ? repeat + 1 : 0);
/* Unmap and try again with new page */
if (h2g_valid(ptr + size - 1)) {
addr = h2g(ptr);
if ((addr & ~TARGET_PAGE_MASK) == 0) {
/* Success. */
if (start == mmap_next_start && addr >= TASK_UNMAPPED_BASE) {
mmap_next_start = addr + size;
}
return addr;
}
/* The address is not properly aligned for the target. */
switch (repeat) {
case 0:
/* Assume the result that the kernel gave us is the
first with enough free space, so start again at the
next higher target page. */
addr = TARGET_PAGE_ALIGN(addr);
break;
case 1:
/* Sometimes the kernel decides to perform the allocation
at the top end of memory instead. */
addr &= TARGET_PAGE_MASK;
break;
case 2:
/* Start over at low memory. */
addr = 0;
break;
default:
/* Fail. This unaligned block must the last. */
addr = -1;
break;
}
} else {
/* Since the result the kernel gave didn't fit, start
again at low memory. If any repetition, fail. */
addr = (repeat ? -1 : 0);
}
/* Unmap and try again. */
munmap(ptr, size);
addr += qemu_host_page_size;
/* ENOMEM if we check whole of target address space */
if (addr == start)
/* ENOMEM if we checked the whole of the target address space. */
if (addr == -1ul) {
return (abi_ulong)-1;
} else if (addr == 0) {
if (wrapped) {
return (abi_ulong)-1;
}
wrapped = 1;
/* Don't actually use 0 when wrapping, instead indicate
that we'd truely like an allocation in low memory. */
addr = (mmap_min_addr > TARGET_PAGE_SIZE
? TARGET_PAGE_ALIGN(mmap_min_addr)
: TARGET_PAGE_SIZE);
} else if (wrapped && addr >= start) {
return (abi_ulong)-1;
}
}
/* Update default start address */
if (start == mmap_next_start)
mmap_next_start = (unsigned long)ptr + size;
return h2g(ptr);
}
/* NOTE: all the constants are the HOST ones */

View file

@ -133,9 +133,7 @@ void init_task_state(TaskState *ts);
void task_settid(TaskState *);
void stop_all_tasks(void);
extern const char *qemu_uname_release;
#if defined(CONFIG_USE_GUEST_BASE)
extern unsigned long mmap_min_addr;
#endif
/* ??? See if we can avoid exposing so much of the loader internals. */
/*