From patchwork Mon Jan 18 04:33:22 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Petros Angelatos X-Patchwork-Id: 569452 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 F0DDD140B04 for ; Mon, 18 Jan 2016 17:37:30 +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=gjS7ExsL; dkim-atps=neutral Received: from localhost ([::1]:57897 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aL3RQ-0002IW-IN for incoming@patchwork.ozlabs.org; Mon, 18 Jan 2016 01:37:28 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:34508) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aL1Vo-0002us-U4 for qemu-devel@nongnu.org; Sun, 17 Jan 2016 23:33:54 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1aL1Vl-0003U3-9i for qemu-devel@nongnu.org; Sun, 17 Jan 2016 23:33:50 -0500 Received: from mail-pa0-x230.google.com ([2607:f8b0:400e:c03::230]:33201) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aL1Vk-0003Tn-NJ for qemu-devel@nongnu.org; Sun, 17 Jan 2016 23:33:49 -0500 Received: by mail-pa0-x230.google.com with SMTP id cy9so427314393pac.0 for ; Sun, 17 Jan 2016 20:33:45 -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=7thNYWoE1N5sAMP26G6JgQHNYOAzBkP40MemBBatiZQ=; b=gjS7ExsLV1+ErVODXfX2fnIKYEm4KLSWAyHbebL+9nXkK4GLdCgmjBS3edZuW13fai ELmIu4htD7XjhGA7QnG1Rs2vYrK/DHfpzrlNREmzJBXsBLrM+bS1fc8hZD6K0/Ra2lPf 2XvQLeB8FEP1MMtOJ6jl2ADq0/pJEJgHDhHck= 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=7thNYWoE1N5sAMP26G6JgQHNYOAzBkP40MemBBatiZQ=; b=B/cQOMEEjmtfLvIPgsSh2DYKpeDspfHxgPDmMy/A99cRWe/vqs3zrYMMx2a+l5Pytq kvFkeTCp2pvzDidc560+H0B1QCxx7fGnmGWhEKB7zyA7QV1IA5/r7hOA3S3utpzCwskt JjwSVRlfTquQGknwN6ifzs9jndx95d5Hg9kO6UqLgg+nz6VVB1prXMvJuGzx22M36aR0 TLm7kX+MAwwD2sxFuRsYdvEqs0ZDbgDKlASa+wvxiPgIQhRUq59Giqmvk0oWyd1djrXV psqPAD5zIwv4v71zPeFMjtYUDazHeuiPHDGw1V638Rpy2ErrHwv9kc3Ra7lyyvXdfK6l Owiw== X-Gm-Message-State: ALoCoQnLHDbCMpdalRIFq3pPlrGDt8IfCEL7MxUf0EXimGGhdvaidMeZ3lnjHV9nSjEUWfNzH0z5ABZLlRH8K+XNuJkCKSu9CA== X-Received: by 10.66.180.48 with SMTP id dl16mr33393848pac.39.1453091625126; Sun, 17 Jan 2016 20:33:45 -0800 (PST) Received: from rachmaninoff.hsd1.wa.comcast.net ([2601:602:8401:a300:e84:dcff:fe44:7081]) by smtp.googlemail.com with ESMTPSA id ya4sm30224124pab.22.2016.01.17.20.33.44 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Sun, 17 Jan 2016 20:33:44 -0800 (PST) From: Petros Angelatos X-Google-Original-From: Petros Angelatos To: qemu-devel@nongnu.org Date: Sun, 17 Jan 2016 20:33:22 -0800 Message-Id: <1453091602-21843-1-git-send-email-petrosagg@gmail.com> X-Mailer: git-send-email 2.7.0 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 2607:f8b0:400e:c03::230 X-Mailman-Approved-At: Mon, 18 Jan 2016 01:37:09 -0500 Cc: lucas.kaldstrom@hotmail.co.uk, riku.voipio@iki.fi, Petros Angelatos Subject: [Qemu-devel] [PATCH] 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 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=path, that sets the absolute path to the QEMU interpreter and enables execve() interception. When a guest process tries to call execve(), qemu_execve() is called instead. qemu_execve() will prepend the interpreter set with -execve, 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 --- linux-user/main.c | 8 ++++ linux-user/qemu.h | 1 + linux-user/syscall.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 119 insertions(+), 1 deletion(-) diff --git a/linux-user/main.c b/linux-user/main.c index ee12035..5951279 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -79,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 @@ -3828,6 +3829,11 @@ static void handle_arg_guest_base(const char *arg) have_guest_base = 1; } +static void handle_arg_execve(const char *arg) +{ + qemu_execve_path = strdup(arg); +} + static void handle_arg_reserved_va(const char *arg) { char *p; @@ -3913,6 +3919,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", true, handle_arg_execve, + "path", "use interpreter at 'path' 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 0cbace4..d0b5442 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -5854,6 +5854,109 @@ static target_timer_t get_timer_id(abi_long arg) return timerid; } +#define BINPRM_BUF_SIZE 128 + +/* 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]; + + for (argc = 0; argv[argc] != NULL; argc++) { + /* nothing */ ; + } + + fd = open(filename, O_RDONLY); + if (fd == -1) { + return -ENOENT; + } + + ret = read(fd, buf, BINPRM_BUF_SIZE); + if (ret == -1) { + close(fd); + return -ENOENT; + } + + 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_. */ @@ -6113,7 +6216,13 @@ 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)); + + if (qemu_execve_path && *qemu_execve_path) { + ret = get_errno(qemu_execve(p, argp, envp)); + } else { + ret = get_errno(execve(p, argp, envp)); + } + unlock_user(p, arg1, 0); goto execve_end;