Fix use-after-free in gdbserver

Message ID 20181128172412.9353-1-tom@tromey.com
State New, archived
Headers

Commit Message

Tom Tromey Nov. 28, 2018, 5:24 p.m. UTC
  -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

Pedro Alves Nov. 29, 2018, 2 p.m. UTC | #1
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);
>
  

Patch

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);