[GWP-ASan] Provide runtime configuration through an env var + sysprop.

This patch introduces GWP-ASan system properties and environment
variables to control the internal sampling rates of GWP-ASan. This can
be used for:

 1. "Torture testing" the system, i.e. running it under an extremely
    high sampling rate under GWP-ASan.
 2. Increasing sampling remotely to allow further crash report
    collection of rare issues.

There are three sets of system properites:
 1. libc.debug.gwp_asan.*.system_default: Default values for native
    executables and system apps.
 2. libc.debug.gwp_asan.*.app_default: Default values for non-system
    apps, and
 3. libc.debug.gwp_asan.*.<basename/app_name>: Default values for an
    individual app or native process.

There are three variables that can be changed:
 1. The allocation sampling rate (default: 2500) - using the environment
    variable GWP_ASAN_SAMPLE_RATE or the libc.debug.gwp_asan.sample_rate.*
    system property.
 2. The process sampling rate (default: 128 for system apps/processes, 1
    for opted-in apps) - using the environment variable
    GWP_ASAN_PROCESS_SAMPLING or the libc.debug.gwp_asan.process_sampling.*
    system property,
 3. The number of slots available (default: 32) - using the environment
    variable GWP_ASAN_MAX_ALLOCS or the libc.debug.gwp_asan.max_allocs.*
    system property.

If not specified, #3 will be calculated as a ratio of the default
|2500 SampleRate : 32 slots|. So, a sample rate of "1250" (i.e. twice as
frequent sampling) will result in a doubling of the max_allocs to "64".

Bug: 219651032
Test: atest bionic-unit-tests
Change-Id: Idb40a2a4d074e01ce3c4e635ad639a91a32d570f
diff --git a/libc/Android.bp b/libc/Android.bp
index 7175c77..7abd6b0 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -28,6 +28,7 @@
     "bionic/isatty.c",
     "bionic/sched_cpualloc.c",
     "bionic/sched_cpucount.c",
+    "bionic/sysprop_helpers.cpp",
     "stdio/fmemopen.cpp",
     "stdio/parsefloat.c",
     "stdio/refill.c",
diff --git a/libc/bionic/gwp_asan_wrappers.cpp b/libc/bionic/gwp_asan_wrappers.cpp
index 8c51347..2f513e5 100644
--- a/libc/bionic/gwp_asan_wrappers.cpp
+++ b/libc/bionic/gwp_asan_wrappers.cpp
@@ -26,21 +26,27 @@
  * SUCH DAMAGE.
  */
 
-#include <platform/bionic/android_unsafe_frame_pointer_chase.h>
-#include <platform/bionic/malloc.h>
-#include <private/bionic_arc4random.h>
-#include <private/bionic_globals.h>
-#include <private/bionic_malloc_dispatch.h>
+#include <alloca.h>
+#include <assert.h>
+#include <ctype.h>
 #include <stddef.h>
 #include <stdint.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
 
-#include "bionic/gwp_asan_wrappers.h"
 #include "gwp_asan/guarded_pool_allocator.h"
 #include "gwp_asan/options.h"
+#include "gwp_asan_wrappers.h"
 #include "malloc_common.h"
+#include "platform/bionic/android_unsafe_frame_pointer_chase.h"
+#include "platform/bionic/malloc.h"
+#include "private/bionic_arc4random.h"
+#include "private/bionic_globals.h"
+#include "private/bionic_malloc_dispatch.h"
+#include "sys/system_properties.h"
+#include "sysprop_helpers.h"
 
 #ifndef LIBC_STATIC
 #include "bionic/malloc_common_dynamic.h"
@@ -49,59 +55,14 @@
 static gwp_asan::GuardedPoolAllocator GuardedAlloc;
 static const MallocDispatch* prev_dispatch;
 
+using Action = android_mallopt_gwp_asan_options_t::Action;
 using Options = gwp_asan::options::Options;
 
-// ============================================================================
-// Implementation of gFunctions.
-// ============================================================================
+// basename() is a mess, see the manpage. Let's be explicit what handling we
+// want (don't touch my string!).
+extern "C" const char* __gnu_basename(const char* path);
 
-// This function handles initialisation as asked for by MallocInitImpl. This
-// should always be called in a single-threaded context.
-bool gwp_asan_initialize(const MallocDispatch* dispatch, bool*, const char*) {
-  prev_dispatch = dispatch;
-
-  Options Opts;
-  Opts.Enabled = true;
-  Opts.MaxSimultaneousAllocations = 32;
-  Opts.SampleRate = 2500;
-  Opts.InstallSignalHandlers = false;
-  Opts.InstallForkHandlers = true;
-  Opts.Backtrace = android_unsafe_frame_pointer_chase;
-
-  GuardedAlloc.init(Opts);
-  // TODO(b/149790891): The log line below causes ART tests to fail as they're
-  // not expecting any output. Disable the output for now.
-  // info_log("GWP-ASan has been enabled.");
-
-  __libc_shared_globals()->gwp_asan_state = GuardedAlloc.getAllocatorState();
-  __libc_shared_globals()->gwp_asan_metadata = GuardedAlloc.getMetadataRegion();
-  return true;
-}
-
-void gwp_asan_finalize() {
-}
-
-void gwp_asan_get_malloc_leak_info(uint8_t**, size_t*, size_t*, size_t*, size_t*) {
-}
-
-void gwp_asan_free_malloc_leak_info(uint8_t*) {
-}
-
-ssize_t gwp_asan_malloc_backtrace(void*, uintptr_t*, size_t) {
-  // TODO(mitchp): GWP-ASan might be able to return the backtrace for the
-  // provided address.
-  return -1;
-}
-
-bool gwp_asan_write_malloc_leak_info(FILE*) {
-  return false;
-}
-
-void* gwp_asan_gfunctions[] = {
-  (void*)gwp_asan_initialize,           (void*)gwp_asan_finalize,
-  (void*)gwp_asan_get_malloc_leak_info, (void*)gwp_asan_free_malloc_leak_info,
-  (void*)gwp_asan_malloc_backtrace,     (void*)gwp_asan_write_malloc_leak_info,
-};
+namespace {
 
 // ============================================================================
 // Implementation of GWP-ASan malloc wrappers.
@@ -175,66 +136,202 @@
   prev_dispatch->malloc_enable();
 }
 
-static const MallocDispatch gwp_asan_dispatch __attribute__((unused)) = {
-  gwp_asan_calloc,
-  gwp_asan_free,
-  Malloc(mallinfo),
-  gwp_asan_malloc,
-  gwp_asan_malloc_usable_size,
-  Malloc(memalign),
-  Malloc(posix_memalign),
+const MallocDispatch gwp_asan_dispatch __attribute__((unused)) = {
+    gwp_asan_calloc,
+    gwp_asan_free,
+    Malloc(mallinfo),
+    gwp_asan_malloc,
+    gwp_asan_malloc_usable_size,
+    Malloc(memalign),
+    Malloc(posix_memalign),
 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
-  Malloc(pvalloc),
+    Malloc(pvalloc),
 #endif
-  gwp_asan_realloc,
+    gwp_asan_realloc,
 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
-  Malloc(valloc),
+    Malloc(valloc),
 #endif
-  gwp_asan_malloc_iterate,
-  gwp_asan_malloc_disable,
-  gwp_asan_malloc_enable,
-  Malloc(mallopt),
-  Malloc(aligned_alloc),
-  Malloc(malloc_info),
+    gwp_asan_malloc_iterate,
+    gwp_asan_malloc_disable,
+    gwp_asan_malloc_enable,
+    Malloc(mallopt),
+    Malloc(aligned_alloc),
+    Malloc(malloc_info),
 };
 
-// The probability (1 / kProcessSampleRate) that a process will be ranodmly
-// selected for sampling. kProcessSampleRate should always be a power of two to
-// avoid modulo bias.
-static constexpr uint8_t kProcessSampleRate = 128;
+bool isPowerOfTwo(uint64_t x) {
+  assert(x != 0);
+  return (x & (x - 1)) == 0;
+}
 
-bool ShouldGwpAsanSampleProcess() {
+bool ShouldGwpAsanSampleProcess(unsigned sample_rate) {
+  if (!isPowerOfTwo(sample_rate)) {
+    warning_log(
+        "GWP-ASan process sampling rate of %u is not a power-of-two, and so modulo bias occurs.",
+        sample_rate);
+  }
+
   uint8_t random_number;
   __libc_safe_arc4random_buf(&random_number, sizeof(random_number));
-  return random_number % kProcessSampleRate == 0;
+  return random_number % sample_rate == 0;
 }
 
-bool MaybeInitGwpAsanFromLibc(libc_globals* globals) {
-  // Never initialize the Zygote here. A Zygote chosen for sampling would also
-  // have all of its children sampled. Instead, the Zygote child will choose
-  // whether it samples or not just after the Zygote forks. For
-  // libc_scudo-preloaded executables (like mediaswcodec), the program name
-  // might not be available yet. The zygote never uses dynamic libc_scudo.
-  const char* progname = getprogname();
-  if (progname && strncmp(progname, "app_process", 11) == 0) {
+bool GwpAsanInitialized = false;
+
+// The probability (1 / SampleRate) that an allocation gets chosen to be put
+// into the special GWP-ASan pool.
+using SampleRate_t = typeof(gwp_asan::options::Options::SampleRate);
+constexpr SampleRate_t kDefaultSampleRate = 2500;
+static const char* kSampleRateSystemSysprop = "libc.debug.gwp_asan.sample_rate.system_default";
+static const char* kSampleRateAppSysprop = "libc.debug.gwp_asan.sample_rate.app_default";
+static const char* kSampleRateTargetedSyspropPrefix = "libc.debug.gwp_asan.sample_rate.";
+static const char* kSampleRateEnvVar = "GWP_ASAN_SAMPLE_RATE";
+
+// The probability (1 / ProcessSampling) that a process will be randomly
+// selected for sampling, for system apps and system processes. The process
+// sampling rate should always be a power of two to avoid modulo bias.
+constexpr unsigned kDefaultProcessSampling = 128;
+static const char* kProcessSamplingSystemSysprop =
+    "libc.debug.gwp_asan.process_sampling.system_default";
+static const char* kProcessSamplingAppSysprop = "libc.debug.gwp_asan.process_sampling.app_default";
+static const char* kProcessSamplingTargetedSyspropPrefix = "libc.debug.gwp_asan.process_sampling.";
+static const char* kProcessSamplingEnvVar = "GWP_ASAN_PROCESS_SAMPLING";
+
+// The upper limit of simultaneous allocations supported by GWP-ASan. Any
+// allocations in excess of this limit will be passed to the backing allocator
+// and can't be sampled. This value, if unspecified, will be automatically
+// calculated to keep the same ratio as the default (2500 sampling : 32 allocs).
+// So, if you specify GWP_ASAN_SAMPLE_RATE=1250 (i.e. twice as frequent), we'll
+// automatically calculate that we need double the slots (64).
+using SimultaneousAllocations_t = typeof(gwp_asan::options::Options::MaxSimultaneousAllocations);
+constexpr SimultaneousAllocations_t kDefaultMaxAllocs = 32;
+static const char* kMaxAllocsSystemSysprop = "libc.debug.gwp_asan.max_allocs.system_default";
+static const char* kMaxAllocsAppSysprop = "libc.debug.gwp_asan.max_allocs.app_default";
+static const char* kMaxAllocsTargetedSyspropPrefix = "libc.debug.gwp_asan.max_allocs.";
+static const char* kMaxAllocsEnvVar = "GWP_ASAN_MAX_ALLOCS";
+
+void SetDefaultGwpAsanOptions(Options* options, unsigned* process_sample_rate,
+                              const android_mallopt_gwp_asan_options_t& mallopt_options) {
+  options->Enabled = true;
+  options->InstallSignalHandlers = false;
+  options->InstallForkHandlers = true;
+  options->Backtrace = android_unsafe_frame_pointer_chase;
+  options->SampleRate = kDefaultSampleRate;
+  options->MaxSimultaneousAllocations = kDefaultMaxAllocs;
+
+  *process_sample_rate = 1;
+  if (mallopt_options.desire == Action::TURN_ON_WITH_SAMPLING) {
+    *process_sample_rate = kDefaultProcessSampling;
+  }
+}
+
+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) {
+  const char* basename = "";
+  if (mallopt_options.program_name) basename = __gnu_basename(mallopt_options.program_name);
+
+  size_t program_specific_sysprop_size = strlen(targeted_sysprop_prefix) + strlen(basename) + 1;
+  char* program_specific_sysprop_name = static_cast<char*>(alloca(program_specific_sysprop_size));
+  async_safe_format_buffer(program_specific_sysprop_name, program_specific_sysprop_size, "%s%s",
+                           targeted_sysprop_prefix, basename);
+
+  const char* sysprop_names[2] = {nullptr, nullptr};
+  // Tests use a blank program name to specify that system properties should not
+  // be used. Tests still continue to use the environment variable though.
+  if (*basename != '\0') {
+    sysprop_names[0] = program_specific_sysprop_name;
+    if (mallopt_options.desire == Action::TURN_ON_FOR_APP) {
+      sysprop_names[1] = app_sysprop;
+    } else {
+      sysprop_names[1] = system_sysprop;
+    }
+  }
+
+  char settings_buf[PROP_VALUE_MAX];
+  if (!get_config_from_env_or_sysprops(env_var, sysprop_names,
+                                       /* sys_prop_names_size */ 2, settings_buf, PROP_VALUE_MAX)) {
     return false;
   }
-  return MaybeInitGwpAsan(globals);
+
+  char* end;
+  unsigned long long value = strtoull(settings_buf, &end, 10);
+  if (value == ULLONG_MAX || *end != '\0') {
+    warning_log("Invalid GWP-ASan %s: \"%s\". Using default value instead.", descriptive_name,
+                settings_buf);
+    return false;
+  }
+
+  *result = value;
+  return true;
 }
 
-static bool GwpAsanInitialized = 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.
+//  2. Process-specific system properties.
+//  3. Global system properties.
+// If any of these overrides are found, we return true. Otherwise, use the default values, and
+// return false.
+bool GetGwpAsanOptions(Options* options, unsigned* process_sample_rate,
+                       const android_mallopt_gwp_asan_options_t& mallopt_options) {
+  SetDefaultGwpAsanOptions(options, process_sample_rate, mallopt_options);
 
-// Maybe initializes GWP-ASan. Called by android_mallopt() and libc's
-// initialisation. This should always be called in a single-threaded context.
-bool MaybeInitGwpAsan(libc_globals* globals, bool force_init) {
+  bool had_overrides = false;
+
+  unsigned long long buf;
+  if (GetGwpAsanOption(&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")) {
+    *process_sample_rate = buf;
+    had_overrides = true;
+  }
+
+  if (GetGwpAsanOption(&buf, mallopt_options, kMaxAllocsSystemSysprop, kMaxAllocsAppSysprop,
+                       kMaxAllocsTargetedSyspropPrefix, kMaxAllocsEnvVar,
+                       "maximum simultaneous allocations")) {
+    options->MaxSimultaneousAllocations = buf;
+    had_overrides = true;
+  } else if (had_overrides) {
+    // Multiply the number of slots available, such that the ratio between
+    // sampling rate and slots is kept the same as the default. For example, a
+    // sampling rate of 1000 is 2.5x more frequent than default, and so
+    // requires 80 slots (32 * 2.5).
+    float frequency_multiplier = static_cast<float>(options->SampleRate) / kDefaultSampleRate;
+    options->MaxSimultaneousAllocations =
+        /* default */ kDefaultMaxAllocs / frequency_multiplier;
+  }
+  return had_overrides;
+}
+
+bool MaybeInitGwpAsan(libc_globals* globals,
+                      const android_mallopt_gwp_asan_options_t& mallopt_options) {
   if (GwpAsanInitialized) {
     error_log("GWP-ASan was already initialized for this process.");
     return false;
   }
 
-  // If the caller hasn't forced GWP-ASan on, check whether we should sample
-  // this process.
-  if (!force_init && !ShouldGwpAsanSampleProcess()) {
+  Options options;
+  unsigned process_sample_rate = kDefaultProcessSampling;
+  if (!GetGwpAsanOptions(&options, &process_sample_rate, mallopt_options) &&
+      mallopt_options.desire == Action::DONT_TURN_ON_UNLESS_OVERRIDDEN) {
+    return false;
+  }
+
+  if (options.SampleRate == 0 || process_sample_rate == 0 ||
+      options.MaxSimultaneousAllocations == 0) {
+    return false;
+  }
+
+  if (!ShouldGwpAsanSampleProcess(process_sample_rate)) {
     return false;
   }
 
@@ -263,28 +360,48 @@
     atomic_store(&globals->current_dispatch_table, &gwp_asan_dispatch);
   }
 
-#ifndef LIBC_STATIC
-  SetGlobalFunctions(gwp_asan_gfunctions);
-#endif  // LIBC_STATIC
-
   GwpAsanInitialized = true;
 
-  gwp_asan_initialize(NativeAllocatorDispatch(), nullptr, nullptr);
+  prev_dispatch = NativeAllocatorDispatch();
+
+  GuardedAlloc.init(options);
+
+  __libc_shared_globals()->gwp_asan_state = GuardedAlloc.getAllocatorState();
+  __libc_shared_globals()->gwp_asan_metadata = GuardedAlloc.getMetadataRegion();
 
   return true;
 }
+};  // anonymous namespace
+
+bool MaybeInitGwpAsanFromLibc(libc_globals* globals) {
+  // Never initialize the Zygote here. A Zygote chosen for sampling would also
+  // have all of its children sampled. Instead, the Zygote child will choose
+  // whether it samples or not just after the Zygote forks. Note that the Zygote
+  // changes its name after it's started, at this point it's still called
+  // "app_process" or "app_process64".
+  static const char kAppProcessNamePrefix[] = "app_process";
+  const char* progname = getprogname();
+  if (strncmp(progname, kAppProcessNamePrefix, sizeof(kAppProcessNamePrefix) - 1) == 0)
+    return false;
+
+  android_mallopt_gwp_asan_options_t mallopt_options;
+  mallopt_options.program_name = progname;
+  mallopt_options.desire = Action::TURN_ON_WITH_SAMPLING;
+
+  return MaybeInitGwpAsan(globals, mallopt_options);
+}
 
 bool DispatchIsGwpAsan(const MallocDispatch* dispatch) {
   return dispatch == &gwp_asan_dispatch;
 }
 
-bool EnableGwpAsan(bool force_init) {
+bool EnableGwpAsan(const android_mallopt_gwp_asan_options_t& options) {
   if (GwpAsanInitialized) {
     return true;
   }
 
   bool ret_value;
   __libc_globals.mutate(
-      [&](libc_globals* globals) { ret_value = MaybeInitGwpAsan(globals, force_init); });
+      [&](libc_globals* globals) { ret_value = MaybeInitGwpAsan(globals, options); });
   return ret_value;
 }
diff --git a/libc/bionic/gwp_asan_wrappers.h b/libc/bionic/gwp_asan_wrappers.h
index c568681..219da9f 100644
--- a/libc/bionic/gwp_asan_wrappers.h
+++ b/libc/bionic/gwp_asan_wrappers.h
@@ -28,19 +28,20 @@
 
 #pragma once
 
-#include <private/bionic_globals.h>
-#include <private/bionic_malloc_dispatch.h>
 #include <stddef.h>
 
-// Enable GWP-ASan, used by android_mallopt.
-bool EnableGwpAsan(bool force_init);
+#include "gwp_asan/options.h"
+#include "platform/bionic/malloc.h"
+#include "private/bionic_globals.h"
+#include "private/bionic_malloc_dispatch.h"
+
+// Enable GWP-ASan, used by android_mallopt. Should always be called in a
+// single-threaded context.
+bool EnableGwpAsan(const android_mallopt_gwp_asan_options_t& options);
 
 // Hooks for libc to possibly install GWP-ASan.
 bool MaybeInitGwpAsanFromLibc(libc_globals* globals);
 
-// Maybe initialize GWP-ASan. Set force_init to true to bypass process sampling.
-bool MaybeInitGwpAsan(libc_globals* globals, bool force_init = false);
-
 // Returns whether GWP-ASan is the provided dispatch table pointer. Used in
 // heapprofd's signal-initialization sequence to determine the intermediate
 // dispatch pointer to use when initing.
diff --git a/libc/bionic/libc_init_static.cpp b/libc/bionic/libc_init_static.cpp
index 815b938..575da62 100644
--- a/libc/bionic/libc_init_static.cpp
+++ b/libc/bionic/libc_init_static.cpp
@@ -38,6 +38,7 @@
 
 #include "libc_init_common.h"
 #include "pthread_internal.h"
+#include "sysprop_helpers.h"
 
 #include "platform/bionic/macros.h"
 #include "platform/bionic/mte.h"
@@ -164,30 +165,6 @@
   layout.finish_layout();
 }
 
-// Get the presiding config string, in the following order of priority:
-//   1. Environment variables.
-//   2. System properties, in the order they're specified in sys_prop_names.
-// If neither of these options are specified, this function returns false.
-// Otherwise, it returns true, and the presiding options string is written to
-// the `options` buffer of size `size`. If this function returns true, `options`
-// is guaranteed to be null-terminated. `options_size` should be at least
-// PROP_VALUE_MAX.
-bool get_config_from_env_or_sysprops(const char* env_var_name, const char* const* sys_prop_names,
-                                     size_t sys_prop_names_size, char* options,
-                                     size_t options_size) {
-  const char* env = getenv(env_var_name);
-  if (env && *env != '\0') {
-    strncpy(options, env, options_size);
-    options[options_size - 1] = '\0'; // Ensure null-termination.
-    return true;
-  }
-
-  for (size_t i = 0; i < sys_prop_names_size; ++i) {
-    if (__system_property_get(sys_prop_names[i], options) && *options != '\0') return true;
-  }
-  return false;
-}
-
 #ifdef __aarch64__
 static bool __read_memtag_note(const ElfW(Nhdr)* note, const char* name, const char* desc,
                                unsigned* result) {
diff --git a/libc/bionic/malloc_common.cpp b/libc/bionic/malloc_common.cpp
index 38168ee..9744968 100644
--- a/libc/bionic/malloc_common.cpp
+++ b/libc/bionic/malloc_common.cpp
@@ -326,12 +326,12 @@
     return LimitEnable(arg, arg_size);
   }
   if (opcode == M_INITIALIZE_GWP_ASAN) {
-    if (arg == nullptr || arg_size != sizeof(bool)) {
+    if (arg == nullptr || arg_size != sizeof(android_mallopt_gwp_asan_options_t)) {
       errno = EINVAL;
       return false;
     }
 
-    return EnableGwpAsan(*reinterpret_cast<bool*>(arg));
+    return EnableGwpAsan(*reinterpret_cast<android_mallopt_gwp_asan_options_t*>(arg));
   }
   errno = ENOTSUP;
   return false;
diff --git a/libc/bionic/malloc_common_dynamic.cpp b/libc/bionic/malloc_common_dynamic.cpp
index 1f58fda..6c2f4d9 100644
--- a/libc/bionic/malloc_common_dynamic.cpp
+++ b/libc/bionic/malloc_common_dynamic.cpp
@@ -526,12 +526,12 @@
     return FreeMallocLeakInfo(reinterpret_cast<android_mallopt_leak_info_t*>(arg));
   }
   if (opcode == M_INITIALIZE_GWP_ASAN) {
-    if (arg == nullptr || arg_size != sizeof(bool)) {
+    if (arg == nullptr || arg_size != sizeof(android_mallopt_gwp_asan_options_t)) {
       errno = EINVAL;
       return false;
     }
 
-    return EnableGwpAsan(*reinterpret_cast<bool*>(arg));
+    return EnableGwpAsan(*reinterpret_cast<android_mallopt_gwp_asan_options_t*>(arg));
   }
   // Try heapprofd's mallopt, as it handles options not covered here.
   return HeapprofdMallopt(opcode, arg, arg_size);
diff --git a/libc/bionic/sysprop_helpers.cpp b/libc/bionic/sysprop_helpers.cpp
new file mode 100644
index 0000000..edae6cc
--- /dev/null
+++ b/libc/bionic/sysprop_helpers.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "sysprop_helpers.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include "sys/system_properties.h"
+
+static bool get_property_value(const char* property_name, char* dest, size_t dest_size) {
+  assert(property_name && dest && dest_size != 0);
+  const prop_info* prop = __system_property_find(property_name);
+  if (!prop) return false;
+
+  struct PropCbCookie {
+    char* dest;
+    size_t size;
+  };
+  *dest = '\0';
+  PropCbCookie cb_cookie = {dest, dest_size};
+
+  __system_property_read_callback(
+      prop,
+      [](void* cookie, const char* /* name */, const char* value, uint32_t /* serial */) {
+        auto* cb_cookie = reinterpret_cast<PropCbCookie*>(cookie);
+        strncpy(cb_cookie->dest, value, cb_cookie->size);
+      },
+      &cb_cookie);
+  if (*dest != '\0' && *dest != '0') return true;
+
+  return false;
+}
+
+bool get_config_from_env_or_sysprops(const char* env_var_name, const char* const* sys_prop_names,
+                                     size_t sys_prop_names_size, char* options,
+                                     size_t options_size) {
+  const char* env = getenv(env_var_name);
+  if (env && *env != '\0') {
+    strncpy(options, env, options_size);
+    options[options_size - 1] = '\0';  // Ensure null-termination.
+    return true;
+  }
+
+  for (size_t i = 0; i < sys_prop_names_size; ++i) {
+    if (sys_prop_names[i] == nullptr) continue;
+    if (get_property_value(sys_prop_names[i], options, options_size)) return true;
+  }
+  return false;
+}
diff --git a/libc/bionic/sysprop_helpers.h b/libc/bionic/sysprop_helpers.h
new file mode 100644
index 0000000..a02c2dc
--- /dev/null
+++ b/libc/bionic/sysprop_helpers.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <sys/cdefs.h>
+
+// Get the presiding config string, in the following order of priority:
+//   1. Environment variables.
+//   2. System properties, in the order they're specified in sys_prop_names.
+// If neither of these options are specified (or they're both an empty string),
+// this function returns false. Otherwise, it returns true, and the presiding
+// options string is written to the `options` buffer of size `size`. If this
+// function returns true, `options` is guaranteed to be null-terminated.
+// `options_size` should be at least PROP_VALUE_MAX.
+__LIBC_HIDDEN__ bool get_config_from_env_or_sysprops(const char* env_var_name,
+                                                     const char* const* sys_prop_names,
+                                                     size_t sys_prop_names_size, char* options,
+                                                     size_t options_size);
diff --git a/libc/platform/bionic/malloc.h b/libc/platform/bionic/malloc.h
index b56ca74..f0f13d0 100644
--- a/libc/platform/bionic/malloc.h
+++ b/libc/platform/bionic/malloc.h
@@ -96,12 +96,42 @@
   // otherwise this mallopt() will internally decide whether to sample the
   // process. The program must be single threaded at the point when the
   // android_mallopt function is called.
-  //   arg = bool*
-  //   arg_size = sizeof(bool)
+  //   arg = android_mallopt_gwp_asan_options_t*
+  //   arg_size = sizeof(android_mallopt_gwp_asan_options_t)
   M_INITIALIZE_GWP_ASAN = 10,
 #define M_INITIALIZE_GWP_ASAN M_INITIALIZE_GWP_ASAN
 };
 
+typedef struct {
+  // The null-terminated name that the zygote is spawning. Because native
+  // SpecializeCommon (where the GWP-ASan mallopt() is called from) happens
+  // before argv[0] is set, we need the zygote to tell us the new app name.
+  const char* program_name = nullptr;
+
+  // An android_mallopt(M_INITIALIZE_GWP_ASAN) is always issued on process
+  // startup and app startup, regardless of whether GWP-ASan is desired or not.
+  // This allows the process/app's desire to be overwritten by the
+  // "libc.debug.gwp_asan.*.app_default" or "libc.debug.gwp_asan.*.<name>"
+  // system properties, as well as the "GWP_ASAN_*" environment variables.
+  //
+  // Worth noting, the "libc.debug.gwp_asan.*.app_default" sysprops *do not*
+  // apply to system apps. They use the "libc.debug.gwp_asan.*.system_default"
+  // sysprops.
+  enum Action {
+    // The app has opted-in to GWP-ASan, and should always have it enabled. This
+    // should only be used by apps.
+    TURN_ON_FOR_APP,
+    // System processes apps have GWP-ASan enabled by default, but use the
+    // process sampling method.
+    TURN_ON_WITH_SAMPLING,
+    // Non-system apps don't have GWP-ASan by default.
+    DONT_TURN_ON_UNLESS_OVERRIDDEN,
+    // Note: GWP-ASan cannot be disabled once it's been enabled.
+  };
+
+  Action desire = DONT_TURN_ON_UNLESS_OVERRIDDEN;
+} android_mallopt_gwp_asan_options_t;
+
 // Manipulates bionic-specific handling of memory allocation APIs such as
 // malloc. Only for use by the Android platform itself.
 //
diff --git a/tests/Android.bp b/tests/Android.bp
index 3061142..ee49e65 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -62,7 +62,10 @@
         // For glibc.
         "-D__STDC_LIMIT_MACROS",
     ],
-    header_libs: ["libcutils_headers"],
+    header_libs: [
+        "libcutils_headers",
+        "gwp_asan_headers"
+    ],
     // Ensure that the tests exercise shadow call stack support and
     // the hint space PAC/BTI instructions.
     arch: {
@@ -571,6 +574,18 @@
     ],
 }
 
+cc_test_library {
+    name: "libBionicGwpAsanTests",
+    defaults: ["bionic_tests_defaults"],
+    srcs: [
+        "gwp_asan_test.cpp",
+    ],
+    include_dirs: [
+        "bionic/libc",
+    ],
+    static_libs: ["libbase"],
+}
+
 // -----------------------------------------------------------------------------
 // Fortify tests.
 // -----------------------------------------------------------------------------
@@ -735,6 +750,7 @@
         "libBionicStandardTests",
         "libBionicElfTlsTests",
         "libBionicFramePointerTests",
+        "libBionicGwpAsanTests",
         "libfortify1-tests-clang",
         "libfortify1-new-tests-clang",
         "libfortify2-tests-clang",
diff --git a/tests/gwp_asan_test.cpp b/tests/gwp_asan_test.cpp
new file mode 100644
index 0000000..b442f51
--- /dev/null
+++ b/tests/gwp_asan_test.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <string>
+
+#if defined(__BIONIC__)
+
+#include "gwp_asan/options.h"
+#include "platform/bionic/malloc.h"
+#include "utils.h"
+
+void RunGwpAsanTest(const char* test_name) {
+  ExecTestHelper eh;
+  eh.SetEnv({"GWP_ASAN_SAMPLE_RATE=1", "GWP_ASAN_PROCESS_SAMPLING=1", "GWP_ASAN_MAX_ALLOCS=40000",
+             nullptr});
+  std::string filter_arg = "--gtest_filter=";
+  filter_arg += test_name;
+  std::string exec(testing::internal::GetArgvs()[0]);
+  eh.SetArgs({exec.c_str(), "--gtest_also_run_disabled_tests", filter_arg.c_str()});
+  eh.Run([&]() { execve(exec.c_str(), eh.GetArgs(), eh.GetEnv()); },
+         /* expected_exit_status */ 0,
+         // |expected_output_regex|, ensure at least one test ran:
+         R"(\[  PASSED  \] [1-9]+0? test)");
+}
+
+// This file implements "torture testing" under GWP-ASan, where we sample every
+// single allocation. The upper limit for the number of GWP-ASan allocations in
+// the torture mode is is generally 40,000, so that svelte devices don't
+// explode, as this uses ~163MiB RAM (4KiB per live allocation).
+TEST(gwp_asan_integration, malloc_tests_under_torture) {
+  RunGwpAsanTest("malloc.*:-malloc.mallinfo*");
+}
+
+#endif  // defined(__BIONIC__)
diff --git a/tests/malloc_test.cpp b/tests/malloc_test.cpp
index f157ec4..8b8c90a 100644
--- a/tests/malloc_test.cpp
+++ b/tests/malloc_test.cpp
@@ -1010,18 +1010,6 @@
   AlignCheck();
 }
 
-// Force GWP-ASan on and verify all alignment checks still pass.
-TEST(malloc, align_check_gwp_asan) {
-#if defined(__BIONIC__)
-  bool force_init = true;
-  ASSERT_TRUE(android_mallopt(M_INITIALIZE_GWP_ASAN, &force_init, sizeof(force_init)));
-
-  AlignCheck();
-#else
-  GTEST_SKIP() << "bionic-only test";
-#endif
-}
-
 // Jemalloc doesn't pass this test right now, so leave it as disabled.
 TEST(malloc, DISABLED_alloc_after_fork) {
   // Both of these need to be a power of 2.
@@ -1371,17 +1359,24 @@
 #endif
 }
 
-TEST(android_mallopt, force_init_gwp_asan) {
 #if defined(__BIONIC__)
-  bool force_init = true;
-  ASSERT_TRUE(android_mallopt(M_INITIALIZE_GWP_ASAN, &force_init, sizeof(force_init)));
+using Action = android_mallopt_gwp_asan_options_t::Action;
+TEST(android_mallopt, DISABLED_multiple_enable_gwp_asan) {
+  android_mallopt_gwp_asan_options_t options;
+  options.program_name = "";  // Don't infer GWP-ASan options from sysprops.
+  options.desire = Action::DONT_TURN_ON_UNLESS_OVERRIDDEN;
+  // GWP-ASan should already be enabled. Trying to enable or disable it should
+  // always pass.
+  ASSERT_TRUE(android_mallopt(M_INITIALIZE_GWP_ASAN, &options, sizeof(options)));
+  options.desire = Action::TURN_ON_WITH_SAMPLING;
+  ASSERT_TRUE(android_mallopt(M_INITIALIZE_GWP_ASAN, &options, sizeof(options)));
+}
+#endif  // defined(__BIONIC__)
 
-  // Verify that trying to do the call again also passes no matter the
-  // value of force_init.
-  force_init = false;
-  ASSERT_TRUE(android_mallopt(M_INITIALIZE_GWP_ASAN, &force_init, sizeof(force_init)));
-  force_init = true;
-  ASSERT_TRUE(android_mallopt(M_INITIALIZE_GWP_ASAN, &force_init, sizeof(force_init)));
+TEST(android_mallopt, multiple_enable_gwp_asan) {
+#if defined(__BIONIC__)
+  // Always enable GWP-Asan, with default options.
+  RunGwpAsanTest("*.DISABLED_multiple_enable_gwp_asan");
 #else
   GTEST_SKIP() << "bionic extension";
 #endif
diff --git a/tests/utils.h b/tests/utils.h
index 284140a..296b6bf 100644
--- a/tests/utils.h
+++ b/tests/utils.h
@@ -270,6 +270,8 @@
   std::vector<const char*> env_;
   std::string output_;
 };
+
+void RunGwpAsanTest(const char* test_name);
 #endif
 
 class FdLeakChecker {