Blob Blame History Raw
From 8eab929c763e3a951794968f1a7d2f99a83c4f9d Mon Sep 17 00:00:00 2001
From: Stef Walter <stefw@redhat.com>
Date: Thu, 27 Aug 2015 09:37:10 +0200
Subject: [PATCH 1/2] common: Produce better backtraces with gdb when possible

Note that this may require this sysctl to be set on the system:

/proc/sys/kernel/yama/ptrace_scope = 0

https://wiki.ubuntu.com/SecurityTeam/Roadmap/KernelHardening#ptrace%20Protection

Reviewed-by: Peter <petervo@redhat.com>
---
 .travis.yml              |   2 +-
 src/common/cockpittest.c | 155 ++++++++++++++++++++++++++++++++++++++++++++++-
 tools/cockpit.spec       |   1 +
 3 files changed, 154 insertions(+), 4 deletions(-)

diff --git a/src/common/cockpittest.c b/src/common/cockpittest.c
index 5501681..bf20b11 100644
--- a/src/common/cockpittest.c
+++ b/src/common/cockpittest.c
@@ -39,6 +39,10 @@
 #include <netinet/ip6.h>
 #include <ifaddrs.h>
 
+#include <sys/select.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+
 /*
  * HACK: We can't yet use g_test_expect_message() and friends.
  * They were pretty broken until GLib 2.40 if you have any debug
@@ -466,20 +470,165 @@ _cockpit_assert_bytes_eq_msg (const char *domain,
                                expect, exp_len);
 }
 
+/*
+ * This gdb code only works if /proc/sys/kernel/yama/ptrace_scope is set to zero
+ * See: https://wiki.ubuntu.com/SecurityTeam/Roadmap/KernelHardening#ptrace%20Protection
+ */
+
+static gboolean stack_trace_done = FALSE;
+
+static void
+stack_trace_sigchld (int signum)
+{
+  stack_trace_done = TRUE;
+}
+
+static void
+stack_trace (char **args)
+{
+  pid_t pid;
+  int in_fd[2];
+  int out_fd[2];
+  fd_set fdset;
+  fd_set readset;
+  struct timeval tv;
+  int sel, idx, state;
+  char buffer[256];
+  char c;
+
+  stack_trace_done = FALSE;
+  signal (SIGCHLD, stack_trace_sigchld);
+
+  if ((pipe (in_fd) == -1) || (pipe (out_fd) == -1))
+    {
+      perror ("unable to open pipe");
+      _exit (0);
+    }
+
+  pid = fork ();
+  if (pid == 0)
+    {
+      /* Save stderr for printing failure below */
+      int old_err = dup (2);
+      fcntl (old_err, F_SETFD, fcntl (old_err, F_GETFD) | FD_CLOEXEC);
+
+      close (0); dup (in_fd[0]);   /* set the stdin to the in pipe */
+      close (1); dup (out_fd[1]);  /* set the stdout to the out pipe */
+
+      execvp (args[0], args);      /* exec gdb */
+
+      /* Print failure to original stderr */
+      perror ("exec gdb failed");
+      _exit (0);
+    }
+  else if (pid == (pid_t) -1)
+    {
+      perror ("unable to fork");
+      _exit (0);
+    }
+
+  FD_ZERO (&fdset);
+  FD_SET (out_fd[0], &fdset);
+
+  write (in_fd[1], "backtrace\n", 10);
+  write (in_fd[1], "quit\n", 5);
+
+  idx = 0;
+  state = 0;
+
+  while (1)
+    {
+      readset = fdset;
+      tv.tv_sec = 1;
+      tv.tv_usec = 0;
+
+      sel = select (FD_SETSIZE, &readset, NULL, NULL, &tv);
+      if (sel == -1)
+        break;
+
+      if ((sel > 0) && (FD_ISSET (out_fd[0], &readset)))
+        {
+          if (read (out_fd[0], &c, 1))
+            {
+              switch (state)
+                {
+                case 0:
+                  if (c == '#')
+                    {
+                      state = 1;
+                      idx = 0;
+                      buffer[idx++] = c;
+                    }
+                  break;
+                case 1:
+                  buffer[idx++] = c;
+                  if ((c == '\n') || (c == '\r'))
+                    {
+                      buffer[idx] = 0;
+                      fprintf (stderr, "%s", buffer);
+                      state = 0;
+                      idx = 0;
+                    }
+                  break;
+                default:
+                  break;
+                }
+            }
+        }
+      else if (stack_trace_done)
+        break;
+    }
+
+  close (in_fd[0]);
+  close (in_fd[1]);
+  close (out_fd[0]);
+  close (out_fd[1]);
+  _exit (0);
+}
+
+static void
+gdb_stack_trace (void)
+{
+  pid_t pid;
+  gchar buf[16];
+  gchar *args[4] = { "gdb", "-p", buf, NULL };
+  int status;
+
+  sprintf (buf, "%u", (guint) getpid ());
+
+  pid = fork ();
+  if (pid == 0)
+    {
+      stack_trace (args);
+      _exit (0);
+    }
+  else if (pid == (pid_t) -1)
+    {
+      perror ("unable to fork gdb");
+      return;
+    }
+
+  waitpid (pid, &status, 0);
+}
+
 void
 cockpit_test_signal_backtrace (int sig)
 {
   void *array[16];
   size_t size;
 
-  // get void*'s for all entries on the stack
+  signal (sig, SIG_DFL);
+
+  /* Try to trace with gdb first */
+  gdb_stack_trace ();
+
+  /* In case above didn't work, print raw stack trace */
   size = backtrace (array, G_N_ELEMENTS (array));
 
-  // print out all the frames to stderr
+  /* print out all the frames to stderr */
   fprintf (stderr, "Error: signal %s:\n", strsignal (sig));
   backtrace_symbols_fd (array, size, STDERR_FILENO);
 
-  signal (sig, SIG_DFL);
   raise (sig);
 }
 
-- 
2.4.3