Skip to content

Commit

Permalink
linux-user: add option to intercept execve() syscalls
Browse files Browse the repository at this point in the history
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 <petrosagg@resin.io>
  • Loading branch information
Petros Angelatos authored and vivier committed May 11, 2016
1 parent 69841b4 commit 8952fce
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 1 deletion.
36 changes: 36 additions & 0 deletions linux-user/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/resource.h>
#include <sys/auxv.h>
#include <glib.h>

#include "qemu.h"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -3879,6 +3881,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;
Expand Down Expand Up @@ -3976,6 +4010,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,
Expand Down
1 change: 1 addition & 0 deletions linux-user/qemu.h
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
117 changes: 116 additions & 1 deletion linux-user/syscall.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ int __clone2(int (*fn)(void *), void *child_stack_base,
#include <linux/route.h>
#include <linux/filter.h>
#include <linux/blkpg.h>
#include <linux/binfmts.h>
#include "linux_loop.h"
#include "uname.h"

Expand Down Expand Up @@ -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_<errcode>. */
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 8952fce

Please sign in to comment.