Merge "Add the recoverable GWP-ASan feature."
diff --git a/libc/Android.bp b/libc/Android.bp
index fb4825d..67da126 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -1444,6 +1444,7 @@
 
     whole_static_libs: [
         "gwp_asan",
+        "gwp_asan_crash_handler",
         "libarm-optimized-routines-string",
         "libasync_safe",
         "libc_bionic_ndk",
@@ -1686,6 +1687,7 @@
         cflags: ["-DLIBC_STATIC"],
         whole_static_libs: [
             "gwp_asan",
+            "gwp_asan_crash_handler",
             "libc_init_static",
             "libc_common_static",
             "libc_unwind_static",
@@ -1695,6 +1697,7 @@
         srcs: [ ":libc_sources_shared" ],
         whole_static_libs: [
             "gwp_asan",
+            "gwp_asan_crash_handler",
             "libc_init_dynamic",
             "libc_common_shared",
             "libunwind-exported",
diff --git a/libc/bionic/gwp_asan_wrappers.cpp b/libc/bionic/gwp_asan_wrappers.cpp
index 3ccaf9b..251633d 100644
--- a/libc/bionic/gwp_asan_wrappers.cpp
+++ b/libc/bionic/gwp_asan_wrappers.cpp
@@ -36,6 +36,7 @@
 #include <string.h>
 #include <sys/types.h>
 
+#include "gwp_asan/crash_handler.h"
 #include "gwp_asan/guarded_pool_allocator.h"
 #include "gwp_asan/options.h"
 #include "gwp_asan_wrappers.h"
@@ -189,6 +190,7 @@
 }
 
 bool GwpAsanInitialized = false;
+bool GwpAsanRecoverable = false;
 
 // The probability (1 / SampleRate) that an allocation gets chosen to be put
 // into the special GWP-ASan pool.
@@ -222,8 +224,32 @@
 static const char* kMaxAllocsTargetedSyspropPrefix = "libc.debug.gwp_asan.max_allocs.";
 static const char* kMaxAllocsEnvVar = "GWP_ASAN_MAX_ALLOCS";
 
+static const char* kRecoverableSystemSysprop = "libc.debug.gwp_asan.recoverable.system_default";
+static const char* kRecoverableAppSysprop = "libc.debug.gwp_asan.recoverable.app_default";
+static const char* kRecoverableTargetedSyspropPrefix = "libc.debug.gwp_asan.recoverable.";
+static const char* kRecoverableEnvVar = "GWP_ASAN_RECOVERABLE";
+
 static const char kPersistPrefix[] = "persist.";
 
+bool NeedsGwpAsanRecovery(void* fault_ptr) {
+  fault_ptr = untag_address(fault_ptr);
+  return GwpAsanInitialized && GwpAsanRecoverable &&
+         __gwp_asan_error_is_mine(GuardedAlloc.getAllocatorState(),
+                                  reinterpret_cast<uintptr_t>(fault_ptr));
+}
+
+void GwpAsanPreCrashHandler(void* fault_ptr) {
+  fault_ptr = untag_address(fault_ptr);
+  if (!NeedsGwpAsanRecovery(fault_ptr)) return;
+  GuardedAlloc.preCrashReport(fault_ptr);
+}
+
+void GwpAsanPostCrashHandler(void* fault_ptr) {
+  fault_ptr = untag_address(fault_ptr);
+  if (!NeedsGwpAsanRecovery(fault_ptr)) return;
+  GuardedAlloc.postCrashReportRecoverableOnly(fault_ptr);
+}
+
 void SetDefaultGwpAsanOptions(Options* options, unsigned* process_sample_rate,
                               const android_mallopt_gwp_asan_options_t& mallopt_options) {
   options->Enabled = true;
@@ -243,11 +269,10 @@
   }
 }
 
-bool GetGwpAsanOption(unsigned long long* result,
-                      const android_mallopt_gwp_asan_options_t& mallopt_options,
-                      const char* system_sysprop, const char* app_sysprop,
-                      const char* targeted_sysprop_prefix, const char* env_var,
-                      const char* descriptive_name) {
+bool GetGwpAsanOptionImpl(char* value_out,
+                          const android_mallopt_gwp_asan_options_t& mallopt_options,
+                          const char* system_sysprop, const char* app_sysprop,
+                          const char* targeted_sysprop_prefix, const char* env_var) {
   const char* basename = "";
   if (mallopt_options.program_name) basename = __gnu_basename(mallopt_options.program_name);
 
@@ -282,17 +307,25 @@
     sysprop_names[3] = persist_default_sysprop;
   }
 
-  char settings_buf[PROP_VALUE_MAX];
-  if (!get_config_from_env_or_sysprops(env_var, sysprop_names, arraysize(sysprop_names),
-                                       settings_buf, PROP_VALUE_MAX)) {
+  return get_config_from_env_or_sysprops(env_var, sysprop_names, arraysize(sysprop_names),
+                                         value_out, PROP_VALUE_MAX);
+}
+
+bool GetGwpAsanIntegerOption(unsigned long long* result,
+                             const android_mallopt_gwp_asan_options_t& mallopt_options,
+                             const char* system_sysprop, const char* app_sysprop,
+                             const char* targeted_sysprop_prefix, const char* env_var,
+                             const char* descriptive_name) {
+  char buffer[PROP_VALUE_MAX];
+  if (!GetGwpAsanOptionImpl(buffer, mallopt_options, system_sysprop, app_sysprop,
+                            targeted_sysprop_prefix, env_var)) {
     return false;
   }
-
   char* end;
-  unsigned long long value = strtoull(settings_buf, &end, 10);
+  unsigned long long value = strtoull(buffer, &end, 10);
   if (value == ULLONG_MAX || *end != '\0') {
     warning_log("Invalid GWP-ASan %s: \"%s\". Using default value instead.", descriptive_name,
-                settings_buf);
+                buffer);
     return false;
   }
 
@@ -300,6 +333,33 @@
   return true;
 }
 
+bool GetGwpAsanBoolOption(bool* result, const android_mallopt_gwp_asan_options_t& mallopt_options,
+                          const char* system_sysprop, const char* app_sysprop,
+                          const char* targeted_sysprop_prefix, const char* env_var,
+                          const char* descriptive_name) {
+  char buffer[PROP_VALUE_MAX] = {};
+  if (!GetGwpAsanOptionImpl(buffer, mallopt_options, system_sysprop, app_sysprop,
+                            targeted_sysprop_prefix, env_var)) {
+    return false;
+  }
+
+  if (strncasecmp(buffer, "1", PROP_VALUE_MAX) == 0 ||
+      strncasecmp(buffer, "true", PROP_VALUE_MAX) == 0) {
+    *result = true;
+    return true;
+  } else if (strncasecmp(buffer, "0", PROP_VALUE_MAX) == 0 ||
+             strncasecmp(buffer, "false", PROP_VALUE_MAX) == 0) {
+    *result = false;
+    return true;
+  }
+
+  warning_log(
+      "Invalid GWP-ASan %s: \"%s\". Using default value \"%s\" instead. Valid values are \"true\", "
+      "\"1\", \"false\", or \"0\".",
+      descriptive_name, buffer, *result ? "true" : "false");
+  return false;
+}
+
 // Initialize the GWP-ASan options structure in *options, taking into account whether someone has
 // asked for specific GWP-ASan settings. The order of priority is:
 //  1. Environment variables.
@@ -314,22 +374,23 @@
   bool had_overrides = false;
 
   unsigned long long buf;
-  if (GetGwpAsanOption(&buf, mallopt_options, kSampleRateSystemSysprop, kSampleRateAppSysprop,
-                       kSampleRateTargetedSyspropPrefix, kSampleRateEnvVar, "sample rate")) {
+  if (GetGwpAsanIntegerOption(&buf, mallopt_options, kSampleRateSystemSysprop,
+                              kSampleRateAppSysprop, kSampleRateTargetedSyspropPrefix,
+                              kSampleRateEnvVar, "sample rate")) {
     options->SampleRate = buf;
     had_overrides = true;
   }
 
-  if (GetGwpAsanOption(&buf, mallopt_options, kProcessSamplingSystemSysprop,
-                       kProcessSamplingAppSysprop, kProcessSamplingTargetedSyspropPrefix,
-                       kProcessSamplingEnvVar, "process sampling rate")) {
+  if (GetGwpAsanIntegerOption(&buf, mallopt_options, kProcessSamplingSystemSysprop,
+                              kProcessSamplingAppSysprop, kProcessSamplingTargetedSyspropPrefix,
+                              kProcessSamplingEnvVar, "process sampling rate")) {
     *process_sample_rate = buf;
     had_overrides = true;
   }
 
-  if (GetGwpAsanOption(&buf, mallopt_options, kMaxAllocsSystemSysprop, kMaxAllocsAppSysprop,
-                       kMaxAllocsTargetedSyspropPrefix, kMaxAllocsEnvVar,
-                       "maximum simultaneous allocations")) {
+  if (GetGwpAsanIntegerOption(&buf, mallopt_options, kMaxAllocsSystemSysprop, kMaxAllocsAppSysprop,
+                              kMaxAllocsTargetedSyspropPrefix, kMaxAllocsEnvVar,
+                              "maximum simultaneous allocations")) {
     options->MaxSimultaneousAllocations = buf;
     had_overrides = true;
   } else if (had_overrides) {
@@ -341,6 +402,16 @@
     options->MaxSimultaneousAllocations =
         /* default */ kDefaultMaxAllocs / frequency_multiplier;
   }
+
+  bool recoverable = false;
+  if (GetGwpAsanBoolOption(&recoverable, mallopt_options, kRecoverableSystemSysprop,
+                           kRecoverableAppSysprop, kRecoverableTargetedSyspropPrefix,
+                           kRecoverableEnvVar, "recoverable")) {
+    options->Recoverable = recoverable;
+    GwpAsanRecoverable = recoverable;
+    had_overrides = true;
+  }
+
   return had_overrides;
 }
 
@@ -400,6 +471,9 @@
 
   __libc_shared_globals()->gwp_asan_state = GuardedAlloc.getAllocatorState();
   __libc_shared_globals()->gwp_asan_metadata = GuardedAlloc.getMetadataRegion();
+  __libc_shared_globals()->debuggerd_needs_gwp_asan_recovery = NeedsGwpAsanRecovery;
+  __libc_shared_globals()->debuggerd_gwp_asan_pre_crash_report = GwpAsanPreCrashHandler;
+  __libc_shared_globals()->debuggerd_gwp_asan_post_crash_report = GwpAsanPostCrashHandler;
 
   return true;
 }
diff --git a/libc/private/bionic_globals.h b/libc/private/bionic_globals.h
index 520e50f..c375cc4 100644
--- a/libc/private/bionic_globals.h
+++ b/libc/private/bionic_globals.h
@@ -107,6 +107,9 @@
 
   const gwp_asan::AllocatorState* gwp_asan_state = nullptr;
   const gwp_asan::AllocationMetadata* gwp_asan_metadata = nullptr;
+  bool (*debuggerd_needs_gwp_asan_recovery)(void* fault_addr) = nullptr;
+  void (*debuggerd_gwp_asan_pre_crash_report)(void* fault_addr) = nullptr;
+  void (*debuggerd_gwp_asan_post_crash_report)(void* fault_addr) = nullptr;
 
   const char* scudo_stack_depot = nullptr;
   const char* scudo_region_info = nullptr;
diff --git a/libdl/libdl.cpp b/libdl/libdl.cpp
index a56a5ab..20f08d9 100644
--- a/libdl/libdl.cpp
+++ b/libdl/libdl.cpp
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
+#include <android/dlext.h>
 #include <dlfcn.h>
 #include <link.h>
+#include <signal.h>
 #include <stdlib.h>
-#include <android/dlext.h>
 
 // These functions are exported by the loader
 // TODO(dimitry): replace these with reference to libc.so
@@ -72,6 +73,9 @@
 __attribute__((__weak__, visibility("default")))
 int __loader_android_get_application_target_sdk_version();
 
+__attribute__((__weak__, visibility("default"))) bool __loader_android_handle_signal(
+    int signal_number, siginfo_t* info, void* context);
+
 // Proxy calls to bionic loader
 __attribute__((__weak__))
 void android_get_LD_LIBRARY_PATH(char* buffer, size_t buffer_size) {
@@ -138,4 +142,14 @@
   return __loader_android_get_application_target_sdk_version();
 }
 
+// Returns true if this function handled the signal, false if the caller should handle the signal
+// itself. This function returns true if the sigchain handler should immediately return, which
+// happens when the signal came from GWP-ASan, and we've dumped a debuggerd report and patched up
+// the GWP-ASan allocator to recover from the fault, and regular execution of the program can
+// continue.
+__attribute__((__weak__)) bool android_handle_signal(int signal_number, siginfo_t* info,
+                                                     void* context) {
+  return __loader_android_handle_signal(signal_number, info, context);
+}
+
 } // extern "C"
diff --git a/libdl/libdl.map.txt b/libdl/libdl.map.txt
index 473bdf2..043ec53 100644
--- a/libdl/libdl.map.txt
+++ b/libdl/libdl.map.txt
@@ -45,4 +45,5 @@
   global:
     android_get_LD_LIBRARY_PATH;
     __cfi_init;
+    android_handle_signal;
 } LIBC_OMR1;
diff --git a/linker/dlfcn.cpp b/linker/dlfcn.cpp
index af05027..a3f5246 100644
--- a/linker/dlfcn.cpp
+++ b/linker/dlfcn.cpp
@@ -28,8 +28,9 @@
 
 #include "linker.h"
 #include "linker_cfi.h"
-#include "linker_globals.h"
+#include "linker_debuggerd.h"
 #include "linker_dlwarning.h"
+#include "linker_globals.h"
 
 #include <link.h>
 #include <pthread.h>
@@ -92,6 +93,8 @@
 #if defined(__arm__)
 _Unwind_Ptr __loader_dl_unwind_find_exidx(_Unwind_Ptr pc, int* pcount) __LINKER_PUBLIC__;
 #endif
+bool __loader_android_handle_signal(int signal_number, siginfo_t* info,
+                                    void* context) __LINKER_PUBLIC__;
 }
 
 static pthread_mutex_t g_dl_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
@@ -302,6 +305,10 @@
   return __libc_shared_globals();
 }
 
+bool __loader_android_handle_signal(int signal_number, siginfo_t* info, void* context) {
+  return debuggerd_handle_signal(signal_number, info, context);
+}
+
 static uint8_t __libdl_info_buf[sizeof(soinfo)] __attribute__((aligned(8)));
 static soinfo* __libdl_info = nullptr;
 
diff --git a/linker/ld_android.cpp b/linker/ld_android.cpp
index 0239c30..1c03106 100644
--- a/linker/ld_android.cpp
+++ b/linker/ld_android.cpp
@@ -44,6 +44,7 @@
 __strong_alias(__loader_android_set_application_target_sdk_version, __internal_linker_error);
 __strong_alias(__loader_android_update_LD_LIBRARY_PATH, __internal_linker_error);
 __strong_alias(__loader_cfi_fail, __internal_linker_error);
+__strong_alias(__loader_android_handle_signal, __internal_linker_error);
 __strong_alias(__loader_dl_iterate_phdr, __internal_linker_error);
 __strong_alias(__loader_dladdr, __internal_linker_error);
 __strong_alias(__loader_dlclose, __internal_linker_error);
diff --git a/linker/linker.arm.map b/linker/linker.arm.map
index be438ca..b805cd6 100644
--- a/linker/linker.arm.map
+++ b/linker/linker.arm.map
@@ -24,6 +24,7 @@
     __loader_remove_thread_local_dtor;
     __loader_shared_globals;
     rtld_db_dlactivity;
+    __loader_android_handle_signal;
   local:
     *;
 };
diff --git a/linker/linker.generic.map b/linker/linker.generic.map
index f3c01c0..4d7f236 100644
--- a/linker/linker.generic.map
+++ b/linker/linker.generic.map
@@ -23,6 +23,7 @@
     __loader_remove_thread_local_dtor;
     __loader_shared_globals;
     rtld_db_dlactivity;
+    __loader_android_handle_signal;
   local:
     *;
 };
diff --git a/linker/linker_debuggerd.h b/linker/linker_debuggerd.h
index d701879..95f99e7 100644
--- a/linker/linker_debuggerd.h
+++ b/linker/linker_debuggerd.h
@@ -28,4 +28,7 @@
 
 #pragma once
 
+#include <signal.h>
+
 void linker_debuggerd_init();
+extern "C" bool debuggerd_handle_signal(int signal_number, siginfo_t* info, void* context);
diff --git a/linker/linker_debuggerd_android.cpp b/linker/linker_debuggerd_android.cpp
index 3d64628..ab6fc30 100644
--- a/linker/linker_debuggerd_android.cpp
+++ b/linker/linker_debuggerd_android.cpp
@@ -46,6 +46,17 @@
       .scudo_ring_buffer_size = __libc_shared_globals()->scudo_ring_buffer_size,
   };
 }
+
+static gwp_asan_callbacks_t get_gwp_asan_callbacks() {
+  return {
+      .debuggerd_needs_gwp_asan_recovery =
+          __libc_shared_globals()->debuggerd_needs_gwp_asan_recovery,
+      .debuggerd_gwp_asan_pre_crash_report =
+          __libc_shared_globals()->debuggerd_gwp_asan_pre_crash_report,
+      .debuggerd_gwp_asan_post_crash_report =
+          __libc_shared_globals()->debuggerd_gwp_asan_post_crash_report,
+  };
+}
 #endif
 
 void linker_debuggerd_init() {
@@ -53,9 +64,10 @@
   // so don't pass in any process info from the bootstrap linker.
   debuggerd_callbacks_t callbacks = {
 #if defined(__ANDROID_APEX__)
-      .get_process_info = get_process_info,
+    .get_process_info = get_process_info,
+    .get_gwp_asan_callbacks = get_gwp_asan_callbacks,
 #endif
-      .post_dump = notify_gdb_of_libraries,
+    .post_dump = notify_gdb_of_libraries,
   };
   debuggerd_init(&callbacks);
 }
diff --git a/linker/linker_debuggerd_stub.cpp b/linker/linker_debuggerd_stub.cpp
index 631e6e4..c671dd9 100644
--- a/linker/linker_debuggerd_stub.cpp
+++ b/linker/linker_debuggerd_stub.cpp
@@ -28,5 +28,11 @@
 
 #include "linker_debuggerd.h"
 
+#include <signal.h>
+
 void linker_debuggerd_init() {
 }
+extern "C" bool debuggerd_handle_signal(int /* signal_number */, siginfo_t* /* info */,
+                                        void* /* context */) {
+  return false;
+}