Fix use-after-free in gdbserver
Commit Message
-fsanitize=address pointed out a use-after-free in gdbserver. In
particular, handle_detach could reference "process" after it was
deleted by detach_inferior. Avoiding this also necessitated changing
target_ops::join to take a pid rather than a process_info*.
Tested by the buildbot using a few of the gdbserver builders.
gdb/gdbserver/ChangeLog
2018-11-28 Tom Tromey <tom@tromey.com>
* win32-low.c (win32_join): Take pid, not process.
* target.h (struct target_ops) <join>: Change argument type.
(join_inferior): Change argument name.
* spu-low.c (spu_join): Take pid, not process.
* server.c (handle_detach): Preserve pid before destroying
process.
* lynx-low.c (lynx_join): Take pid, not process.
* linux-low.c (linux_join): Take pid, not process.
---
gdb/gdbserver/ChangeLog | 11 +++++++++++
gdb/gdbserver/linux-low.c | 4 ++--
gdb/gdbserver/lynx-low.c | 2 +-
gdb/gdbserver/server.c | 10 +++++++---
gdb/gdbserver/spu-low.c | 4 ++--
gdb/gdbserver/target.h | 8 ++++----
gdb/gdbserver/win32-low.c | 4 ++--
7 files changed, 29 insertions(+), 14 deletions(-)
Comments
On 11/28/2018 05:24 PM, Tom Tromey wrote:
> -fsanitize=address pointed out a use-after-free in gdbserver. In
> particular, handle_detach could reference "process" after it was
> deleted by detach_inferior. Avoiding this also necessitated changing
> target_ops::join to take a pid rather than a process_info*.
>
> Tested by the buildbot using a few of the gdbserver builders.
Bah, join used to be passed a pid, I just changed that recently-ish.
This is OK.
Thanks,
Pedro Alves
>
> gdb/gdbserver/ChangeLog
> 2018-11-28 Tom Tromey <tom@tromey.com>
>
> * win32-low.c (win32_join): Take pid, not process.
> * target.h (struct target_ops) <join>: Change argument type.
> (join_inferior): Change argument name.
> * spu-low.c (spu_join): Take pid, not process.
> * server.c (handle_detach): Preserve pid before destroying
> process.
> * lynx-low.c (lynx_join): Take pid, not process.
> * linux-low.c (linux_join): Take pid, not process.
> ---
> gdb/gdbserver/ChangeLog | 11 +++++++++++
> gdb/gdbserver/linux-low.c | 4 ++--
> gdb/gdbserver/lynx-low.c | 2 +-
> gdb/gdbserver/server.c | 10 +++++++---
> gdb/gdbserver/spu-low.c | 4 ++--
> gdb/gdbserver/target.h | 8 ++++----
> gdb/gdbserver/win32-low.c | 4 ++--
> 7 files changed, 29 insertions(+), 14 deletions(-)
>
> diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
> index 701f3e863c..4d849279ca 100644
> --- a/gdb/gdbserver/linux-low.c
> +++ b/gdb/gdbserver/linux-low.c
> @@ -1670,12 +1670,12 @@ linux_mourn (struct process_info *process)
> }
>
> static void
> -linux_join (process_info *proc)
> +linux_join (int pid)
> {
> int status, ret;
>
> do {
> - ret = my_waitpid (proc->pid, &status, 0);
> + ret = my_waitpid (pid, &status, 0);
> if (WIFEXITED (status) || WIFSIGNALED (status))
> break;
> } while (ret != -1 || errno != ECHILD);
> diff --git a/gdb/gdbserver/lynx-low.c b/gdb/gdbserver/lynx-low.c
> index 6c5933bc47..3bf3588a71 100644
> --- a/gdb/gdbserver/lynx-low.c
> +++ b/gdb/gdbserver/lynx-low.c
> @@ -562,7 +562,7 @@ lynx_mourn (struct process_info *proc)
> /* Implement the join target_ops method. */
>
> static void
> -lynx_join (process_info *proc)
> +lynx_join (int pid)
> {
> /* The PTRACE_DETACH is sufficient to detach from the process.
> So no need to do anything extra. */
> diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c
> index 4ec3548d64..a0be0d4f7e 100644
> --- a/gdb/gdbserver/server.c
> +++ b/gdb/gdbserver/server.c
> @@ -1255,11 +1255,15 @@ handle_detach (char *own_buf)
>
> fprintf (stderr, "Detaching from process %d\n", process->pid);
> stop_tracing ();
> +
> + /* We'll need this after PROCESS has been destroyed. */
> + int pid = process->pid;
> +
> if (detach_inferior (process) != 0)
> write_enn (own_buf);
> else
> {
> - discard_queued_stop_replies (ptid_t (process->pid));
> + discard_queued_stop_replies (ptid_t (pid));
> write_ok (own_buf);
>
> if (extended_protocol || target_running ())
> @@ -1269,7 +1273,7 @@ handle_detach (char *own_buf)
> and instead treat this like a normal program exit. */
> cs.last_status.kind = TARGET_WAITKIND_EXITED;
> cs.last_status.value.integer = 0;
> - cs.last_ptid = ptid_t (process->pid);
> + cs.last_ptid = ptid_t (pid);
>
> current_thread = NULL;
> }
> @@ -1281,7 +1285,7 @@ handle_detach (char *own_buf)
> /* If we are attached, then we can exit. Otherwise, we
> need to hang around doing nothing, until the child is
> gone. */
> - join_inferior (process);
> + join_inferior (pid);
> exit (0);
> }
> }
> diff --git a/gdb/gdbserver/spu-low.c b/gdb/gdbserver/spu-low.c
> index 83a31a203d..239c212639 100644
> --- a/gdb/gdbserver/spu-low.c
> +++ b/gdb/gdbserver/spu-low.c
> @@ -362,12 +362,12 @@ spu_mourn (struct process_info *process)
> }
>
> static void
> -spu_join (process_info *proc)
> +spu_join (int pid)
> {
> int status, ret;
>
> do {
> - ret = waitpid (proc->pid, &status, 0);
> + ret = waitpid (pid, &status, 0);
> if (WIFEXITED (status) || WIFSIGNALED (status))
> break;
> } while (ret != -1 || errno != ECHILD);
> diff --git a/gdb/gdbserver/target.h b/gdb/gdbserver/target.h
> index fce54e05ad..6f810b6db9 100644
> --- a/gdb/gdbserver/target.h
> +++ b/gdb/gdbserver/target.h
> @@ -103,9 +103,9 @@ struct target_ops
>
> void (*mourn) (struct process_info *proc);
>
> - /* Wait for process PROC to exit. */
> + /* Wait for process PID to exit. */
>
> - void (*join) (process_info *proc);
> + void (*join) (int pid);
>
> /* Return 1 iff the thread with process ID PID is alive. */
>
> @@ -530,8 +530,8 @@ int kill_inferior (process_info *proc);
> #define store_inferior_registers(regcache, regno) \
> (*the_target->store_registers) (regcache, regno)
>
> -#define join_inferior(proc) \
> - (*the_target->join) (proc)
> +#define join_inferior(pid) \
> + (*the_target->join) (pid)
>
> #define target_supports_non_stop() \
> (the_target->supports_non_stop ? (*the_target->supports_non_stop ) () : 0)
> diff --git a/gdb/gdbserver/win32-low.c b/gdb/gdbserver/win32-low.c
> index 4aed58d3b8..1ad71c7be9 100644
> --- a/gdb/gdbserver/win32-low.c
> +++ b/gdb/gdbserver/win32-low.c
> @@ -873,9 +873,9 @@ win32_mourn (struct process_info *process)
> /* Implementation of target_ops::join. */
>
> static void
> -win32_join (process_info *proc)
> +win32_join (int pid)
> {
> - HANDLE h = OpenProcess (PROCESS_ALL_ACCESS, FALSE, proc->pid);
> + HANDLE h = OpenProcess (PROCESS_ALL_ACCESS, FALSE, pid);
> if (h != NULL)
> {
> WaitForSingleObject (h, INFINITE);
>
@@ -1670,12 +1670,12 @@ linux_mourn (struct process_info *process)
}
static void
-linux_join (process_info *proc)
+linux_join (int pid)
{
int status, ret;
do {
- ret = my_waitpid (proc->pid, &status, 0);
+ ret = my_waitpid (pid, &status, 0);
if (WIFEXITED (status) || WIFSIGNALED (status))
break;
} while (ret != -1 || errno != ECHILD);
@@ -562,7 +562,7 @@ lynx_mourn (struct process_info *proc)
/* Implement the join target_ops method. */
static void
-lynx_join (process_info *proc)
+lynx_join (int pid)
{
/* The PTRACE_DETACH is sufficient to detach from the process.
So no need to do anything extra. */
@@ -1255,11 +1255,15 @@ handle_detach (char *own_buf)
fprintf (stderr, "Detaching from process %d\n", process->pid);
stop_tracing ();
+
+ /* We'll need this after PROCESS has been destroyed. */
+ int pid = process->pid;
+
if (detach_inferior (process) != 0)
write_enn (own_buf);
else
{
- discard_queued_stop_replies (ptid_t (process->pid));
+ discard_queued_stop_replies (ptid_t (pid));
write_ok (own_buf);
if (extended_protocol || target_running ())
@@ -1269,7 +1273,7 @@ handle_detach (char *own_buf)
and instead treat this like a normal program exit. */
cs.last_status.kind = TARGET_WAITKIND_EXITED;
cs.last_status.value.integer = 0;
- cs.last_ptid = ptid_t (process->pid);
+ cs.last_ptid = ptid_t (pid);
current_thread = NULL;
}
@@ -1281,7 +1285,7 @@ handle_detach (char *own_buf)
/* If we are attached, then we can exit. Otherwise, we
need to hang around doing nothing, until the child is
gone. */
- join_inferior (process);
+ join_inferior (pid);
exit (0);
}
}
@@ -362,12 +362,12 @@ spu_mourn (struct process_info *process)
}
static void
-spu_join (process_info *proc)
+spu_join (int pid)
{
int status, ret;
do {
- ret = waitpid (proc->pid, &status, 0);
+ ret = waitpid (pid, &status, 0);
if (WIFEXITED (status) || WIFSIGNALED (status))
break;
} while (ret != -1 || errno != ECHILD);
@@ -103,9 +103,9 @@ struct target_ops
void (*mourn) (struct process_info *proc);
- /* Wait for process PROC to exit. */
+ /* Wait for process PID to exit. */
- void (*join) (process_info *proc);
+ void (*join) (int pid);
/* Return 1 iff the thread with process ID PID is alive. */
@@ -530,8 +530,8 @@ int kill_inferior (process_info *proc);
#define store_inferior_registers(regcache, regno) \
(*the_target->store_registers) (regcache, regno)
-#define join_inferior(proc) \
- (*the_target->join) (proc)
+#define join_inferior(pid) \
+ (*the_target->join) (pid)
#define target_supports_non_stop() \
(the_target->supports_non_stop ? (*the_target->supports_non_stop ) () : 0)
@@ -873,9 +873,9 @@ win32_mourn (struct process_info *process)
/* Implementation of target_ops::join. */
static void
-win32_join (process_info *proc)
+win32_join (int pid)
{
- HANDLE h = OpenProcess (PROCESS_ALL_ACCESS, FALSE, proc->pid);
+ HANDLE h = OpenProcess (PROCESS_ALL_ACCESS, FALSE, pid);
if (h != NULL)
{
WaitForSingleObject (h, INFINITE);