From patchwork Tue Jun 14 19:26:19 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joel Holdsworth X-Patchwork-Id: 635510 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3rTfn11LvLz9t0q for ; Wed, 15 Jun 2016 05:27:49 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=vcatechnology-com.20150623.gappssmtp.com header.i=@vcatechnology-com.20150623.gappssmtp.com header.b=C2zS5IaY; dkim-atps=neutral Received: from localhost ([::1]:37665 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1bCu03-0003lv-2r for incoming@patchwork.ozlabs.org; Tue, 14 Jun 2016 15:27:47 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:50847) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1bCtyw-0002R7-3m for qemu-devel@nongnu.org; Tue, 14 Jun 2016 15:26:39 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1bCtyq-000261-PG for qemu-devel@nongnu.org; Tue, 14 Jun 2016 15:26:37 -0400 Received: from mail-io0-x22c.google.com ([2607:f8b0:4001:c06::22c]:34470) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1bCtyq-00025v-IB for qemu-devel@nongnu.org; Tue, 14 Jun 2016 15:26:32 -0400 Received: by mail-io0-x22c.google.com with SMTP id 5so3657029ioy.1 for ; Tue, 14 Jun 2016 12:26:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=vcatechnology-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=zSTvbHVwbDocod4xwf3cHZwDPBCJfWTl49Va2VTXo0Y=; b=C2zS5IaYCS2abSuAtZgfOto/pLnn6KUc8gzA9rE58fn1Ga9I0kDiWUaTzVM5x6gIr3 vFwvcEO6JbOaSeYHvV09YIvGWraokoGF+FBWyu+4w5wugEHWQgGF6fX2XTK5/1teY1IS QYCeDD9yZkM75TvqSjSaeeXggw+WPL5k+lONmcDhrcnBKGO5p5eyYcBKX4yVWPz9Cyab zeY5+erBVWrk2WMpIYS0UWFw6IKDNq7aj8QfS5ASmoFOneTqM3UEfJCDO3iJMxcrwKl4 HDiUhJ7i4RFT3xNszvh7HMjLeTWrurHi2pm7y8KWPbv8XGeFvcdaT5JPGcBHMt8IHUcf x7/Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=zSTvbHVwbDocod4xwf3cHZwDPBCJfWTl49Va2VTXo0Y=; b=afonZK8Csvfi3UeLcHpuMZs8S2kvLLw4cxolzycWUpDdUkC8Y+1xagGDu5NW4+2IKJ 0xXFUFxypekLgneWnFjcU89F3TYXP6jbklJYmWERyzLHdg4Eup4f16pld+M7T1xuX+xb BvZp1TgqY7WPbrNzznfSc/tcQ22/m/+OrWmxtNzQcRtuyvfD52liUkLXzNYxuu+5v1Za OnBO4/Oqb1fWBc6JUNjmBeNo5aACUL1BFfdI6ckJhSJ+6HDai3ktRpqUPz+zFyHuuNAC P3VvoGpge0AgGkeihFwAwehFkzBjx/4q9/HKvf4D6K5HBVByzqNXKU4ZF9h/k6tHC1ti tw9w== X-Gm-Message-State: ALyK8tJQyAKWojtD5l0L3CkTdeBcnYKf/oTgdF1/2dG6LYjdcS0GC0ZPPIpvADUVgepiiABS X-Received: by 10.107.129.150 with SMTP id l22mr37641390ioi.170.1465932391822; Tue, 14 Jun 2016 12:26:31 -0700 (PDT) Received: from localhost.localdomain (50-205-134-174-static.hfc.comcastbusiness.net. [50.205.134.174]) by smtp.googlemail.com with ESMTPSA id w10sm2509039itc.1.2016.06.14.12.26.31 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 14 Jun 2016 12:26:31 -0700 (PDT) From: Joel Holdsworth To: qemu-devel@nongnu.org Date: Tue, 14 Jun 2016 20:26:19 +0100 Message-Id: <1465932382-28645-2-git-send-email-joel.holdsworth@vcatechnology.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1465932382-28645-1-git-send-email-joel.holdsworth@vcatechnology.com> References: <1465932382-28645-1-git-send-email-joel.holdsworth@vcatechnology.com> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 2607:f8b0:4001:c06::22c Subject: [Qemu-devel] [PATCH v2 1/4] linux-user: add option to intercept execve() syscalls X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: riku.voipio@iki.fi, Vasileios.Kalintiris@imgtec.com, Petros Angelatos Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" From: Petros Angelatos In order for one to use QEMU user mode emulation under a chroot, it is required to use binfmt_misc. This can be avoided by QEMU never doing a raw execve() to the host system. Introduce a new option, -execve, that uses the current QEMU interpreter to intercept execve(). qemu_execve() will prepend the interpreter path , similar to what binfmt_misc would do, and then pass the modified execve() to the host. It is necessary to parse hashbang scripts in that function otherwise the kernel will try to run the interpreter of a script without QEMU and get an invalid exec format error. Signed-off-by: Petros Angelatos Tested-by: Laurent Vivier Reviewed-by: Laurent Vivier --- linux-user/main.c | 37 ++++++++++++++ linux-user/qemu.h | 1 + linux-user/syscall.c | 137 ++++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 164 insertions(+), 11 deletions(-) diff --git a/linux-user/main.c b/linux-user/main.c index f8a8764..429dc06 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -18,6 +18,8 @@ */ #include "qemu/osdep.h" #include "qemu-version.h" + +#include #include #include #include @@ -79,6 +81,7 @@ static void usage(int exitcode); static const char *interp_prefix = CONFIG_QEMU_INTERP_PREFIX; const char *qemu_uname_release; +const char *qemu_execve_path; /* XXX: on x86 MAP_GROWSDOWN only works if ESP <= address + 32, so we allocate a bigger stack. Need a better solution, for example @@ -3947,6 +3950,38 @@ static void handle_arg_guest_base(const char *arg) have_guest_base = 1; } +static void handle_arg_execve(const char *arg) +{ + const char *execfn; + char buf[PATH_MAX]; + char *ret; + int len; + + /* try getauxval() */ + execfn = (const char *) getauxval(AT_EXECFN); + + if (execfn != 0) { + ret = realpath(execfn, buf); + + if (ret != NULL) { + qemu_execve_path = strdup(buf); + return; + } + } + + /* try /proc/self/exe */ + len = readlink("/proc/self/exe", buf, sizeof(buf) - 1); + + if (len != -1) { + buf[len] = '\0'; + qemu_execve_path = strdup(buf); + return; + } + + fprintf(stderr, "qemu_execve: unable to determine intepreter's path\n"); + exit(EXIT_FAILURE); +} + static void handle_arg_reserved_va(const char *arg) { char *p; @@ -4032,6 +4067,8 @@ static const struct qemu_argument arg_table[] = { "uname", "set qemu uname release string to 'uname'"}, {"B", "QEMU_GUEST_BASE", true, handle_arg_guest_base, "address", "set guest_base address to 'address'"}, + {"execve", "QEMU_EXECVE", false, handle_arg_execve, + "", "use this interpreter when a process calls execve()"}, {"R", "QEMU_RESERVED_VA", true, handle_arg_reserved_va, "size", "reserve 'size' bytes for guest virtual address space"}, {"d", "QEMU_LOG", true, handle_arg_log, diff --git a/linux-user/qemu.h b/linux-user/qemu.h index 56f29c3..567bcc1 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -149,6 +149,7 @@ void init_task_state(TaskState *ts); void task_settid(TaskState *); void stop_all_tasks(void); extern const char *qemu_uname_release; +extern const char *qemu_execve_path; extern unsigned long mmap_min_addr; /* ??? See if we can avoid exposing so much of the loader internals. */ diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 71ccbd9..a478f56 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -106,6 +106,7 @@ int __clone2(int (*fn)(void *), void *child_stack_base, #include #endif #include +#include #include "linux_loop.h" #include "uname.h" @@ -6659,6 +6660,128 @@ static target_timer_t get_timer_id(abi_long arg) return timerid; } +/* qemu_execve() Must return target values and target errnos. */ +static abi_long qemu_execve(char *filename, char *argv[], + char *envp[]) +{ + char *i_arg = NULL, *i_name = NULL; + char **new_argp; + int argc, fd, ret, i, offset = 3; + char *cp; + char buf[BINPRM_BUF_SIZE]; + + /* normal execve case */ + if (qemu_execve_path == NULL || *qemu_execve_path == 0) { + return get_errno(execve(filename, argv, envp)); + } + + for (argc = 0; argv[argc] != NULL; argc++) { + /* nothing */ ; + } + + fd = open(filename, O_RDONLY); + if (fd == -1) { + return get_errno(fd); + } + + ret = read(fd, buf, BINPRM_BUF_SIZE); + if (ret == -1) { + close(fd); + return get_errno(ret); + } + + /* if we have less than 2 bytes, we can guess it is not executable */ + if (ret < 2) { + close(fd); + return -host_to_target_errno(ENOEXEC); + } + + close(fd); + + /* adapted from the kernel + * https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/fs/binfmt_script.c + */ + if ((buf[0] == '#') && (buf[1] == '!')) { + /* + * This section does the #! interpretation. + * Sorta complicated, but hopefully it will work. -TYT + */ + + buf[BINPRM_BUF_SIZE - 1] = '\0'; + cp = strchr(buf, '\n'); + if (cp == NULL) { + cp = buf + BINPRM_BUF_SIZE - 1; + } + *cp = '\0'; + while (cp > buf) { + cp--; + if ((*cp == ' ') || (*cp == '\t')) { + *cp = '\0'; + } else { + break; + } + } + for (cp = buf + 2; (*cp == ' ') || (*cp == '\t'); cp++) { + /* nothing */ ; + } + if (*cp == '\0') { + return -ENOEXEC; /* No interpreter name found */ + } + i_name = cp; + i_arg = NULL; + for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++) { + /* nothing */ ; + } + while ((*cp == ' ') || (*cp == '\t')) { + *cp++ = '\0'; + } + if (*cp) { + i_arg = cp; + } + + if (i_arg) { + offset = 5; + } else { + offset = 4; + } + } + + new_argp = alloca((argc + offset + 1) * sizeof(void *)); + + /* Copy the original arguments with offset */ + for (i = 0; i < argc; i++) { + new_argp[i + offset] = argv[i]; + } + + new_argp[0] = strdup(qemu_execve_path); + new_argp[1] = strdup("-0"); + new_argp[offset] = filename; + new_argp[argc + offset] = NULL; + + if (i_name) { + new_argp[2] = i_name; + new_argp[3] = i_name; + + if (i_arg) { + new_argp[4] = i_arg; + } + } else { + new_argp[2] = argv[0]; + } + + /* Although execve() is not an interruptible syscall it is + * a special case where we must use the safe_syscall wrapper: + * if we allow a signal to happen before we make the host + * syscall then we will 'lose' it, because at the point of + * execve the process leaves QEMU's control. So we use the + * safe syscall wrapper to ensure that we either take the + * signal as a guest signal, or else it does not happen + * before the execve completes and makes it the other + * program's problem. + */ + return get_errno(safe_execve(qemu_execve_path, new_argp, envp)); +} + /* do_syscall() should always have a single exit point at the end so that actions, such as logging of syscall results, can be performed. All errnos that do_syscall() returns must be -TARGET_. */ @@ -6937,17 +7060,9 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1, if (!(p = lock_user_string(arg1))) goto execve_efault; - /* Although execve() is not an interruptible syscall it is - * a special case where we must use the safe_syscall wrapper: - * if we allow a signal to happen before we make the host - * syscall then we will 'lose' it, because at the point of - * execve the process leaves QEMU's control. So we use the - * safe syscall wrapper to ensure that we either take the - * signal as a guest signal, or else it does not happen - * before the execve completes and makes it the other - * program's problem. - */ - ret = get_errno(safe_execve(p, argp, envp)); + + ret = qemu_execve(p, argp, envp); + unlock_user(p, arg1, 0); goto execve_end;