Improve ptrace-error detection on Linux targets
authorSergio Durigan Junior <sergiodj@redhat.com>
Fri, 16 Aug 2019 23:36:37 +0000 (19:36 -0400)
committerSergio Durigan Junior <sergiodj@redhat.com>
Thu, 26 Sep 2019 17:48:58 +0000 (13:48 -0400)
In Fedora GDB, we carry the following patch:

  https://src.fedoraproject.org/rpms/gdb/blob/8ac06474ff1e2aa4920d14e0666b083eeaca8952/f/gdb-attach-fail-reasons-5of5.patch

Its purpose is to try to detect a specific scenario where SELinux's
'deny_ptrace' option is enabled, which prevents GDB from ptrace'ing in
order to debug the inferior (PTRACE_ATTACH and PTRACE_TRACEME will
fail with EACCES in this case).

I like the idea of improving error detection and providing more
information to the user (a simple "Permission denied" can be really
frustrating), but I don't fully agree with the way the patch was
implemented: it makes GDB link against libselinux only for the sake of
consulting the 'deny_ptrace' setting, and then prints a warning if
ptrace failed and this setting is on.

My first thought (and attempt) was to make GDB print a generic warning
when a ptrace error happened; this message would just point the user
to our documentation, where she could find more information about
possible causes for the error (and try to diagnose/fix the problem).
This proved to be too simple, and I was convinced that it is actually
a good idea to go the extra kilometre and try to pinpoint the specific
problem (or problems) preventing ptrace from working, as well as
provide useful suggestions on how the user can fix things.

Here is the patch I came up with.  It implements a new function,
'linux_ptrace_restricted_fail_reason', which does a few things to
check what's wrong with ptrace:

  - It dlopen's "libselinux.so.1" and checks if the "deny_ptrace"
    option is enabled.

  - It reads the contents of "/proc/sys/kernel/yama/ptrace_scope" and
    checks if it's different than 0.

For each of these checks, if it succeeds, the user will see a message
informing about the restriction in place, and how it can be disabled.
For example, if "deny_ptrace" is enabled, the user will see:

  # gdb /usr/bin/true
  ...
  Starting program: /usr/bin/true
  warning: Could not trace the inferior process.
  warning: ptrace: Permission denied
  The SELinux 'deny_ptrace' option is enabled and preventing GDB
  from using 'ptrace'.  You can disable it by executing (as root):

    setsebool deny_ptrace off

  If you are debugging the inferior remotely, the ptrace restriction(s) need
  to be disabled in the target system (e.g., where GDBserver is running).
  During startup program exited with code 127.
  (gdb)

In case "/proc/sys/kernel/yama/ptrace_scope" is > 0:

  # gdb /usr/bin/true
  ...
  Starting program: /usr/bin/true
  warning: Could not trace the inferior process.
  warning: ptrace: Operation not permitted
  The Linux kernel's Yama ptrace scope is in effect, which can prevent
  GDB from using 'ptrace'.  You can disable it by executing (as root):

    echo 0 > /proc/sys/kernel/yama/ptrace_scope

  If you are debugging the inferior remotely, the ptrace restriction(s) need
  to be disabled in the target system (e.g., where GDBserver is running).
  During startup program exited with code 127.
  (gdb)

If both restrictions are enabled, both messages will show up.

This works for gdbserver as well, and actually fixes a latent bug I
found: when ptrace is restricted, gdbserver would hang due to an
unchecked ptrace call:

  # gdbserver :9988 /usr/bin/true
  gdbserver: linux_ptrace_test_ret_to_nx: Cannot PTRACE_TRACEME: Operation not permitted
  gdbserver: linux_ptrace_test_ret_to_nx: status 256 is not WIFSTOPPED!
  gdbserver: linux_ptrace_test_ret_to_nx: failed to kill child pid 2668100 No such process
  [ Here you would have to issue a C-c ]

Now, you will see:

  # gdbserver :9988 /usr/bin/true
  gdbserver: linux_ptrace_test_ret_to_nx: Cannot PTRACE_TRACEME: Permission denied
  gdbserver: linux_ptrace_test_ret_to_nx: status 256 is not WIFSTOPPED!
  gdbserver: linux_ptrace_test_ret_to_nx: failed to kill child pid 2766868 No such process
  gdbserver: Could not trace the inferior process.
  gdbserver: ptrace: Permission denied
  The SELinux 'deny_ptrace' option is enabled and preventing GDB
  from using 'ptrace'.  You can disable it by executing (as root):

    setsebool deny_ptrace off

  If you are debugging the inferior remotely, the ptrace restriction(s) need
  to be disabled in the target system (e.g., where GDBserver is running).
  #

(I decided to keep all the other messages, even though I find them a
bit distracting).

If GDB can't determine the cause for the failure, it will still print
the generic error message which tells the user to check our
documentation:

  There might be restrictions preventing ptrace from working.  Please see
  the appendix "Linux kernel ptrace restrictions" in the GDB documentation
  for more details.
  If you are debugging the inferior remotely, the ptrace restriction(s) need
  to be disabled in the target system (e.g., where GDBserver is running).

This means that the patch expands our documentation and creates a new
appendix section named "Linux kernel ptrace restrictions", with
sub-sections for each possible restriction that might be in place.

Notice how, on every message, we instruct the user to "do the right
thing" if gdbserver is being used.  This is because if the user
started gdbserver *before* any ptrace restriction was in place, and
then, for some reason, one or more restrictions get enabled, then the
error message will be displayed both on gdbserver *and* on the
connected GDB.  Since the user will be piloting GDB, it's important to
explicitly say that the ptrace restrictions are enabled in the target,
where gdbserver is running.

The current list of possible restrictions is:

  - SELinux's 'deny_ptrace' option (detected).

  - YAMA's /proc/sys/kernel/yama/ptrace_scope setting (detected).

  - seccomp on Docker containers (I couldn't find how to detect).

It's important to mention that all of this is Linux-specific; as far
as I know, SELinux, YAMA and seccomp are Linux-only features.

I tested this patch locally, on my Fedora 30 machine (actually, a
Fedora Rawhide VM), but I'm not proposing a testcase for it because of
the difficulty of writing one.

WDYT?

gdb/doc/ChangeLog:
2019-09-26  Sergio Durigan Junior  <sergiodj@redhat.com>

* gdb.texinfo (Linux kernel ptrace restrictions): New appendix
section.

gdb/ChangeLog:
2019-09-26  Sergio Durigan Junior  <sergiodj@redhat.com>
    Jan Kratochvil  <jan.kratochvil@redhat.com>
    Pedro Alves  <palves@redhat.com>

* gdbsupport/gdb-dlfcn.h (gdb_dlopen): Update comment and
mention that the function throws an error.
* inf-ptrace.c (default_inf_ptrace_me_fail_reason): New
function.
(inf_ptrace_me_fail_reason): New variable.
(inf_ptrace_me): Update call to 'trace_start_error_with_name'.
* inf-ptrace.h (inf_ptrace_me_fail_reason): New variable.
* linux-nat.c (attach_proc_task_lwp_callback): Call
'linux_ptrace_attach_fail_reason_lwp'.
(linux_nat_target::attach): Update call to
'linux_ptrace_attach_fail_reason'.
(_initialize_linux_nat): Set 'inf_ptrace_me_fail_reason'.
* nat/fork-inferior.c (trace_start_error_with_name): Add
optional 'append' argument.
* nat/fork-inferior.h (trace_start_error_with_name): Update
prototype.
* nat/linux-ptrace.c: Include "gdbsupport/gdb-dlfcn.h",
"gdbsupport/filestuff.h" and "nat/fork-inferior.h".
(selinux_ftype): New typedef.
(linux_ptrace_restricted_fail_reason): New function.
(linux_ptrace_attach_fail_reason_1): New function.
(linux_ptrace_attach_fail_reason): Change first argument type
from 'ptid_t' to 'pid_t'.  Call
'linux_ptrace_attach_fail_reason_1' and
'linux_ptrace_restricted_fail_reason'.
(linux_ptrace_attach_fail_reason_lwp): New function.
(linux_ptrace_me_fail_reason): New function.
(errno_pipe): New variable.
(linux_fork_to_function): Initialize pipe before forking.
(linux_child_function): Deal with errno-passing from child.
Handle ptrace error.
(linux_check_child_ptrace_errno): New function.
(linux_check_child_ptrace_errno): Call
'linux_check_child_ptrace_errno'.
* nat/linux-ptrace.h (linux_ptrace_attach_fail_reason): Update
prototype.
(linux_ptrace_attach_fail_reason_lwp): New prototype.
(linux_ptrace_me_fail_reason): New prototype.
* remote.c (extended_remote_target::attach): Handle error
message passed by the server when attach fails.

gdb/gdbserver/ChangeLog:
2019-09-26  Sergio Durigan Junior  <sergiodj@redhat.com>
    Pedro Alves  <palves@redhat.com>

* linux-low.c (linux_ptrace_fun): Call
'linux_ptrace_me_fail_reason'.
(attach_proc_task_lwp_callback): Call
'linux_ptrace_attach_fail_reason_lwp'.
(linux_attach): Call 'linux_ptrace_attach_fail_reason'.
* server.c (handle_v_attach): Use try..catch when calling
'attach_inferior', and send an error message to the client
when needed.
* thread-db.c (attach_thread): Call
'linux_ptrace_attach_fail_reason_lwp'.

16 files changed:
gdb/ChangeLog
gdb/doc/ChangeLog
gdb/doc/gdb.texinfo
gdb/gdbserver/ChangeLog
gdb/gdbserver/linux-low.c
gdb/gdbserver/server.c
gdb/gdbserver/thread-db.c
gdb/gdbsupport/gdb-dlfcn.h
gdb/inf-ptrace.c
gdb/inf-ptrace.h
gdb/linux-nat.c
gdb/nat/fork-inferior.c
gdb/nat/fork-inferior.h
gdb/nat/linux-ptrace.c
gdb/nat/linux-ptrace.h
gdb/remote.c

index f323cf6b14d692b3465084efb2d8497e1a0ea4f0..37626bdd1e1e576f9bae7e44889dccf7318edbb5 100644 (file)
@@ -1,3 +1,48 @@
+2019-09-26  Sergio Durigan Junior  <sergiodj@redhat.com>
+           Jan Kratochvil  <jan.kratochvil@redhat.com>
+           Pedro Alves  <palves@redhat.com>
+
+       * gdbsupport/gdb-dlfcn.h (gdb_dlopen): Update comment and
+       mention that the function throws an error.
+       * inf-ptrace.c (default_inf_ptrace_me_fail_reason): New
+       function.
+       (inf_ptrace_me_fail_reason): New variable.
+       (inf_ptrace_me): Update call to 'trace_start_error_with_name'.
+       * inf-ptrace.h (inf_ptrace_me_fail_reason): New variable.
+       * linux-nat.c (attach_proc_task_lwp_callback): Call
+       'linux_ptrace_attach_fail_reason_lwp'.
+       (linux_nat_target::attach): Update call to
+       'linux_ptrace_attach_fail_reason'.
+       (_initialize_linux_nat): Set 'inf_ptrace_me_fail_reason'.
+       * nat/fork-inferior.c (trace_start_error_with_name): Add
+       optional 'append' argument.
+       * nat/fork-inferior.h (trace_start_error_with_name): Update
+       prototype.
+       * nat/linux-ptrace.c: Include "gdbsupport/gdb-dlfcn.h",
+       "gdbsupport/filestuff.h" and "nat/fork-inferior.h".
+       (selinux_ftype): New typedef.
+       (linux_ptrace_restricted_fail_reason): New function.
+       (linux_ptrace_attach_fail_reason_1): New function.
+       (linux_ptrace_attach_fail_reason): Change first argument type
+       from 'ptid_t' to 'pid_t'.  Call
+       'linux_ptrace_attach_fail_reason_1' and
+       'linux_ptrace_restricted_fail_reason'.
+       (linux_ptrace_attach_fail_reason_lwp): New function.
+       (linux_ptrace_me_fail_reason): New function.
+       (errno_pipe): New variable.
+       (linux_fork_to_function): Initialize pipe before forking.
+       (linux_child_function): Deal with errno-passing from child.
+       Handle ptrace error.
+       (linux_check_child_ptrace_errno): New function.
+       (linux_check_child_ptrace_errno): Call
+       'linux_check_child_ptrace_errno'.
+       * nat/linux-ptrace.h (linux_ptrace_attach_fail_reason): Update
+       prototype.
+       (linux_ptrace_attach_fail_reason_lwp): New prototype.
+       (linux_ptrace_me_fail_reason): New prototype.
+       * remote.c (extended_remote_target::attach): Handle error
+       message passed by the server when attach fails.
+
 2019-09-26  Christian Biesinger  <cbiesinger@google.com>
 
        * blockframe.c (find_pc_partial_function): Change return type to bool.
index 0a10fa3fade7b0d911b1188e954a8c64e8cf557d..003c8c2ffea47de467b5ef9e0f595f794c12d8f3 100644 (file)
@@ -1,3 +1,8 @@
+2019-09-26  Sergio Durigan Junior  <sergiodj@redhat.com>
+
+       * gdb.texinfo (Linux kernel ptrace restrictions): New appendix
+       section.
+
 2019-09-20  Ulrich Weigand  <uweigand@de.ibm.com>
 
        * doc/gdb.texinfo (Remote Configuration): Remove documentation for
index f2713c03960dbe3bf4cc15714fc2bd68794db26e..e7b5b18f2b329afc4a628918e5e6b7e87116430f 100644 (file)
@@ -182,6 +182,9 @@ software in general.  We will miss him.
                                 @value{GDBN}
 * Operating System Information:: Getting additional information from
                                  the operating system
+* Linux kernel ptrace restrictions::        Restrictions sometimes
+                                            imposed by the Linux
+                                            kernel on @code{ptrace}
 * Trace File Format::          GDB trace file format
 * Index Section Format::        .gdb_index section format
 * Man Pages::                  Manual pages
@@ -44665,6 +44668,146 @@ should contain a comma-separated list of cores that this process
 is running on.  Target may provide additional columns,
 which @value{GDBN} currently ignores.
 
+@node Linux kernel ptrace restrictions
+@appendix Linux kernel @code{ptrace} restrictions
+@cindex linux kernel ptrace restrictions, attach
+
+The @code{ptrace} system call is used by @value{GDBN} and
+@code{gdbserver} on GNU/Linux to, among other things, attach to a new
+or existing inferior in order to start debugging it.  Due to security
+concerns, some distributions and vendors disable or severely restrict
+the ability to perform these operations, which can make @value{GDBN}
+or @code{gdbserver} malfunction.  In this section, we will expand on
+how this malfunction can manifest itself, and how to modify the
+system's settings in order to be able to use @value{GDBN} and
+@code{gdbserver} properly.
+
+@menu
+* The error message::                   The error message displayed when the
+                                        system prevents @value{GDBN}
+                                        or @code{gdbserver} from using
+                                        @code{ptrace}
+* SELinux's deny_ptrace::               SELinux and the @code{deny_ptrace} option
+* Yama's ptrace_scope::                 Yama and the @code{ptrace_scope} setting
+* Docker and seccomp::                  Docker and the @code{seccomp}
+                                        infrastructure
+@end menu
+
+@node The error message
+@appendixsection The error message
+
+When the system prevents @value{GDBN} or @code{gdbserver} from using
+the @code{ptrace} system call, you will likely see a descriptive error
+message explaining what is wrong and how to attempt to fix the
+problem.  For example, when SELinux's @code{deny_ptrace} option is
+enabled, you can see:
+
+@smallexample
+$ gdb program
+...
+(@value{GDBP}) run
+Starting program: program
+warning: Could not trace the inferior process.
+Error:
+warning: ptrace: Permission denied
+The SELinux 'deny_ptrace' option is enabled and preventing @value{GDBN}
+from using 'ptrace'.  You can disable it by executing (as root):
+
+  setsebool deny_ptrace off
+
+If you are debugging the inferior remotely, the instruction(s) above must
+be performed in the target system (e.g., where GDBserver is running).
+During startup program exited with code 127.
+(@value{GDBP})
+@end smallexample
+
+Sometimes, it may not be possible to acquire the necessary data to
+determine the root cause of the failure.  In this case, you will see a
+generic error message pointing you to this section:
+
+@smallexample
+$ gdb program
+...
+Starting program: program
+warning: Could not trace the inferior process.
+Error:
+warning: ptrace: Permission denied
+There might be restrictions preventing ptrace from working.  Please see
+the appendix "Linux kernel ptrace restrictions" in the GDB documentation
+for more details.
+During startup program exited with code 127.
+(@value{GDBP})
+@end smallexample
+
+@node SELinux's deny_ptrace
+@appendixsection SELinux's @code{deny_ptrace}
+@cindex SELinux
+@cindex deny_ptrace
+
+If you are using SELinux, you might want to check whether the
+@code{deny_ptrace} option is enabled by doing:
+
+@smallexample
+$ getsebool deny_ptrace
+deny_ptrace --> on
+@end smallexample
+
+If the option is enabled, you can disable it by doing, as root:
+
+@smallexample
+# setsebool deny_ptrace off
+@end smallexample
+
+The option will be disabled until the next reboot.  If you would like
+to disable it permanently, you can do (as root):
+
+@smallexample
+# setsebool -P deny_ptrace off
+@end smallexample
+
+@node Yama's ptrace_scope
+@appendixsection Yama's @code{ptrace_scope}
+@cindex yama, ptrace_scope
+
+If your system has Yama enabled, you might want to check whether the
+@code{ptrace_scope} setting is enabled by checking the value of
+@file{/proc/sys/kernel/yama/ptrace_scope}:
+
+@smallexample
+$ cat /proc/sys/kernel/yama/ptrace_scope
+0
+@end smallexample
+
+If you see anything other than @code{0}, @value{GDBN} or
+@code{gdbserver} can be affected by it.  You can temporarily disable
+the feature by doing, as root:
+
+@smallexample
+# sysctl kernel.yama.ptrace_scope=0
+kernel.yama.ptrace_scope = 0
+@end smallexample
+
+You can make this permanent by doing, as root:
+
+@smallexample
+# sysctl -w kernel.yama.ptrace_scope=0
+kernel.yama.ptrace_scope = 0
+@end smallexample
+
+@node Docker and seccomp
+@appendixsection Docker and @code{seccomp}
+@cindex docker, seccomp
+
+If you are using Docker (@uref{https://www.docker.com/}) containers,
+you will probably have to disable its @code{seccomp} protections in
+order to be able to use @value{GDBN} or @code{gdbserver}.  To do that,
+you can use the options @code{--cap-add=SYS_PTRACE --security-opt
+seccomp=unconfined} when invoking Docker:
+
+@smallexample
+$ docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined
+@end smallexample
+
 @node Trace File Format
 @appendix Trace File Format
 @cindex trace file format
index c0c6f51a0695b9891f8c275e7f2f3792d47d6a9d..e60980dc560b0032f1c55aefda98586845c0d659 100644 (file)
@@ -1,3 +1,17 @@
+2019-09-26  Sergio Durigan Junior  <sergiodj@redhat.com>
+           Pedro Alves  <palves@redhat.com>
+
+       * linux-low.c (linux_ptrace_fun): Call
+       'linux_ptrace_me_fail_reason'.
+       (attach_proc_task_lwp_callback): Call
+       'linux_ptrace_attach_fail_reason_lwp'.
+       (linux_attach): Call 'linux_ptrace_attach_fail_reason'.
+       * server.c (handle_v_attach): Use try..catch when calling
+       'attach_inferior', and send an error message to the client
+       when needed.
+       * thread-db.c (attach_thread): Call
+       'linux_ptrace_attach_fail_reason_lwp'.
+
 2019-09-20  Christian Biesinger  <cbiesinger@google.com>
 
        * debug.c (debug_threads): Remove comment in favor of the header.
index d64c3641ffb12412945593815c8ce9e51acfa3b5..c0e15c122f2a7314594c5149d53934061313701d 100644 (file)
@@ -967,7 +967,8 @@ linux_ptrace_fun ()
 {
   if (ptrace (PTRACE_TRACEME, 0, (PTRACE_TYPE_ARG3) 0,
              (PTRACE_TYPE_ARG4) 0) < 0)
-    trace_start_error_with_name ("ptrace");
+    trace_start_error_with_name ("ptrace",
+                                linux_ptrace_me_fail_reason (errno).c_str ());
 
   if (setpgid (0, 0) < 0)
     trace_start_error_with_name ("setpgid");
@@ -1165,7 +1166,7 @@ attach_proc_task_lwp_callback (ptid_t ptid)
       else if (err != 0)
        {
          std::string reason
-           = linux_ptrace_attach_fail_reason_string (ptid, err);
+           = linux_ptrace_attach_fail_reason_lwp (ptid, err);
 
          warning (_("Cannot attach to lwp %d: %s"), lwpid, reason.c_str ());
        }
@@ -1197,8 +1198,8 @@ linux_attach (unsigned long pid)
     {
       remove_process (proc);
 
-      std::string reason = linux_ptrace_attach_fail_reason_string (ptid, err);
-      error ("Cannot attach to process %ld: %s", pid, reason.c_str ());
+      std::string reason = linux_ptrace_attach_fail_reason (pid, err);
+      error (_("Cannot attach to process %ld: %s"), pid, reason.c_str ());
     }
 
   /* Don't ignore the initial SIGSTOP if we just attached to this
index 67e8e3e54de363ffa77547b79d9ed01f0a4981df..976ecbd2dfbfe2d137a6a37944df804b1c93bb7d 100644 (file)
@@ -2893,9 +2893,21 @@ handle_v_attach (char *own_buf)
 {
   client_state &cs = get_client_state ();
   int pid;
+  int ret;
 
   pid = strtol (own_buf + 8, NULL, 16);
-  if (pid != 0 && attach_inferior (pid) == 0)
+
+  try
+    {
+      ret = attach_inferior (pid);
+    }
+  catch (const gdb_exception_error &e)
+    {
+      snprintf (own_buf, PBUFSIZ, "E.%s", e.what ());
+      return 0;
+    }
+
+  if (pid != 0 && ret == 0)
     {
       /* Don't report shared library events after attaching, even if
         some libraries are preloaded.  GDB will always poll the
index c6b43a06cca7adee05ac4bfd39d1443a32b60192..e3acf83850245467c5f4b66fecc3656388a30955 100644 (file)
@@ -224,7 +224,7 @@ attach_thread (const td_thrhandle_t *th_p, td_thrinfo_t *ti_p)
   err = linux_attach_lwp (ptid);
   if (err != 0)
     {
-      std::string reason = linux_ptrace_attach_fail_reason_string (ptid, err);
+      std::string reason = linux_ptrace_attach_fail_reason_lwp (ptid, err);
 
       warning ("Could not attach to thread %ld (LWP %d): %s",
               (unsigned long) ti_p->ti_tid, ti_p->ti_lid, reason.c_str ());
index 6a39d38941acf63a0b0802f72e8b085642408f5f..e933b7a473e2c0401c198e49125a042ab829103e 100644 (file)
@@ -32,8 +32,8 @@ struct dlclose_deleter
 typedef std::unique_ptr<void, dlclose_deleter> gdb_dlhandle_up;
 
 /* Load the dynamic library file named FILENAME, and return a handle
-   for that dynamic library.  Return NULL if the loading fails for any
-   reason.  */
+   for that dynamic library.  Throw an error if the loading fails for
+   any reason.  */
 
 gdb_dlhandle_up gdb_dlopen (const char *filename);
 
index 4a8e7323736cdab22ee328119a0b2ffa0b0600e9..b792af00d10d085d5482d779e9bd41e80fc3a8b8 100644 (file)
@@ -94,6 +94,20 @@ inf_ptrace_target::remove_fork_catchpoint (int pid)
 #endif /* PT_GET_PROCESS_STATE */
 \f
 
+/* Default method for "inf_ptrace_me_fail_reason", which returns an
+   empty string.  */
+
+static std::string
+default_inf_ptrace_me_fail_reason (int err)
+{
+  return {};
+}
+
+/* See inf-ptrace.h.  */
+
+std::string (*inf_ptrace_me_fail_reason) (int err)
+  = default_inf_ptrace_me_fail_reason;
+
 /* Prepare to be traced.  */
 
 static void
@@ -101,7 +115,8 @@ inf_ptrace_me (void)
 {
   /* "Trace me, Dr. Memory!"  */
   if (ptrace (PT_TRACE_ME, 0, (PTRACE_TYPE_ARG3) 0, 0) < 0)
-    trace_start_error_with_name ("ptrace");
+    trace_start_error_with_name ("ptrace",
+                                inf_ptrace_me_fail_reason (errno).c_str ());
 }
 
 /* Start a new inferior Unix child process.  EXEC_FILE is the file to
index 98b5d2e09e686b0c4490f1fd442e4be61bab6a61..7cdab9af892d44cefa86dcb0e5ad4d54bfc8219f 100644 (file)
@@ -83,4 +83,14 @@ protected:
 
 extern pid_t get_ptrace_pid (ptid_t);
 
+/* Pointer to "inf_ptrace_me_fail_reason", which implements a function
+   that can be called by "inf_ptrace_me" in order to obtain the reason
+   for a ptrace failure.  ERR is the ERRNO value set by the failing
+   ptrace call.
+
+   This pointer can be overriden by targets that want to personalize
+   the error message printed when ptrace fails (see linux-nat.c, for
+   example).  */
+extern std::string (*inf_ptrace_me_fail_reason) (int err);
+
 #endif
index cd5cf1830d3fdc1ed4f48b7571f30538cb35dc4e..2c7ded70436cbbd989484d9651762383ad8e83b6 100644 (file)
@@ -1132,7 +1132,7 @@ attach_proc_task_lwp_callback (ptid_t ptid)
          else
            {
              std::string reason
-               = linux_ptrace_attach_fail_reason_string (ptid, err);
+               = linux_ptrace_attach_fail_reason_lwp (ptid, err);
 
              warning (_("Cannot attach to lwp %d: %s"),
                       lwpid, reason.c_str ());
@@ -1187,8 +1187,9 @@ linux_nat_target::attach (const char *args, int from_tty)
     }
   catch (const gdb_exception_error &ex)
     {
+      int saved_errno = errno;
       pid_t pid = parse_pid_to_attach (args);
-      std::string reason = linux_ptrace_attach_fail_reason (pid);
+      std::string reason = linux_ptrace_attach_fail_reason (pid, saved_errno);
 
       if (!reason.empty ())
        throw_error (ex.error, "warning: %s\n%s", reason.c_str (),
@@ -4567,6 +4568,10 @@ Enables printf debugging output."),
   sigemptyset (&blocked_mask);
 
   lwp_lwpid_htab_create ();
+
+  /* Set the proper function to generate a message when ptrace
+     fails.  */
+  inf_ptrace_me_fail_reason = linux_ptrace_me_fail_reason;
 }
 \f
 
index 355e9bef430308e748901d1820748360eeae0c48..2ead4a4858b4c77e94427e8aff8e7aa53ee85cb7 100644 (file)
@@ -591,7 +591,7 @@ trace_start_error (const char *fmt, ...)
 /* See nat/fork-inferior.h.  */
 
 void
-trace_start_error_with_name (const char *string)
+trace_start_error_with_name (const char *string, const char *append)
 {
-  trace_start_error ("%s: %s", string, safe_strerror (errno));
+  trace_start_error ("%s: %s%s", string, safe_strerror (errno), append);
 }
index 065496c38279105e101c781c6db893a13d8cea7c..405f2cf548bae2a6b20065a609f26f73056fa7bf 100644 (file)
@@ -98,9 +98,10 @@ extern void trace_start_error (const char *fmt, ...)
   ATTRIBUTE_NORETURN ATTRIBUTE_PRINTF (1, 2);
 
 /* 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)
+   combining STRING with the system error message for errno, and
+   (optionally) with APPEND.  This function does not return.  */
+extern void trace_start_error_with_name (const char *string,
+                                        const char *append = "")
   ATTRIBUTE_NORETURN;
 
 #endif /* NAT_FORK_INFERIOR_H */
index c1ebc0a032dfce899aeeba0c7534c93508b7cc9a..8a048d2ec99cb08d4039600e81ae827e973277e0 100644 (file)
@@ -21,6 +21,9 @@
 #include "linux-procfs.h"
 #include "linux-waitpid.h"
 #include "gdbsupport/buffer.h"
+#include "gdbsupport/gdb-dlfcn.h"
+#include "nat/fork-inferior.h"
+#include "gdbsupport/filestuff.h"
 #ifdef HAVE_SYS_PROCFS_H
 #include <sys/procfs.h>
 #endif
    of 0 means there are no supported features.  */
 static int supported_ptrace_options = -1;
 
-/* Find all possible reasons we could fail to attach PID and return these
-   as a string.  An empty string is returned if we didn't find any reason.  */
+typedef int (*selinux_ftype) (const char *);
 
-std::string
-linux_ptrace_attach_fail_reason (pid_t pid)
+/* Helper function which checks if ptrace is probably restricted
+   (i.e., if ERR is either EACCES or EPERM), and returns a string with
+   possible workarounds.  */
+
+static std::string
+linux_ptrace_restricted_fail_reason (int err)
+{
+  if (err != EACCES && err != EPERM)
+    {
+      /* It just makes sense to perform the checks below if errno was
+        either EACCES or EPERM.  */
+      return {};
+    }
+
+  std::string ret;
+  gdb_dlhandle_up handle;
+
+  try
+    {
+      handle = gdb_dlopen ("libselinux.so.1");
+    }
+  catch (const gdb_exception_error &e)
+    {
+      handle.reset (nullptr);
+    }
+
+  if (handle != nullptr)
+    {
+      selinux_ftype selinux_get_bool
+       = (selinux_ftype) gdb_dlsym (handle, "security_get_boolean_active");
+
+      if (selinux_get_bool != NULL
+         && (*selinux_get_bool) ("deny_ptrace") == 1)
+       string_appendf (ret,
+                       _("\n\
+The SELinux 'deny_ptrace' option is enabled and preventing GDB\n\
+from using 'ptrace'.  You can disable it by executing (as root):\n\
+\n\
+  setsebool deny_ptrace off\n"));
+    }
+
+  gdb_file_up yama_ptrace_scope
+    = gdb_fopen_cloexec ("/proc/sys/kernel/yama/ptrace_scope", "r");
+
+  if (yama_ptrace_scope != nullptr)
+    {
+      char yama_scope = fgetc (yama_ptrace_scope.get ());
+
+      if (yama_scope != '0')
+       string_appendf (ret,
+                       _("\n\
+The Linux kernel's Yama ptrace scope is in effect, which can prevent\n\
+GDB from using 'ptrace'.  You can disable it by executing (as root):\n\
+\n\
+  echo 0 > /proc/sys/kernel/yama/ptrace_scope\n"));
+    }
+
+  if (ret.empty ())
+    {
+      /* It wasn't possible to determine the exact reason for the
+        ptrace error.  Let's just emit a generic error message
+        pointing the user to our documentation, where she can find
+        instructions on how to try to diagnose the problem.  */
+      ret = _("\n\
+There might be restrictions preventing ptrace from working.  Please see\n\
+the appendix \"Linux kernel ptrace restrictions\" in the GDB documentation\n\
+for more details.");
+    }
+
+  /* The user may be debugging remotely, so we have to warn that
+     the instructions above should be performed in the target.  */
+  string_appendf (ret,
+                 _("\n\
+If you are debugging the inferior remotely, the ptrace restriction(s) must\n\
+be disabled in the target system (e.g., where GDBserver is running)."));
+
+  return ret;
+}
+
+/* Find all possible reasons we could fail to attach PID and return
+   these as a string.  An empty string is returned if we didn't find
+   any reason.  Helper for linux_ptrace_attach_fail_reason and
+   linux_ptrace_attach_fail_reason_lwp.  */
+
+static std::string
+linux_ptrace_attach_fail_reason_1 (pid_t pid)
 {
   pid_t tracerpid = linux_proc_get_tracerpid_nowarn (pid);
   std::string result;
@@ -56,10 +142,24 @@ linux_ptrace_attach_fail_reason (pid_t pid)
 /* See linux-ptrace.h.  */
 
 std::string
-linux_ptrace_attach_fail_reason_string (ptid_t ptid, int err)
+linux_ptrace_attach_fail_reason (pid_t pid, int err)
+{
+  std::string result = linux_ptrace_attach_fail_reason_1 (pid);
+  std::string ptrace_restrict = linux_ptrace_restricted_fail_reason (err);
+
+  if (!ptrace_restrict.empty ())
+    result += "\n" + ptrace_restrict;
+
+  return result;
+}
+
+/* See linux-ptrace.h.  */
+
+std::string
+linux_ptrace_attach_fail_reason_lwp (ptid_t ptid, int err)
 {
   long lwpid = ptid.lwp ();
-  std::string reason = linux_ptrace_attach_fail_reason (lwpid);
+  std::string reason = linux_ptrace_attach_fail_reason_1 (lwpid);
 
   if (!reason.empty ())
     return string_printf ("%s (%d), %s", safe_strerror (err), err,
@@ -68,6 +168,14 @@ linux_ptrace_attach_fail_reason_string (ptid_t ptid, int err)
     return string_printf ("%s (%d)", safe_strerror (err), err);
 }
 
+/* See linux-ptrace.h.  */
+
+std::string
+linux_ptrace_me_fail_reason (int err)
+{
+  return linux_ptrace_restricted_fail_reason (err);
+}
+
 #if defined __i386__ || defined __x86_64__
 
 /* Address of the 'ret' instruction in asm code block below.  */
@@ -257,6 +365,12 @@ linux_ptrace_test_ret_to_nx (void)
 #endif /* defined __i386__ || defined __x86_64__ */
 }
 
+/* If the PTRACE_TRACEME call on linux_child_function errors, we need
+   to be able to send ERRNO back to the parent so that it can check
+   whether there are restrictions in place preventing ptrace from
+   working.  We do that with a pipe.  */
+static int errno_pipe[2];
+
 /* Helper function to fork a process and make the child process call
    the function FUNCTION, passing CHILD_STACK as parameter.
 
@@ -273,6 +387,11 @@ linux_fork_to_function (gdb_byte *child_stack, int (*function) (void *))
   /* Sanity check the function pointer.  */
   gdb_assert (function != NULL);
 
+  /* Create the pipe that will be used by the child to pass ERRNO
+     after the PTRACE_TRACEME call.  */
+  if (pipe (errno_pipe) != 0)
+    trace_start_error_with_name ("pipe");
+
 #if defined(__UCLIBC__) && defined(HAS_NOMMU)
 #define STACK_SIZE 4096
 
@@ -321,7 +440,21 @@ linux_grandchild_function (void *child_stack)
 static int
 linux_child_function (void *child_stack)
 {
-  ptrace (PTRACE_TRACEME, 0, (PTRACE_TYPE_ARG3) 0, (PTRACE_TYPE_ARG4) 0);
+  /* Close read end.  */
+  close (errno_pipe[0]);
+
+  int ret = ptrace (PTRACE_TRACEME, 0, (PTRACE_TYPE_ARG3) 0,
+                   (PTRACE_TYPE_ARG4) 0);
+  int ptrace_errno = errno;
+
+  /* Write ERRNO to the pipe, even if it's zero, and close the writing
+     end of the pipe.  */
+  write (errno_pipe[1], &ptrace_errno, sizeof (ptrace_errno));
+  close (errno_pipe[1]);
+
+  if (ret != 0)
+    _exit (0);
+
   kill (getpid (), SIGSTOP);
 
   /* Fork a grandchild.  */
@@ -336,6 +469,48 @@ static void linux_test_for_tracesysgood (int child_pid);
 static void linux_test_for_tracefork (int child_pid);
 static void linux_test_for_exitkill (int child_pid);
 
+/* Helper function to wait for the child to send us the ptrace ERRNO,
+   and check if it's OK.  */
+
+static void
+linux_check_child_ptrace_errno ()
+{
+  int child_errno;
+  fd_set rset;
+  struct timeval timeout;
+
+  /* Close the writing end of the pipe.  */
+  close (errno_pipe[1]);
+
+  FD_ZERO (&rset);
+  FD_SET (errno_pipe[0], &rset);
+
+  /* One second should be plenty of time to wait for the child's
+     reply.  */
+  timeout.tv_sec = 1;
+  timeout.tv_usec = 0;
+
+  int ret = select (errno_pipe[0] + 1, &rset, NULL, NULL, &timeout);
+
+  if (ret < 0)
+    trace_start_error_with_name ("select");
+  else if (ret == 0)
+    error (_("Timeout while waiting for child's ptrace errno"));
+  else
+    read (errno_pipe[0], &child_errno, sizeof (child_errno));
+
+  if (child_errno != 0)
+    {
+      /* The child can't use PTRACE_TRACEME.  We just bail out.  */
+      std::string reason = linux_ptrace_restricted_fail_reason (child_errno);
+
+      errno = child_errno;
+      trace_start_error_with_name ("ptrace", reason.c_str ());
+    }
+
+  close (errno_pipe[0]);
+}
+
 /* Determine ptrace features available on this target.  */
 
 void
@@ -352,6 +527,9 @@ linux_check_ptrace_features (void)
      reporting.  */
   child_pid = linux_fork_to_function (NULL, linux_child_function);
 
+  /* Check if the child can successfully use ptrace.  */
+  linux_check_child_ptrace_errno ();
+
   ret = my_waitpid (child_pid, &status, 0);
   if (ret == -1)
     perror_with_name (("waitpid"));
index fd2f12a342ce5a85cab20fc02772bec0caf1b280..90afb60f3414aabecf4bc06954b97f11758bb8a5 100644 (file)
@@ -176,12 +176,27 @@ struct buffer;
 # define TRAP_HWBKPT 4
 #endif
 
-extern std::string linux_ptrace_attach_fail_reason (pid_t pid);
-
-/* Find all possible reasons we could have failed to attach to PTID
-   and return them as a string.  ERR is the error PTRACE_ATTACH failed
-   with (an errno).  */
-extern std::string linux_ptrace_attach_fail_reason_string (ptid_t ptid, int err);
+/* Find all possible reasons we could fail to attach PID and return
+   these as a string.  An empty string is returned if we didn't find
+   any reason.  If ERR is EACCES or EPERM, we also add a warning about
+   possible restrictions to use ptrace.  */
+extern std::string linux_ptrace_attach_fail_reason (pid_t pid, int err);
+
+/* Find all possible reasons we could have failed to attach to PID's
+   LWPID and return them as a string.  ERR is the error PTRACE_ATTACH
+   failed with (an errno).  Unlike linux_ptrace_attach_fail_reason,
+   this function should be used when attaching to an LWP other than
+   the leader; it does not warn about ptrace restrictions.  */
+extern std::string linux_ptrace_attach_fail_reason_lwp (ptid_t pid, int err);
+
+/* When the call to 'ptrace (PTRACE_TRACEME...' fails, and we have
+   already forked, this function can be called in order to try to
+   obtain the reason why ptrace failed.  ERR should be the ERRNO value
+   returned by ptrace.
+
+   This function will return a 'std::string' containing the fail
+   reason, or an empty string otherwise.  */
+extern std::string linux_ptrace_me_fail_reason (int err);
 
 extern void linux_ptrace_init_warnings (void);
 extern void linux_check_ptrace_features (void);
index 21160e13acc4b57743c69f164810da74032daa62..efc5084cfe5dc0dec6341cdcb2cc0824a760495e 100644 (file)
@@ -5825,8 +5825,20 @@ extended_remote_target::attach (const char *args, int from_tty)
     case PACKET_UNKNOWN:
       error (_("This target does not support attaching to a process"));
     default:
-      error (_("Attaching to %s failed"),
-            target_pid_to_str (ptid_t (pid)).c_str ());
+      {
+       std::string errmsg = rs->buf.data ();
+
+       if (!errmsg.empty ())
+         {
+           /* Get rid of the "E." prefix.  */
+           errmsg.erase (0, 2);
+         }
+
+       error (_("Attaching to %s failed%s%s"),
+              target_pid_to_str (ptid_t (pid)).c_str (),
+              !errmsg.empty () ? "\n" : "",
+              errmsg.c_str ());
+      }
     }
 
   set_current_inferior (remote_add_inferior (false, pid, 1, 0));