From 23f0befcc2aecabb5fbfd1b2159389eefc9d4018 Mon Sep 17 00:00:00 2001 From: Mitch Phillips Date: Thu, 23 Jun 2022 11:07:00 -0700 Subject: Add persistent GWP-ASan sysprops. Adds persistent sysprops for test infra usage, and adds the tests for the sysprops. The test does some fancy flocking in order to restore any existing GWP-ASan sysprop usage in the test cleanup. Bug: 236738714 Test: atest bionic-unit-tests Ignore-AOSP-First: cherry-pick to internal branch Change-Id: I8956296d39c98ce8c7dd0a703b240530d8ad48db Merged-In: I8956296d39c98ce8c7dd0a703b240530d8ad48db --- libc/bionic/gwp_asan_wrappers.cpp | 41 +++++--- libc/bionic/sysprop_helpers.cpp | 2 +- tests/Android.bp | 15 +-- tests/gwp_asan_test.cpp | 206 +++++++++++++++++++++++++++++++++++--- tests/utils.cpp | 55 ++++++++++ tests/utils.h | 2 + 6 files changed, 282 insertions(+), 39 deletions(-) create mode 100644 tests/utils.cpp diff --git a/libc/bionic/gwp_asan_wrappers.cpp b/libc/bionic/gwp_asan_wrappers.cpp index 7e19b311e..fc59c8882 100644 --- a/libc/bionic/gwp_asan_wrappers.cpp +++ b/libc/bionic/gwp_asan_wrappers.cpp @@ -41,6 +41,7 @@ #include "gwp_asan_wrappers.h" #include "malloc_common.h" #include "platform/bionic/android_unsafe_frame_pointer_chase.h" +#include "platform/bionic/macros.h" #include "platform/bionic/malloc.h" #include "private/bionic_arc4random.h" #include "private/bionic_globals.h" @@ -221,6 +222,8 @@ static const char* kMaxAllocsAppSysprop = "libc.debug.gwp_asan.max_allocs.app_de static const char* kMaxAllocsTargetedSyspropPrefix = "libc.debug.gwp_asan.max_allocs."; static const char* kMaxAllocsEnvVar = "GWP_ASAN_MAX_ALLOCS"; +static const char kPersistPrefix[] = "persist."; + void SetDefaultGwpAsanOptions(Options* options, unsigned* process_sample_rate, const android_mallopt_gwp_asan_options_t& mallopt_options) { options->Enabled = true; @@ -244,26 +247,40 @@ bool GetGwpAsanOption(unsigned long long* result, 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}; + constexpr size_t kSyspropMaxLen = 512; + char program_specific_sysprop[kSyspropMaxLen] = {}; + char persist_program_specific_sysprop[kSyspropMaxLen] = {}; + char persist_default_sysprop[kSyspropMaxLen] = {}; + const char* sysprop_names[4] = {}; // 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; + const char* default_sysprop = system_sysprop; if (mallopt_options.desire == Action::TURN_ON_FOR_APP) { - sysprop_names[1] = app_sysprop; - } else { - sysprop_names[1] = system_sysprop; + default_sysprop = app_sysprop; } + async_safe_format_buffer(&program_specific_sysprop[0], kSyspropMaxLen, "%s%s", + targeted_sysprop_prefix, basename); + async_safe_format_buffer(&persist_program_specific_sysprop[0], kSyspropMaxLen, "%s%s", + kPersistPrefix, program_specific_sysprop); + async_safe_format_buffer(&persist_default_sysprop[0], kSyspropMaxLen, "%s%s", kPersistPrefix, + default_sysprop); + + // In order of precedence, always take the program-specific sysprop (e.g. + // '[persist.]libc.debug.gwp_asan.sample_rate.cameraserver') over the + // generic sysprop (e.g. + // '[persist.]libc.debug.gwp_asan.(system_default|app_default)'). In + // addition, always take the non-persistent option over the persistent + // option. + sysprop_names[0] = program_specific_sysprop; + sysprop_names[1] = persist_program_specific_sysprop; + sysprop_names[2] = default_sysprop; + sysprop_names[3] = persist_default_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)) { + if (!get_config_from_env_or_sysprops(env_var, sysprop_names, arraysize(sysprop_names), + settings_buf, PROP_VALUE_MAX)) { return false; } diff --git a/libc/bionic/sysprop_helpers.cpp b/libc/bionic/sysprop_helpers.cpp index edae6cc84..10da3efc2 100644 --- a/libc/bionic/sysprop_helpers.cpp +++ b/libc/bionic/sysprop_helpers.cpp @@ -53,7 +53,7 @@ static bool get_property_value(const char* property_name, char* dest, size_t des strncpy(cb_cookie->dest, value, cb_cookie->size); }, &cb_cookie); - if (*dest != '\0' && *dest != '0') return true; + if (*dest != '\0') return true; return false; } diff --git a/tests/Android.bp b/tests/Android.bp index a54ffb835..8639cfc0a 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -479,6 +479,7 @@ cc_test_library { "uchar_test.cpp", "unistd_nofortify_test.cpp", "unistd_test.cpp", + "utils.cpp", "utmp_test.cpp", "wchar_test.cpp", "wctype_test.cpp", @@ -598,18 +599,6 @@ 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. // ----------------------------------------------------------------------------- @@ -774,7 +763,6 @@ cc_test_library { "libBionicStandardTests", "libBionicElfTlsTests", "libBionicFramePointerTests", - "libBionicGwpAsanTests", "libfortify1-tests-clang", "libfortify1-new-tests-clang", "libfortify2-tests-clang", @@ -876,6 +864,7 @@ cc_defaults { "__cxa_thread_atexit_test.cpp", "gtest_globals.cpp", "gtest_main.cpp", + "gwp_asan_test.cpp", "thread_local_test.cpp", ], diff --git a/tests/gwp_asan_test.cpp b/tests/gwp_asan_test.cpp index b2c7780b1..8e5132304 100644 --- a/tests/gwp_asan_test.cpp +++ b/tests/gwp_asan_test.cpp @@ -28,27 +28,20 @@ #include #include +#include #include #if defined(__BIONIC__) +#include "android-base/file.h" #include "gwp_asan/options.h" #include "platform/bionic/malloc.h" +#include "sys/system_properties.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(), nullptr}); - 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)"); -} +// basename is a mess, use gnu basename explicitly to avoid the need for string +// mutation. +extern "C" const char* __gnu_basename(const char* path); // 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 @@ -58,4 +51,191 @@ TEST(gwp_asan_integration, malloc_tests_under_torture) { RunGwpAsanTest("malloc.*:-malloc.mallinfo*"); } +class SyspropRestorer { + private: + std::vector> props_to_restore_; + // System properties are global for a device, so the tests that mutate the + // GWP-ASan system properties must be run mutually exclusive. Because + // bionic-unit-tests is run in an isolated gtest fashion (each test is run in + // its own process), we have to use flocks to synchronise between tests. + int flock_fd_; + + public: + SyspropRestorer() { + std::string path = testing::internal::GetArgvs()[0]; + flock_fd_ = open(path.c_str(), O_RDONLY); + EXPECT_NE(flock_fd_, -1) << "failed to open self for a flock"; + EXPECT_NE(flock(flock_fd_, LOCK_EX), -1) << "failed to flock myself"; + + const char* basename = __gnu_basename(path.c_str()); + std::vector props = { + std::string("libc.debug.gwp_asan.sample_rate.") + basename, + std::string("libc.debug.gwp_asan.process_sampling.") + basename, + std::string("libc.debug.gwp_asan.max_allocs.") + basename, + "libc.debug.gwp_asan.sample_rate.system_default", + "libc.debug.gwp_asan.sample_rate.app_default", + "libc.debug.gwp_asan.process_sampling.system_default", + "libc.debug.gwp_asan.process_sampling.app_default", + "libc.debug.gwp_asan.max_allocs.system_default", + "libc.debug.gwp_asan.max_allocs.app_default", + }; + + size_t base_props_size = props.size(); + for (size_t i = 0; i < base_props_size; ++i) { + props.push_back("persist." + props[i]); + } + + std::string reset_log; + + for (const std::string& prop : props) { + std::string value = GetSysprop(prop); + props_to_restore_.emplace_back(prop, value); + if (!value.empty()) { + __system_property_set(prop.c_str(), ""); + } + } + } + + ~SyspropRestorer() { + for (const auto& kv : props_to_restore_) { + if (kv.second != GetSysprop(kv.first)) { + __system_property_set(kv.first.c_str(), kv.second.c_str()); + } + } + close(flock_fd_); + } + + static std::string GetSysprop(const std::string& name) { + std::string value; + const prop_info* pi = __system_property_find(name.c_str()); + if (pi == nullptr) return value; + __system_property_read_callback( + pi, + [](void* cookie, const char* /* name */, const char* value, uint32_t /* serial */) { + std::string* v = static_cast(cookie); + *v = value; + }, + &value); + return value; + } +}; + +TEST(gwp_asan_integration, DISABLED_assert_gwp_asan_enabled) { + std::string maps; + EXPECT_TRUE(android::base::ReadFileToString("/proc/self/maps", &maps)); + EXPECT_TRUE(maps.find("GWP-ASan") != std::string::npos) << maps; + + volatile int* x = new int; + delete x; + EXPECT_DEATH({ *x = 7; }, ""); +} + +TEST(gwp_asan_integration, DISABLED_assert_gwp_asan_disabled) { + std::string maps; + EXPECT_TRUE(android::base::ReadFileToString("/proc/self/maps", &maps)); + EXPECT_TRUE(maps.find("GWP-ASan") == std::string::npos); +} + +TEST(gwp_asan_integration, sysprops_program_specific) { + SyspropRestorer restorer; + + std::string path = testing::internal::GetArgvs()[0]; + const char* basename = __gnu_basename(path.c_str()); + __system_property_set((std::string("libc.debug.gwp_asan.sample_rate.") + basename).c_str(), "1"); + __system_property_set((std::string("libc.debug.gwp_asan.process_sampling.") + basename).c_str(), + "1"); + __system_property_set((std::string("libc.debug.gwp_asan.max_allocs.") + basename).c_str(), + "40000"); + + RunSubtestNoEnv("gwp_asan_integration.DISABLED_assert_gwp_asan_enabled"); +} + +TEST(gwp_asan_integration, sysprops_persist_program_specific) { + SyspropRestorer restorer; + + std::string path = testing::internal::GetArgvs()[0]; + const char* basename = __gnu_basename(path.c_str()); + __system_property_set( + (std::string("persist.libc.debug.gwp_asan.sample_rate.") + basename).c_str(), "1"); + __system_property_set( + (std::string("persist.libc.debug.gwp_asan.process_sampling.") + basename).c_str(), "1"); + __system_property_set((std::string("persist.libc.debug.gwp_asan.max_allocs.") + basename).c_str(), + "40000"); + + RunSubtestNoEnv("gwp_asan_integration.DISABLED_assert_gwp_asan_enabled"); +} + +TEST(gwp_asan_integration, sysprops_system) { + SyspropRestorer restorer; + + __system_property_set("libc.debug.gwp_asan.sample_rate.system_default", "1"); + __system_property_set("libc.debug.gwp_asan.process_sampling.system_default", "1"); + __system_property_set("libc.debug.gwp_asan.max_allocs.system_default", "40000"); + + RunSubtestNoEnv("gwp_asan_integration.DISABLED_assert_gwp_asan_enabled"); +} + +TEST(gwp_asan_integration, sysprops_persist_system) { + SyspropRestorer restorer; + + __system_property_set("persist.libc.debug.gwp_asan.sample_rate.system_default", "1"); + __system_property_set("persist.libc.debug.gwp_asan.process_sampling.system_default", "1"); + __system_property_set("persist.libc.debug.gwp_asan.max_allocs.system_default", "40000"); + + RunSubtestNoEnv("gwp_asan_integration.DISABLED_assert_gwp_asan_enabled"); +} + +TEST(gwp_asan_integration, sysprops_non_persist_overrides_persist) { + SyspropRestorer restorer; + + __system_property_set("libc.debug.gwp_asan.sample_rate.system_default", "1"); + __system_property_set("libc.debug.gwp_asan.process_sampling.system_default", "1"); + __system_property_set("libc.debug.gwp_asan.max_allocs.system_default", "40000"); + + __system_property_set("persist.libc.debug.gwp_asan.sample_rate.system_default", "0"); + __system_property_set("persist.libc.debug.gwp_asan.process_sampling.system_default", "0"); + __system_property_set("persist.libc.debug.gwp_asan.max_allocs.system_default", "0"); + + RunSubtestNoEnv("gwp_asan_integration.DISABLED_assert_gwp_asan_enabled"); +} + +TEST(gwp_asan_integration, sysprops_program_specific_overrides_default) { + SyspropRestorer restorer; + + std::string path = testing::internal::GetArgvs()[0]; + const char* basename = __gnu_basename(path.c_str()); + __system_property_set( + (std::string("persist.libc.debug.gwp_asan.sample_rate.") + basename).c_str(), "1"); + __system_property_set( + (std::string("persist.libc.debug.gwp_asan.process_sampling.") + basename).c_str(), "1"); + __system_property_set((std::string("persist.libc.debug.gwp_asan.max_allocs.") + basename).c_str(), + "40000"); + + __system_property_set("libc.debug.gwp_asan.sample_rate.system_default", "0"); + __system_property_set("libc.debug.gwp_asan.process_sampling.system_default", "0"); + __system_property_set("libc.debug.gwp_asan.max_allocs.system_default", "0"); + + RunSubtestNoEnv("gwp_asan_integration.DISABLED_assert_gwp_asan_enabled"); +} + +TEST(gwp_asan_integration, sysprops_can_disable) { + SyspropRestorer restorer; + + __system_property_set("libc.debug.gwp_asan.sample_rate.system_default", "0"); + __system_property_set("libc.debug.gwp_asan.process_sampling.system_default", "0"); + __system_property_set("libc.debug.gwp_asan.max_allocs.system_default", "0"); + + RunSubtestNoEnv("gwp_asan_integration.DISABLED_assert_gwp_asan_disabled"); +} + +TEST(gwp_asan_integration, env_overrides_sysprop) { + SyspropRestorer restorer; + + __system_property_set("libc.debug.gwp_asan.sample_rate.system_default", "0"); + __system_property_set("libc.debug.gwp_asan.process_sampling.system_default", "0"); + __system_property_set("libc.debug.gwp_asan.max_allocs.system_default", "0"); + + RunGwpAsanTest("gwp_asan_integration.DISABLED_assert_gwp_asan_enabled"); +} + #endif // defined(__BIONIC__) diff --git a/tests/utils.cpp b/tests/utils.cpp new file mode 100644 index 000000000..825883327 --- /dev/null +++ b/tests/utils.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 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 "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(), nullptr}); + 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-9]* test)"); +} + +void RunSubtestNoEnv(const char* test_name) { + ExecTestHelper eh; + 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(), nullptr}); + 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)"); +} diff --git a/tests/utils.h b/tests/utils.h index 72214c2b9..81869e36a 100644 --- a/tests/utils.h +++ b/tests/utils.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -264,6 +265,7 @@ class ExecTestHelper { }; void RunGwpAsanTest(const char* test_name); +void RunSubtestNoEnv(const char* test_name); #endif class FdLeakChecker { -- cgit v1.2.3