From patchwork Wed May 25 16:07:49 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joel Holdsworth X-Patchwork-Id: 9135725 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 5E2906075C for ; Wed, 25 May 2016 16:08:37 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 4F5E2282B3 for ; Wed, 25 May 2016 16:08:37 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 441C0282C0; Wed, 25 May 2016 16:08:37 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 76427282B3 for ; Wed, 25 May 2016 16:08:36 +0000 (UTC) Received: from localhost ([::1]:33868 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1b5bMJ-000657-J4 for patchwork-qemu-devel@patchwork.kernel.org; Wed, 25 May 2016 12:08:35 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:50403) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1b5bKY-0004vO-WA for qemu-devel@nongnu.org; Wed, 25 May 2016 12:06:50 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1b5bKU-0002o7-P6 for qemu-devel@nongnu.org; Wed, 25 May 2016 12:06:46 -0400 Received: from mail-it0-x22f.google.com ([2607:f8b0:4001:c0b::22f]:37382) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1b5bKU-0002nz-Ic for qemu-devel@nongnu.org; Wed, 25 May 2016 12:06:42 -0400 Received: by mail-it0-x22f.google.com with SMTP id z123so31140184itg.0 for ; Wed, 25 May 2016 09:06:42 -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=Ay3664MvAocMFov+spN1MvvqzqVtU41W+d1g9oCwSK0=; b=ewgcB9kJEMHPsTyGHuW9eQPam8chOhqO7ffJGORyMdvUd247ZFNkacjhsKfei1fngr 5qGj5pBbHSgbWzthYWOUboRTMACDYX7Q1IUcBEgord6T8aazebdMwtU4BDIXy8tql88/ 8VlOkJPfKNty+/7fQWfalIw1kBEBcyqmEti3jEjpqAN1gv6pHmUxpuz8nL+dwZDfLfu3 8P3SKYVzj/0AHplktFRDGniyB/qI8iUf2VvQsDleUY+wC3/dQqfJcvBWKVzniblZv0qh pxm9us3QkhafpMMR7JeRhzW5r+6SjbDYPc25gOnexumzZZVd6nMC0z0s093WFwv32lcc bvJA== 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=Ay3664MvAocMFov+spN1MvvqzqVtU41W+d1g9oCwSK0=; b=GhnINHxTTlf1PPZQydD1gA6J0aB4SN4wX9Z6ISWtiAofgmXmzwsvPIL078JUZrtGzW 1P4dogqh3hlj2U42rnC+KbRDEXwNyRPbpUxUNMcwv1xqvuOtsimzYV+pfw9Ssw2nIKlI MhAoZ1/wytRXnl9yG9v8XblOrnhHwKr+jh8M3DCCW8z8+2pewr2Hpv/7C0eweGol2CLE FVI+Ihipsxt0oVXcc8+RN57+JeRVILlp8lhMszLLM18wjXNrSVQaKQoWg6NGjGMol2E1 KJZ1iPHaWstE/yRN5p5qhoEE87Dws6KnsNdsbPYKw17ng7MNTvFw4E5K0DFaKzH63rEc OPXw== X-Gm-Message-State: ALyK8tKEWlMuaCPMrfdS+XV8LzNi5jCvtB79X2FNSmM3Em3YnNGk9VXfs2NtIsdV8TpCLqrg X-Received: by 10.36.122.206 with SMTP id a197mr4604590itc.13.1464192401834; Wed, 25 May 2016 09:06:41 -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 d196sm2975958ioe.41.2016.05.25.09.06.40 (version=TLSv1/SSLv3 cipher=OTHER); Wed, 25 May 2016 09:06:41 -0700 (PDT) From: Joel Holdsworth To: qemu-devel@nongnu.org Date: Wed, 25 May 2016 17:07:49 +0100 Message-Id: <1464192472-7885-2-git-send-email-joel.holdsworth@vcatechnology.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1464192472-7885-1-git-send-email-joel.holdsworth@vcatechnology.com> References: <1464192472-7885-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:c0b::22f Subject: [Qemu-devel] [PATCH 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+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP 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 | 36 ++++++++++++++++ linux-user/qemu.h | 1 + linux-user/syscall.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 153 insertions(+), 1 deletion(-) diff --git a/linux-user/main.c b/linux-user/main.c index 95ed11d..093f858 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -17,6 +17,7 @@ * along with this program; if not, see . */ #include "qemu/osdep.h" +#include #include #include #include @@ -78,6 +79,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 @@ -3868,6 +3870,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; @@ -3953,6 +3987,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 208c63e..50fe716 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -138,6 +138,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 032d338..750e381 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -101,6 +101,7 @@ int __clone2(int (*fn)(void *), void *child_stack_base, #include #include #include +#include #include "linux_loop.h" #include "uname.h" @@ -5847,6 +5848,118 @@ 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]; + } + + return get_errno(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_. */ @@ -6106,7 +6219,9 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1, if (!(p = lock_user_string(arg1))) goto execve_efault; - ret = get_errno(execve(p, argp, envp)); + + ret = qemu_execve(p, argp, envp); + unlock_user(p, arg1, 0); goto execve_end;