aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitch Phillips <mitchp@google.com>2022-04-21 19:51:53 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-04-21 19:51:53 +0000
commit29104a5bf09d9c30cad79c8c4694c123f0abddea (patch)
tree83444dc13350002b184abc67dfe911475f675b7e
parent0d860e6ff847263c0cdea12b81f1d4faa028d91d (diff)
parentde31754be1afe3bbe797b3aaec6f124452564aec (diff)
downloadbionic-29104a5bf09d9c30cad79c8c4694c123f0abddea.tar.gz
Merge "[GWP-ASan] Provide runtime configuration through an env var + sysprop." am: 3865c8f942 am: a6b526a12d am: 75011e6012 am: 99422461a7 am: de31754be1
Original change: https://android-review.googlesource.com/c/platform/bionic/+/2038947 Change-Id: Ie4378da15d269c67ea5a9a1f15aba0608483e501 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--libc/Android.bp1
-rw-r--r--libc/bionic/gwp_asan_wrappers.cpp321
-rw-r--r--libc/bionic/gwp_asan_wrappers.h15
-rw-r--r--libc/bionic/libc_init_static.cpp25
-rw-r--r--libc/bionic/malloc_common.cpp4
-rw-r--r--libc/bionic/malloc_common_dynamic.cpp4
-rw-r--r--libc/bionic/sysprop_helpers.cpp76
-rw-r--r--libc/bionic/sysprop_helpers.h45
-rw-r--r--libc/platform/bionic/malloc.h34
-rw-r--r--tests/Android.bp18
-rw-r--r--tests/gwp_asan_test.cpp61
-rw-r--r--tests/malloc_test.cpp39
-rw-r--r--tests/utils.h2
13 files changed, 483 insertions, 162 deletions
diff --git a/libc/Android.bp b/libc/Android.bp
index 07e483e4f..97146aa6f 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -28,6 +28,7 @@ libc_common_src_files = [
"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 79b4b69c7..7e19b311e 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.
-// ============================================================================
-
-// 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;
-}
+// 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);
-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.
@@ -186,66 +147,202 @@ void gwp_asan_malloc_enable() {
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(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);
+ }
-bool ShouldGwpAsanSampleProcess() {
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);
+
+ 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;
+ }
-// 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) {
+ 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;
}
@@ -274,28 +371,48 @@ bool MaybeInitGwpAsan(libc_globals* globals, bool force_init) {
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 c568681e2..219da9fc5 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 815b9388f..575da626d 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 @@ static void layout_static_tls(KernelArgumentBlock& args) {
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 38168eeec..9744968fc 100644
--- a/libc/bionic/malloc_common.cpp
+++ b/libc/bionic/malloc_common.cpp
@@ -326,12 +326,12 @@ extern "C" bool android_mallopt(int opcode, void* arg, size_t arg_size) {
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 1f58fdaca..6c2f4d941 100644
--- a/libc/bionic/malloc_common_dynamic.cpp
+++ b/libc/bionic/malloc_common_dynamic.cpp
@@ -526,12 +526,12 @@ extern "C" bool android_mallopt(int opcode, void* arg, size_t arg_size) {
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 000000000..edae6cc84
--- /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 000000000..a02c2dc41
--- /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 b56ca746a..f0f13d012 100644
--- a/libc/platform/bionic/malloc.h
+++ b/libc/platform/bionic/malloc.h
@@ -96,12 +96,42 @@ enum {
// 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 2ea6087c3..a54ffb835 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -62,7 +62,10 @@ cc_defaults {
// 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: {
@@ -595,6 +598,18 @@ cc_test_library {
],
}
+cc_test_library {
+ name: "libBionicGwpAsanTests",
+ defaults: ["bionic_tests_defaults"],
+ srcs: [
+ "gwp_asan_test.cpp",
+ ],
+ include_dirs: [
+ "bionic/libc",
+ ],
+ static_libs: ["libbase"],
+}
+
// -----------------------------------------------------------------------------
// Fortify tests.
// -----------------------------------------------------------------------------
@@ -759,6 +774,7 @@ cc_test_library {
"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 000000000..b442f51e6
--- /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 1b875cdf1..69f8506fd 100644
--- a/tests/malloc_test.cpp
+++ b/tests/malloc_test.cpp
@@ -1011,18 +1011,6 @@ TEST(malloc, align_check) {
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.
@@ -1372,17 +1360,24 @@ TEST(android_mallopt, set_allocation_limit_multiple_threads) {
#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)));
-
- // 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)));
+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__)
+
+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 a62dde612..72214c2b9 100644
--- a/tests/utils.h
+++ b/tests/utils.h
@@ -262,6 +262,8 @@ class ExecTestHelper {
std::vector<const char*> env_;
std::string output_;
};
+
+void RunGwpAsanTest(const char* test_name);
#endif
class FdLeakChecker {