diff --git a/linux-user/elfload.c b/linux-user/elfload.c index 04e8e6e065..8677bba0d8 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -332,6 +332,49 @@ enum ARM_HWCAP_ARM_VFPv3D16 = 1 << 13, }; +#define TARGET_HAS_GUEST_VALIDATE_BASE +/* We want the opportunity to check the suggested base */ +bool guest_validate_base(unsigned long guest_base) +{ + unsigned long real_start, test_page_addr; + + /* We need to check that we can force a fault on access to the + * commpage at 0xffff0fxx + */ + test_page_addr = guest_base + (0xffff0f00 & qemu_host_page_mask); + /* Note it needs to be writeable to let us initialise it */ + real_start = (unsigned long) + mmap((void *)test_page_addr, qemu_host_page_size, + PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + /* If we can't map it then try another address */ + if (real_start == -1ul) { + return 0; + } + + if (real_start != test_page_addr) { + /* OS didn't put the page where we asked - unmap and reject */ + munmap((void *)real_start, qemu_host_page_size); + return 0; + } + + /* Leave the page mapped + * Populate it (mmap should have left it all 0'd) + */ + + /* Kernel helper versions */ + __put_user(5, (uint32_t *)g2h(0xffff0ffcul)); + + /* Now it's populated make it RO */ + if (mprotect((void *)test_page_addr, qemu_host_page_size, PROT_READ)) { + perror("Protecting guest commpage"); + exit(-1); + } + + return 1; /* All good */ +} + #define ELF_HWCAP (ARM_HWCAP_ARM_SWP | ARM_HWCAP_ARM_HALF \ | ARM_HWCAP_ARM_THUMB | ARM_HWCAP_ARM_FAST_MULT \ | ARM_HWCAP_ARM_FPA | ARM_HWCAP_ARM_VFP \ @@ -1309,6 +1352,14 @@ static abi_ulong create_elf_tables(abi_ulong p, int argc, int envc, return sp; } +#ifndef TARGET_HAS_GUEST_VALIDATE_BASE +/* If the guest doesn't have a validation function just agree */ +bool guest_validate_base(unsigned long guest_base) +{ + return 1; +} +#endif + static void probe_guest_base(const char *image_name, abi_ulong loaddr, abi_ulong hiaddr) { @@ -1345,7 +1396,9 @@ static void probe_guest_base(const char *image_name, if (real_start == (unsigned long)-1) { goto exit_perror; } - if (real_start == host_start) { + guest_base = real_start - loaddr; + if ((real_start == host_start) && + guest_validate_base(guest_base)) { break; } /* That address didn't work. Unmap and try a different one. @@ -1368,7 +1421,6 @@ static void probe_guest_base(const char *image_name, qemu_log("Relocating guest address space from 0x" TARGET_ABI_FMT_lx " to 0x%lx\n", loaddr, real_start); - guest_base = real_start - loaddr; } return; diff --git a/linux-user/main.c b/linux-user/main.c index 89a51d76cd..25cb4ddf36 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -456,6 +456,83 @@ void cpu_loop(CPUX86State *env) #ifdef TARGET_ARM +/* + * See the Linux kernel's Documentation/arm/kernel_user_helpers.txt + * Input: + * r0 = pointer to oldval + * r1 = pointer to newval + * r2 = pointer to target value + * + * Output: + * r0 = 0 if *ptr was changed, non-0 if no exchange happened + * C set if *ptr was changed, clear if no exchange happened + * + * Note segv's in kernel helpers are a bit tricky, we can set the + * data address sensibly but the PC address is just the entry point. + */ +static void arm_kernel_cmpxchg64_helper(CPUARMState *env) +{ + uint64_t oldval, newval, val; + uint32_t addr, cpsr; + target_siginfo_t info; + + /* Based on the 32 bit code in do_kernel_trap */ + + /* XXX: This only works between threads, not between processes. + It's probably possible to implement this with native host + operations. However things like ldrex/strex are much harder so + there's not much point trying. */ + start_exclusive(); + cpsr = cpsr_read(env); + addr = env->regs[2]; + + if (get_user_u64(oldval, env->regs[0])) { + env->cp15.c6_data = env->regs[0]; + goto segv; + }; + + if (get_user_u64(newval, env->regs[1])) { + env->cp15.c6_data = env->regs[1]; + goto segv; + }; + + if (get_user_u64(val, addr)) { + env->cp15.c6_data = addr; + goto segv; + } + + if (val == oldval) { + val = newval; + + if (put_user_u64(val, addr)) { + env->cp15.c6_data = addr; + goto segv; + }; + + env->regs[0] = 0; + cpsr |= CPSR_C; + } else { + env->regs[0] = -1; + cpsr &= ~CPSR_C; + } + cpsr_write(env, cpsr, CPSR_C); + end_exclusive(); + return; + +segv: + end_exclusive(); + /* We get the PC of the entry address - which is as good as anything, + on a real kernel what you get depends on which mode it uses. */ + info.si_signo = SIGSEGV; + info.si_errno = 0; + /* XXX: check env->error_code */ + info.si_code = TARGET_SEGV_MAPERR; + info._sifields._sigfault._addr = env->cp15.c6_data; + queue_signal(env, info.si_signo, &info); + + end_exclusive(); +} + /* Handle a jump to the kernel code page. */ static int do_kernel_trap(CPUARMState *env) @@ -495,6 +572,10 @@ do_kernel_trap(CPUARMState *env) case 0xffff0fe0: /* __kernel_get_tls */ env->regs[0] = env->cp15.c13_tls2; break; + case 0xffff0f60: /* __kernel_cmpxchg64 */ + arm_kernel_cmpxchg64_helper(env); + break; + default: return 1; } @@ -752,7 +833,6 @@ void cpu_loop(CPUARMState *env) goto do_segv; case EXCP_DATA_ABORT: addr = env->cp15.c6_data; - goto do_segv; do_segv: { info.si_signo = SIGSEGV; @@ -3180,6 +3260,13 @@ int main(int argc, char **argv, char **envp) } qemu_log("Reserved 0x%lx bytes of guest address space\n", reserved_va); } + + if (reserved_va || have_guest_base) { + if (!guest_validate_base(guest_base)) { + fprintf(stderr, "Guest base/Reserved VA rejected by guest code\n"); + exit(1); + } + } #endif /* CONFIG_USE_GUEST_BASE */ /* diff --git a/linux-user/qemu.h b/linux-user/qemu.h index 627c8b3423..55ad9d8586 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -202,6 +202,12 @@ int get_osversion(void); void fork_start(void); void fork_end(int child); +/* Return true if the proposed guest_base is suitable for the guest. + * The guest code may leave a page mapped and populate it if the + * address is suitable. + */ +bool guest_validate_base(unsigned long guest_base); + #include "qemu-log.h" /* strace.c */