From e6997d52de0523c876dfc0c53e8da6958230d284 Mon Sep 17 00:00:00 2001 From: Mitch Phillips Date: Mon, 30 Nov 2020 15:04:14 -0800 Subject: [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.*.: 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 --- libc/Android.bp | 1 + libc/bionic/gwp_asan_wrappers.cpp | 321 +++++++++++++++++++++++----------- libc/bionic/gwp_asan_wrappers.h | 15 +- libc/bionic/libc_init_static.cpp | 25 +-- libc/bionic/malloc_common.cpp | 4 +- libc/bionic/malloc_common_dynamic.cpp | 4 +- libc/bionic/sysprop_helpers.cpp | 76 ++++++++ libc/bionic/sysprop_helpers.h | 45 +++++ libc/platform/bionic/malloc.h | 34 +++- tests/Android.bp | 18 +- tests/gwp_asan_test.cpp | 61 +++++++ tests/malloc_test.cpp | 39 ++--- tests/utils.h | 2 + 13 files changed, 483 insertions(+), 162 deletions(-) create mode 100644 libc/bionic/sysprop_helpers.cpp create mode 100644 libc/bionic/sysprop_helpers.h create mode 100644 tests/gwp_asan_test.cpp diff --git a/libc/Android.bp b/libc/Android.bp index 7175c77c2..7abd6b053 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 8c513475d..2f513e5a6 100644 --- a/libc/bionic/gwp_asan_wrappers.cpp +++ b/libc/bionic/gwp_asan_wrappers.cpp @@ -26,21 +26,27 @@ * SUCH DAMAGE. */ -#include -#include -#include -#include -#include +#include +#include +#include #include #include #include +#include #include #include -#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. @@ -175,66 +136,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(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(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 @@ 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 -#include #include -// 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(arg)); + return EnableGwpAsan(*reinterpret_cast(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(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(arg)); + return EnableGwpAsan(*reinterpret_cast(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 +#include +#include +#include +#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(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 +#include + +// 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.*." + // 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 3061142e0..ee49e6525 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: { @@ -571,6 +574,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. // ----------------------------------------------------------------------------- @@ -735,6 +750,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 +#include +#include + +#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 f157ec497..8b8c90a46 100644 --- a/tests/malloc_test.cpp +++ b/tests/malloc_test.cpp @@ -1010,18 +1010,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. @@ -1371,17 +1359,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 284140a0d..296b6bf82 100644 --- a/tests/utils.h +++ b/tests/utils.h @@ -270,6 +270,8 @@ class ExecTestHelper { std::vector env_; std::string output_; }; + +void RunGwpAsanTest(const char* test_name); #endif class FdLeakChecker { -- cgit v1.2.3