PR gdb/16188: Verify PTRACE_TRACEME succeeded

Message ID 20170218050900.31399-1-sergiodj@redhat.com
State New, archived
Headers

Commit Message

Sergio Durigan Junior Feb. 18, 2017, 5:09 a.m. UTC
  This patch fixes PR gdb/16188, which is about the fact that
fork_inferior doesn't verify the return value of the "traceme_fun"
callback.  On most targets, this callback is actually a wrapper to a
ptrace call that does a PTRACE_TRACEME on the forked GDB process that
will eventually become the inferior.

Thanks to Pedro, this second version of the patch is simpler and more
more logical.  Basically, two helper functions are added:
trace_start_error and trace_start_error_with_name.  The former can be
used when there is a customized error message to be printed to the
user.  The latter works like perror_with_name, so you just need to
pass the function that error'd.

Both helper functions mentioned above do basically the same thing:
print the error message to stderr and call _exit, properly terminating
the forked inferior.

Most of the patch takes care of guarding the necessary system calls
against errors on the "traceme_fun" callbacks.  It is not right to
call error on these situations, so I've replaced these calls with the
proper helper function call.

Regression-tested on BuildBot.

Thanks,

gdb/ChangeLog:
2017-02-17  Sergio Durigan Junior  <sergiodj@redhat.com>
	    Pedro Alves  <palves@redhat.com>

	PR gdb/16188
	* darwin-nat.c (darwin_ptrace_me): Check if calls to system
	calls succeeded.
	* fork-child.c (trace_start_error): New function.
	(trace_start_error_with_name): Likewise.
	* gnu-nat.c (gnu_ptrace_me): Check if call to PTRACE succeeded.
	* inf-ptrace.c (inf_ptrace_me): Likewise.
	* inferior.h (trace_start_error): New prototype.
	(trace_start_error_with_name): Likewise.
---
 gdb/darwin-nat.c | 20 +++++++++++++-------
 gdb/fork-child.c | 25 +++++++++++++++++++++++++
 gdb/gnu-nat.c    |  2 +-
 gdb/inf-ptrace.c |  3 ++-
 gdb/inferior.h   | 14 ++++++++++++++
 5 files changed, 55 insertions(+), 9 deletions(-)
  

Comments

Pedro Alves Feb. 20, 2017, 12:19 p.m. UTC | #1
Hi Sergio,

This LGTM, save for the errno handling in Darwin bits:

On 02/18/2017 05:09 AM, Sergio Durigan Junior wrote:
> diff --git a/gdb/darwin-nat.c b/gdb/darwin-nat.c
> index 8c5e8a0..e02e51d 100644
> --- a/gdb/darwin-nat.c
> +++ b/gdb/darwin-nat.c
> @@ -254,7 +254,6 @@ darwin_ptrace (const char *name,
>  {
>    int ret;
>  
> -  errno = 0;
>    ret = ptrace (request, pid, arg3, arg4);
>    if (ret == -1 && errno == 0)
>      ret = 0;

Removing "errno = 0" here is incorrect.  There are ptrace calls where a -1
return is not an error, thus that check for "errno==0" after the
ptrace call.  Since system calls are not required to clear errno on
success, that errno=0 is required.

This is Darwin, but the Linux man pages, in "man ptrace" say:

 On error, all requests return -1, and errno is set appropriately.  Since the
 value returned by a successful PTRACE_PEEK* request may be -1, the caller
 must clear errno before the call, and then check it afterward to determine whether
 or not an error occurred.

And actually, the comment just above darwin_ptrace talks
about clearning errno.  So it's really incorrect.

> @@ -1728,23 +1727,30 @@ darwin_ptrace_me (void)
>    int res;
>    char c;
>  
> +  errno = 0;

OTOH, I don't see the need to clear it here.  Below,
errno will only be used when a syscall fails, and in
failure case, the syscall must set errno.

>    /* Close write end point.  */
> -  close (ptrace_fds[1]);
> +  if (close (ptrace_fds[1]) < 0)
> +    trace_start_error_with_name ("close");
>  
>    /* Wait until gdb is ready.  */
>    res = read (ptrace_fds[0], &c, 1);
>    if (res != 0)
> -    error (_("unable to read from pipe, read returned: %d"), res);
> -  close (ptrace_fds[0]);
> +    trace_start_error (_("unable to read from pipe, read returned: %d"), res);
> +
> +  if (close (ptrace_fds[0]) < 0)
> +    trace_start_error_with_name ("close");
>  
>    /* Get rid of privileges.  */
> -  setegid (getgid ());
> +  if (setegid (getgid ()) < 0)
> +    trace_start_error_with_name ("setegid");
>  
>    /* Set TRACEME.  */
> -  PTRACE (PT_TRACE_ME, 0, 0, 0);
> +  if (PTRACE (PT_TRACE_ME, 0, 0, 0) < 0)
> +    trace_start_error_with_name ("PTRACE");
>  
>    /* Redirect signals to exception port.  */
> -  PTRACE (PT_SIGEXC, 0, 0, 0);
> +  if (PTRACE (PT_SIGEXC, 0, 0, 0) < 0)
> +    trace_start_error_with_name ("PTRACE");
>  }

Thanks,
Pedro Alves
  
Sergio Durigan Junior Feb. 20, 2017, 12:51 p.m. UTC | #2
On Monday, February 20 2017, Pedro Alves wrote:

> Hi Sergio,
>
> This LGTM, save for the errno handling in Darwin bits:
>
> On 02/18/2017 05:09 AM, Sergio Durigan Junior wrote:
>> diff --git a/gdb/darwin-nat.c b/gdb/darwin-nat.c
>> index 8c5e8a0..e02e51d 100644
>> --- a/gdb/darwin-nat.c
>> +++ b/gdb/darwin-nat.c
>> @@ -254,7 +254,6 @@ darwin_ptrace (const char *name,
>>  {
>>    int ret;
>>  
>> -  errno = 0;
>>    ret = ptrace (request, pid, arg3, arg4);
>>    if (ret == -1 && errno == 0)
>>      ret = 0;
>
> Removing "errno = 0" here is incorrect.  There are ptrace calls where a -1
> return is not an error, thus that check for "errno==0" after the
> ptrace call.  Since system calls are not required to clear errno on
> success, that errno=0 is required.
>
> This is Darwin, but the Linux man pages, in "man ptrace" say:
>
>  On error, all requests return -1, and errno is set appropriately.  Since the
>  value returned by a successful PTRACE_PEEK* request may be -1, the caller
>  must clear errno before the call, and then check it afterward to determine whether
>  or not an error occurred.
>
> And actually, the comment just above darwin_ptrace talks
> about clearning errno.  So it's really incorrect.

Oh, I'm really sorry, this was actually a mistake on the patch.  I meant
to delete the 'errno = 0;' on darwin_ptrace_me, not on darwin_ptrace.
Of course, I understand that errno must be cleared before the ptrace
call and I had read the exact same paragraph on the manpage.  Anyway,
sorry for wasting your time on this.

>> @@ -1728,23 +1727,30 @@ darwin_ptrace_me (void)
>>    int res;
>>    char c;
>>  
>> +  errno = 0;
>
> OTOH, I don't see the need to clear it here.  Below,
> errno will only be used when a syscall fails, and in
> failure case, the syscall must set errno.

Yeah.

I fixed the mistake and pushed the patch.  Thanks.

  0db8980cc0ee05727c11f8b7c6674137a4d5de4e
  

Patch

diff --git a/gdb/darwin-nat.c b/gdb/darwin-nat.c
index 8c5e8a0..e02e51d 100644
--- a/gdb/darwin-nat.c
+++ b/gdb/darwin-nat.c
@@ -254,7 +254,6 @@  darwin_ptrace (const char *name,
 {
   int ret;
 
-  errno = 0;
   ret = ptrace (request, pid, arg3, arg4);
   if (ret == -1 && errno == 0)
     ret = 0;
@@ -1728,23 +1727,30 @@  darwin_ptrace_me (void)
   int res;
   char c;
 
+  errno = 0;
   /* Close write end point.  */
-  close (ptrace_fds[1]);
+  if (close (ptrace_fds[1]) < 0)
+    trace_start_error_with_name ("close");
 
   /* Wait until gdb is ready.  */
   res = read (ptrace_fds[0], &c, 1);
   if (res != 0)
-    error (_("unable to read from pipe, read returned: %d"), res);
-  close (ptrace_fds[0]);
+    trace_start_error (_("unable to read from pipe, read returned: %d"), res);
+
+  if (close (ptrace_fds[0]) < 0)
+    trace_start_error_with_name ("close");
 
   /* Get rid of privileges.  */
-  setegid (getgid ());
+  if (setegid (getgid ()) < 0)
+    trace_start_error_with_name ("setegid");
 
   /* Set TRACEME.  */
-  PTRACE (PT_TRACE_ME, 0, 0, 0);
+  if (PTRACE (PT_TRACE_ME, 0, 0, 0) < 0)
+    trace_start_error_with_name ("PTRACE");
 
   /* Redirect signals to exception port.  */
-  PTRACE (PT_SIGEXC, 0, 0, 0);
+  if (PTRACE (PT_SIGEXC, 0, 0, 0) < 0)
+    trace_start_error_with_name ("PTRACE");
 }
 
 /* Dummy function to be sure fork_inferior uses fork(2) and not vfork(2).  */
diff --git a/gdb/fork-child.c b/gdb/fork-child.c
index eaa8cb5..f6256fb 100644
--- a/gdb/fork-child.c
+++ b/gdb/fork-child.c
@@ -109,6 +109,31 @@  escape_bang_in_quoted_argument (const char *shell_file)
   return 0;
 }
 
+/* See inferior.h.  */
+
+void
+trace_start_error (const char *fmt, ...)
+{
+  va_list ap;
+
+  va_start (ap, fmt);
+  fprintf_unfiltered (gdb_stderr, "Could not trace the inferior "
+		                  "process.\nError: ");
+  vfprintf_unfiltered (gdb_stderr, fmt, ap);
+  va_end (args);
+
+  gdb_flush (gdb_stderr);
+  _exit (0177);
+}
+
+/* See inferior.h.  */
+
+void
+trace_start_error_with_name (const char *string)
+{
+  trace_start_error ("%s: %s", string, safe_strerror (errno));
+}
+
 /* Start an inferior Unix child process and sets inferior_ptid to its
    pid.  EXEC_FILE is the file to run.  ALLARGS is a string containing
    the arguments to the program.  ENV is the environment vector to
diff --git a/gdb/gnu-nat.c b/gdb/gnu-nat.c
index 9935dcb..7efb3c1 100644
--- a/gdb/gnu-nat.c
+++ b/gdb/gnu-nat.c
@@ -2126,7 +2126,7 @@  gnu_ptrace_me (void)
   struct inf *inf = cur_inf ();
   inf_debug (inf, "tracing self");
   if (ptrace (PTRACE_TRACEME) != 0)
-    error (_("ptrace (PTRACE_TRACEME) failed!"));
+    trace_start_error_with_name ("ptrace");
 }
 
 static void
diff --git a/gdb/inf-ptrace.c b/gdb/inf-ptrace.c
index f61bfe7..21578742 100644
--- a/gdb/inf-ptrace.c
+++ b/gdb/inf-ptrace.c
@@ -79,7 +79,8 @@  static void
 inf_ptrace_me (void)
 {
   /* "Trace me, Dr. Memory!"  */
-  ptrace (PT_TRACE_ME, 0, (PTRACE_TYPE_ARG3)0, 0);
+  if (ptrace (PT_TRACE_ME, 0, (PTRACE_TYPE_ARG3) 0, 0) < 0)
+    trace_start_error_with_name ("ptrace");
 }
 
 /* Start a new inferior Unix child process.  EXEC_FILE is the file to
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 258cc29..7c0ddf3 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -132,6 +132,20 @@  extern void child_terminal_init_with_pgrp (int pgrp);
 
 /* From fork-child.c */
 
+/* Report an error that happened when starting to trace the inferior
+   (i.e., when the "traceme_fun" callback is called on fork_inferior)
+   and bail out.  This function does not return.  */
+
+extern void trace_start_error (const char *fmt, ...)
+  ATTRIBUTE_NORETURN;
+
+/* Like "trace_start_error", but the error message is constructed by
+   combining STRING with the system error message for errno.  This
+   function does not return.  */
+
+extern void trace_start_error_with_name (const char *string)
+  ATTRIBUTE_NORETURN;
+
 extern int fork_inferior (char *, char *, char **,
 			  void (*)(void),
 			  void (*)(int), void (*)(void), char *,