Fix use-after-free in gdbserver

Message ID 20181128172412.9353-1-tom@tromey.com
State New
Headers show
Series
  • Fix use-after-free in gdbserver
Related show

Commit Message

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

-- 
2.17.2

Comments

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