Fix NOMMU + daemonized tracer SEGV

pathname[] was getting destroyed, execve of garbage pathname
failing, and to top it off, the tracer's stack was also
smashed and trecer segfaulted.

* strace.c (exec_or_die): New function.
(startup_child): Don't use pathname[] contents after vfork,
make a malloced copy instead. Explain "NOMMU + -D bug"
and how we work around it.

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2013-02-19 16:30:31 +01:00
parent 5c9d8f4fc5
commit f909c8d0d7

172
strace.c
View File

@ -981,13 +981,79 @@ startup_attach(void)
sigprocmask(SIG_SETMASK, &empty_set, NULL);
}
/* Stack-o-phobic exec helper, in the hope to work around
* NOMMU + "daemonized tracer" difficulty.
*/
struct exec_params {
int fd_to_close;
uid_t run_euid;
gid_t run_egid;
char **argv;
char *pathname;
};
static struct exec_params params_for_tracee;
static void __attribute__ ((noinline, noreturn))
exec_or_die(void)
{
struct exec_params *params = &params_for_tracee;
if (params->fd_to_close >= 0)
close(params->fd_to_close);
if (!daemonized_tracer && !use_seize) {
if (ptrace(PTRACE_TRACEME, 0L, 0L, 0L) < 0) {
perror_msg_and_die("ptrace(PTRACE_TRACEME, ...)");
}
}
if (username != NULL) {
/*
* It is important to set groups before we
* lose privileges on setuid.
*/
if (initgroups(username, run_gid) < 0) {
perror_msg_and_die("initgroups");
}
if (setregid(run_gid, params->run_egid) < 0) {
perror_msg_and_die("setregid");
}
if (setreuid(run_uid, params->run_euid) < 0) {
perror_msg_and_die("setreuid");
}
}
else if (geteuid() != 0)
if (setreuid(run_uid, run_uid) < 0) {
perror_msg_and_die("setreuid");
}
if (!daemonized_tracer) {
/*
* Induce a ptrace stop. Tracer (our parent)
* will resume us with PTRACE_SYSCALL and display
* the immediately following execve syscall.
* Can't do this on NOMMU systems, we are after
* vfork: parent is blocked, stopping would deadlock.
*/
if (!NOMMU_SYSTEM)
kill(getpid(), SIGSTOP);
} else {
alarm(3);
/* we depend on SIGCHLD set to SIG_DFL by init code */
/* if it happens to be SIG_IGN'ed, wait won't block */
wait(NULL);
alarm(0);
}
execv(params->pathname, params->argv);
perror_msg_and_die("exec");
}
static void
startup_child(char **argv)
{
struct stat statbuf;
const char *filename;
char pathname[MAXPATHLEN];
int pid = 0;
int pid;
struct tcb *tcp;
filename = argv[0];
@ -1045,69 +1111,29 @@ startup_child(char **argv)
if (stat(pathname, &statbuf) < 0) {
perror_msg_and_die("Can't stat '%s'", filename);
}
params_for_tracee.fd_to_close = (shared_log != stderr) ? fileno(shared_log) : -1;
params_for_tracee.run_euid = (statbuf.st_mode & S_ISUID) ? statbuf.st_uid : run_uid;
params_for_tracee.run_egid = (statbuf.st_mode & S_ISGID) ? statbuf.st_gid : run_gid;
params_for_tracee.argv = argv;
/*
* On NOMMU, can be safely freed only after execve in tracee.
* It's hard to know when that happens, so we just leak it.
*/
params_for_tracee.pathname = strdup(pathname);
strace_child = pid = fork();
if (pid < 0) {
perror_msg_and_die("fork");
}
if ((pid != 0 && daemonized_tracer) /* -D: parent to become a traced process */
|| (pid == 0 && !daemonized_tracer) /* not -D: child to become a traced process */
if ((pid != 0 && daemonized_tracer)
|| (pid == 0 && !daemonized_tracer)
) {
pid = getpid();
if (shared_log != stderr)
close(fileno(shared_log));
if (!daemonized_tracer && !use_seize) {
if (ptrace(PTRACE_TRACEME, 0L, 0L, 0L) < 0) {
perror_msg_and_die("ptrace(PTRACE_TRACEME, ...)");
}
}
if (username != NULL) {
uid_t run_euid = run_uid;
gid_t run_egid = run_gid;
if (statbuf.st_mode & S_ISUID)
run_euid = statbuf.st_uid;
if (statbuf.st_mode & S_ISGID)
run_egid = statbuf.st_gid;
/*
* It is important to set groups before we
* lose privileges on setuid.
*/
if (initgroups(username, run_gid) < 0) {
perror_msg_and_die("initgroups");
}
if (setregid(run_gid, run_egid) < 0) {
perror_msg_and_die("setregid");
}
if (setreuid(run_uid, run_euid) < 0) {
perror_msg_and_die("setreuid");
}
}
else if (geteuid() != 0)
if (setreuid(run_uid, run_uid) < 0) {
perror_msg_and_die("setreuid");
}
if (!daemonized_tracer) {
/*
* Induce a ptrace stop. Tracer (our parent)
* will resume us with PTRACE_SYSCALL and display
* the immediately following execve syscall.
* Can't do this on NOMMU systems, we are after
* vfork: parent is blocked, stopping would deadlock.
*/
if (!NOMMU_SYSTEM)
kill(pid, SIGSTOP);
} else {
alarm(3);
/* we depend on SIGCHLD set to SIG_DFL by init code */
/* if it happens to be SIG_IGN'ed, wait won't block */
wait(NULL);
alarm(0);
}
execv(pathname, argv);
perror_msg_and_die("exec");
/* We are to become the tracee. Two cases:
* -D: we are parent
* not -D: we are child
*/
exec_or_die();
}
/* We are the tracer */
@ -1157,8 +1183,23 @@ startup_child(char **argv)
/* attaching will be done later, by startup_attach */
/* note: we don't do newoutf(tcp) here either! */
//NOMMU BUG! -D is active, we (child) return, and this smashes the stack!
//When parent will be unpaused, it segfaults.
/* NOMMU BUG! -D mode is active, we (child) return,
* and we will scribble over parent's stack!
* When parent later unpauses, it segfaults.
*
* We work around it
* (1) by declaring exec_or_die() NORETURN,
* hopefully compiler will just jump to it
* instead of call (won't push anything to stack),
* (2) by trying very hard in exec_or_die()
* to not use any stack,
* (3) having a really big (MAXPATHLEN) stack object
* in this function, which creates a "buffer" between
* child's and parent's stack pointers.
* This may save us if (1) and (2) failed
* and compiler decided to use stack in exec_or_die() anyway
* (happens on i386 because of stack parameter passing).
*/
}
}
@ -1718,10 +1759,11 @@ init(int argc, char *argv[])
sigemptyset(&empty_set);
sigemptyset(&blocked_set);
/* STARTUP_CHILD must be called before the signal handlers get
installed below as they are inherited into the spawned process.
Also we do not need to be protected by them as during interruption
in the STARTUP_CHILD mode we kill the spawned process anyway. */
/* startup_child() must be called before the signal handlers get
* installed below as they are inherited into the spawned process.
* Also we do not need to be protected by them as during interruption
* in the startup_child() mode we kill the spawned process anyway.
*/
if (argv[0]) {
skip_startup_execve = 1;
startup_child(argv);