Fix execution of /proc/self/exe within child processes
* exec/exec.h (struct exec_tracee): New field `new_child'. Also, make `waiting_for_syscall' a bitfield. * exec/trace.c (PTRACE_GETEVENTMSG): New declaration. (MAX_TRACEES): Bump to 4096. (handle_clone_prepare): New function. (handle_clone): If required, set `new_child' and wait for a ptrace event describing the parent to arrive. (after_fork): Clear new field. (exec_waitpid): Upon a ptrace event describing a clone, create the child's tracee if it doesn't already exist. Otherwise, copy over the parent's cmdline and start running it.
This commit is contained in:
parent
2ba6c5035c
commit
0fbe79727b
2 changed files with 155 additions and 40 deletions
|
@ -153,7 +153,11 @@ struct exec_tracee
|
|||
|
||||
/* Whether or not the tracee is currently waiting for a system call
|
||||
to complete. */
|
||||
bool waiting_for_syscall;
|
||||
bool waiting_for_syscall : 1;
|
||||
|
||||
/* Whether or not the tracee has been created but is not yet
|
||||
processed by `handle_clone'. */
|
||||
bool new_child : 1;
|
||||
|
||||
#ifndef REENTRANT
|
||||
/* Name of the executable being run. */
|
||||
|
|
189
exec/trace.c
189
exec/trace.c
|
@ -50,6 +50,10 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
|
|||
#define SYS_SECCOMP 1
|
||||
#endif /* SYS_SECCOMP */
|
||||
|
||||
#ifndef PTRACE_GETEVENTMSG
|
||||
#define PTRACE_GETEVENTMSG 0x4201
|
||||
#endif /* PTRACE_GETEVENTMSG */
|
||||
|
||||
|
||||
|
||||
/* Program tracing functions.
|
||||
|
@ -63,7 +67,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
|
|||
|
||||
|
||||
/* Number of tracees children are allowed to create. */
|
||||
#define MAX_TRACEES 1024
|
||||
#define MAX_TRACEES 4096
|
||||
|
||||
#ifdef __aarch64__
|
||||
|
||||
|
@ -381,23 +385,66 @@ remove_tracee (struct exec_tracee *tracee)
|
|||
|
||||
/* Child process tracing. */
|
||||
|
||||
/* Handle the completion of a `clone' or `clone3' system call,
|
||||
resulting in the creation of the process PID. Allocate a new
|
||||
tracee structure from a static area for the processes's pid.
|
||||
/* Array of `struct exec_tracees' that they are allocated from. */
|
||||
static struct exec_tracee static_tracees[MAX_TRACEES];
|
||||
|
||||
Value is 0 upon success, 1 otherwise. */
|
||||
/* Number of tracees currently allocated. */
|
||||
static int tracees;
|
||||
|
||||
static int
|
||||
handle_clone (pid_t pid)
|
||||
/* Return the `struct exec_tracee' corresponding to the specified
|
||||
PROCESS. */
|
||||
|
||||
static struct exec_tracee *
|
||||
find_tracee (pid_t process)
|
||||
{
|
||||
static struct exec_tracee static_tracees[MAX_TRACEES];
|
||||
static int tracees;
|
||||
struct exec_tracee *tracee;
|
||||
long rc;
|
||||
int flags;
|
||||
|
||||
/* Now allocate a new tracee, either from static_tracees or the free
|
||||
list. */
|
||||
for (tracee = tracing_processes; tracee; tracee = tracee->next)
|
||||
{
|
||||
if (tracee->pid == process)
|
||||
return tracee;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Prepare to handle the completion of a `clone' system call.
|
||||
|
||||
If the new clone is not yet being traced, create a new tracee for
|
||||
PARENT's child, copying over its current command line. Then, set
|
||||
`new_child' in the new tracee. Otherwise, continue it until the
|
||||
next syscall. */
|
||||
|
||||
static void
|
||||
handle_clone_prepare (struct exec_tracee *parent)
|
||||
{
|
||||
#ifndef REENTRANT
|
||||
long rc;
|
||||
unsigned long pid;
|
||||
struct exec_tracee *tracee;
|
||||
|
||||
rc = ptrace (PTRACE_GETEVENTMSG, parent->pid, NULL,
|
||||
&pid);
|
||||
if (rc)
|
||||
return;
|
||||
|
||||
/* See if the tracee already exists. */
|
||||
tracee = find_tracee (pid);
|
||||
|
||||
if (tracee)
|
||||
{
|
||||
/* Continue the tracee. Record its command line, as that has
|
||||
not yet been done. */
|
||||
|
||||
assert (tracee->new_child);
|
||||
tracee->new_child = false;
|
||||
tracee->exec_file = NULL;
|
||||
ptrace (PTRACE_SYSCALL, tracee->pid, 0, 0);
|
||||
|
||||
if (parent->exec_file)
|
||||
tracee->exec_file = strdup (parent->exec_file);
|
||||
return;
|
||||
}
|
||||
|
||||
if (free_tracees)
|
||||
{
|
||||
|
@ -410,13 +457,75 @@ handle_clone (pid_t pid)
|
|||
tracees++;
|
||||
}
|
||||
else
|
||||
return 1;
|
||||
return;
|
||||
|
||||
tracee->pid = pid;
|
||||
tracee->next = tracing_processes;
|
||||
tracee->waiting_for_syscall = false;
|
||||
tracee->new_child = true;
|
||||
tracee->exec_file = NULL;
|
||||
tracing_processes = tracee;
|
||||
|
||||
/* Copy over the command line. */
|
||||
|
||||
if (parent->exec_file)
|
||||
tracee->exec_file = strdup (parent->exec_file);
|
||||
#endif /* REENTRANT */
|
||||
}
|
||||
|
||||
/* Handle the completion of a `clone' or `clone3' system call,
|
||||
resulting in the creation of the process PID. If TRACEE is NULL,
|
||||
allocate a new tracee structure from a static area for the
|
||||
processes's pid, then set TRACEE->new_child to true and await the
|
||||
parent's corresponding ptrace event to arrive; otherwise, just
|
||||
clear TRACEE->new_child.
|
||||
|
||||
Value is 0 upon success, 2 if TRACEE should remain suspended until
|
||||
the parent's ptrace-stop, and 1 otherwise. */
|
||||
|
||||
static int
|
||||
handle_clone (struct exec_tracee *tracee, pid_t pid)
|
||||
{
|
||||
long rc;
|
||||
int flags, value;
|
||||
|
||||
/* Now allocate a new tracee, either from static_tracees or the free
|
||||
list, if no tracee was supplied. */
|
||||
|
||||
value = 0;
|
||||
|
||||
if (!tracee)
|
||||
{
|
||||
if (free_tracees)
|
||||
{
|
||||
tracee = free_tracees;
|
||||
free_tracees = free_tracees->next;
|
||||
}
|
||||
else if (tracees < MAX_TRACEES)
|
||||
{
|
||||
tracee = &static_tracees[tracees];
|
||||
tracees++;
|
||||
}
|
||||
else
|
||||
return 1;
|
||||
|
||||
tracee->pid = pid;
|
||||
tracee->next = tracing_processes;
|
||||
tracee->waiting_for_syscall = false;
|
||||
#ifndef REENTRANT
|
||||
tracee->exec_file = NULL;
|
||||
#endif /* REENTRANT */
|
||||
tracing_processes = tracee;
|
||||
tracee->new_child = true;
|
||||
|
||||
/* Wait for the ptrace-stop to happen in the parent. */
|
||||
value = 2;
|
||||
}
|
||||
else
|
||||
/* Clear the flag saying that this is a newly created child
|
||||
process. */
|
||||
tracee->new_child = false;
|
||||
|
||||
/* Apply required options to the child, so that the kernel
|
||||
automatically traces children and makes it easy to differentiate
|
||||
between system call traps and other kinds of traps. */
|
||||
|
@ -432,15 +541,18 @@ handle_clone (pid_t pid)
|
|||
if (rc)
|
||||
goto bail;
|
||||
|
||||
/* The new tracee is currently stopped. Continue it until the next
|
||||
system call. */
|
||||
if (value != 2)
|
||||
{
|
||||
/* The new tracee is currently stopped. Continue it until the next
|
||||
system call. */
|
||||
|
||||
rc = ptrace (PTRACE_SYSCALL, pid, 0, 0);
|
||||
rc = ptrace (PTRACE_SYSCALL, pid, 0, 0);
|
||||
|
||||
if (rc)
|
||||
goto bail;
|
||||
if (rc)
|
||||
goto bail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return value;
|
||||
|
||||
bail:
|
||||
remove_tracee (tracee);
|
||||
|
@ -1148,6 +1260,7 @@ after_fork (pid_t pid)
|
|||
tracee->pid = pid;
|
||||
tracee->next = tracing_processes;
|
||||
tracee->waiting_for_syscall = false;
|
||||
tracee->new_child = false;
|
||||
#ifndef REENTRANT
|
||||
tracee->exec_file = NULL;
|
||||
#endif /* REENTRANT */
|
||||
|
@ -1155,23 +1268,6 @@ after_fork (pid_t pid)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Return the `struct exec_tracee' corresponding to the specified
|
||||
PROCESS. */
|
||||
|
||||
static struct exec_tracee *
|
||||
find_tracee (pid_t process)
|
||||
{
|
||||
struct exec_tracee *tracee;
|
||||
|
||||
for (tracee = tracing_processes; tracee; tracee = tracee->next)
|
||||
{
|
||||
if (tracee->pid == process)
|
||||
return tracee;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Wait for a child process to exit, like `waitpid'. However, if a
|
||||
child stops to perform a system call, send it on its way and return
|
||||
-1. OPTIONS must not contain WUNTRACED. */
|
||||
|
@ -1199,12 +1295,12 @@ exec_waitpid (pid_t pid, int *wstatus, int options)
|
|||
{
|
||||
tracee = find_tracee (pid);
|
||||
|
||||
if (!tracee)
|
||||
if (!tracee || tracee->new_child)
|
||||
{
|
||||
if (WSTOPSIG (status) == SIGSTOP)
|
||||
/* A new process has been created and stopped. Record
|
||||
it now. */
|
||||
handle_clone (pid);
|
||||
handle_clone (tracee, pid);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
@ -1248,6 +1344,21 @@ exec_waitpid (pid_t pid, int *wstatus, int options)
|
|||
case SIGTRAP | (PTRACE_EVENT_FORK << 8):
|
||||
case SIGTRAP | (PTRACE_EVENT_VFORK << 8):
|
||||
case SIGTRAP | (PTRACE_EVENT_CLONE << 8):
|
||||
|
||||
/* Both PTRACE_EVENT_CLONE and SIGSTOP must arrive before a
|
||||
process is continued. Otherwise, its parent's cmdline
|
||||
cannot be obtained and propagated.
|
||||
|
||||
If the PID of the new process is currently not being
|
||||
traced, create a new tracee. Set `new_child' to true,
|
||||
and copy over the old command line in preparation for a
|
||||
SIGSTOP signal being delivered to it.
|
||||
|
||||
Otherwise, start the tracee running until the next
|
||||
syscall. */
|
||||
|
||||
handle_clone_prepare (tracee);
|
||||
|
||||
/* These events are handled by tracing SIGSTOP signals sent
|
||||
to unknown tracees. Make sure not to pass through
|
||||
status, as there's no signal really being delivered. */
|
||||
|
|
Loading…
Add table
Reference in a new issue