Test flags before using "info"

Our debugger signal catcher expects to receive three args, but if
somebody cleared SA_SIGINFO we only get one, and bad things happen
when we try to use the second one.  Test to see if SA_SIGINFO is
still set before we try to use the argument.

(cherry-pick of f84bc8d6f6368f1c846124a8168761ee8cc589c0.)

Bug: 7272866
Change-Id: I69a65c25e833aea70acb78f9ba40ed93308583e6
diff --git a/linker/debugger.c b/linker/debugger.c
index dde66f3..e4d4ae9 100644
--- a/linker/debugger.c
+++ b/linker/debugger.c
@@ -29,14 +29,10 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
-#include <ctype.h>
+#include <stdbool.h>
 #include <signal.h>
-#include <sys/mman.h>
 #include <sys/prctl.h>
 #include <errno.h>
-
-#include "linker.h"
-
 #include <sys/socket.h>
 #include <sys/un.h>
 
@@ -113,11 +109,15 @@
 #include <../libc/private/logd.h>
 
 /*
- * Writes a summary of the signal to the log file.
+ * Writes a summary of the signal to the log file.  We do this so that, if
+ * for some reason we're not able to contact debuggerd, there is still some
+ * indication of the failure in the log.
  *
  * We could be here as a result of native heap corruption, or while a
  * mutex is being held, so we don't want to use any libc functions that
  * could allocate memory or hold a lock.
+ *
+ * "info" will be NULL if the siginfo_t information was not available.
  */
 static void logSignalSummary(int signum, const siginfo_t* info)
 {
@@ -145,14 +145,46 @@
         // implies that 16 byte names are not.
         threadname[MAX_TASK_NAME_LEN] = 0;
     }
-    format_buffer(buffer, sizeof(buffer),
-        "Fatal signal %d (%s) at 0x%08x (code=%d), thread %d (%s)",
-        signum, signame, info->si_addr, info->si_code, gettid(), threadname);
+    if (info != NULL) {
+        format_buffer(buffer, sizeof(buffer),
+            "Fatal signal %d (%s) at 0x%08x (code=%d), thread %d (%s)",
+            signum, signame, info->si_addr, info->si_code, gettid(), threadname);
+    } else {
+        format_buffer(buffer, sizeof(buffer),
+            "Fatal signal %d (%s), thread %d (%s)",
+            signum, signame, gettid(), threadname);
+    }
 
     __libc_android_log_write(ANDROID_LOG_FATAL, "libc", buffer);
 }
 
 /*
+ * Returns true if the handler for signal "signum" has SA_SIGINFO set.
+ */
+static bool haveSiginfo(int signum)
+{
+    struct sigaction oldact, newact;
+
+    memset(&newact, 0, sizeof(newact));
+    newact.sa_handler = SIG_DFL;
+    newact.sa_flags = SA_RESTART;
+    sigemptyset(&newact.sa_mask);
+
+    if (sigaction(signum, &newact, &oldact) < 0) {
+        __libc_android_log_write(ANDROID_LOG_FATAL, "libc",
+            "Failed testing for SA_SIGINFO");
+        return 0;
+    }
+    bool ret = (oldact.sa_flags & SA_SIGINFO) != 0;
+
+    if (sigaction(signum, &oldact, NULL) < 0) {
+        __libc_android_log_write(ANDROID_LOG_FATAL, "libc",
+            "Restore failed in test for SA_SIGINFO");
+    }
+    return ret;
+}
+
+/*
  * Catches fatal signals so we can ask debuggerd to ptrace us before
  * we crash.
  */
@@ -162,6 +194,14 @@
     unsigned tid;
     int s;
 
+    /*
+     * It's possible somebody cleared the SA_SIGINFO flag, which would mean
+     * our "info" arg holds an undefined value.
+     */
+    if (!haveSiginfo(n)) {
+        info = NULL;
+    }
+
     logSignalSummary(n, info);
 
     tid = gettid();