From patchwork Mon Feb 15 05:51:47 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Petros Angelatos X-Patchwork-Id: 582756 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org 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 ozlabs.org (Postfix) with ESMTPS id AD62C140BEB for ; Mon, 15 Feb 2016 16:53:55 +1100 (AEDT) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=resin.io header.i=@resin.io header.b=iIwmIU4v; dkim-atps=neutral Received: from localhost ([::1]:56803 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aVC6c-0002KS-0t for incoming@patchwork.ozlabs.org; Mon, 15 Feb 2016 00:53:54 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:46860) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aVC6L-000214-DI for qemu-devel@nongnu.org; Mon, 15 Feb 2016 00:53:38 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1aVC6J-000521-U6 for qemu-devel@nongnu.org; Mon, 15 Feb 2016 00:53:37 -0500 Received: from mail-pa0-x233.google.com ([2607:f8b0:400e:c03::233]:35332) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aVC6J-00051n-IE for qemu-devel@nongnu.org; Mon, 15 Feb 2016 00:53:35 -0500 Received: by mail-pa0-x233.google.com with SMTP id ho8so81018278pac.2 for ; Sun, 14 Feb 2016 21:53:34 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=resin.io; s=googledkim; h=from:to:cc:subject:date:message-id; bh=S5FVfLKGU992N4f6jOJEFl6xszJPm3w9l14AT7k/ztg=; b=iIwmIU4v6kDoXybjugEi9dQo/XL3+6+HzADW3ljMNwy93kpsxpxbttcJGazgBLMz1A XdJOvNwET5mjGj1jRpcTOctVTOky06t/jlyKaYQ8msku0HZjCxibvlKujCz1FB5OhSKj rNx8TgB++x9fZ+uX1LlAabpchtSuEKZ8ma6aI= 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; bh=S5FVfLKGU992N4f6jOJEFl6xszJPm3w9l14AT7k/ztg=; b=mjHncqkzyNdHpm+PGmVoYGq3jTShfatKXI2Xsna1WoiNavjGPp6hszGcV55ySDOhoF WMuD7F38IDoGJUCjg5inCfk805MyNWlH4urquOmOuveCWiUPJPkmNoVbiKgLXbAgTHYz pdNf2iln4QPwsI9d+evHsh1sJsGh5pFmYK9UaL7Fb6m/mVQANdeCbWd97ugRKHvva58E CpgDCjQRhDcpb1qs7VeFKOSNbB7xS2bPOASruE0iH0Q39QjbR5yG4LUJdRDqcWaxJRkf y0FHNbeC0fsM8xWKSFhcUEgXzWXbXZ7gdASOpirO8trYyVL4UrbfUeQ2l8+v8uXUhgeU 8ZiQ== X-Gm-Message-State: AG10YOTQRIL05M8AfM2/hNc8qOsdbSgBTlPQCcCewzBbrGjeygRatAXCQZHL61uFfF4lrw== X-Received: by 10.66.249.70 with SMTP id ys6mr14716494pac.5.1455515614419; Sun, 14 Feb 2016 21:53:34 -0800 (PST) Received: from rachmaninoff.hsd1.wa.comcast.net ([2601:602:8401:a300::49c0]) by smtp.googlemail.com with ESMTPSA id b63sm35251373pfj.25.2016.02.14.21.53.32 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Sun, 14 Feb 2016 21:53:33 -0800 (PST) From: Petros Angelatos To: qemu-devel@nongnu.org Date: Sun, 14 Feb 2016 21:51:47 -0800 Message-Id: <1455515507-26877-1-git-send-email-petrosagg@resin.io> X-Mailer: git-send-email 2.7.1 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 2607:f8b0:400e:c03::233 Cc: lucas.kaldstrom@hotmail.co.uk, peter.maydell@linaro.org, riku.voipio@iki.fi, laurent@vivier.eu, Petros Angelatos Subject: [Qemu-devel] [PATCH v3] linux-user: add option to intercept execve() syscalls X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org 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 --- v3 changes: - rebase the patchset against current code 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 e719a2d..0596e6e 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 @@ -75,6 +76,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 @@ -3824,6 +3826,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; @@ -3909,6 +3943,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 bd90cc3..0d9b058 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -140,6 +140,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 54ce14a..61b7326 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -99,6 +99,7 @@ int __clone2(int (*fn)(void *), void *child_stack_base, #include #include #include +#include #include "linux_loop.h" #include "uname.h" @@ -5842,6 +5843,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_. */ @@ -6101,7 +6214,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;