Merge "Switch to the more common idiom for scripts in genrules."
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 08fac4f..18e8bbc 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -38,6 +38,9 @@
     },
     {
       "name": "memunreachable_unit_test"
+    },
+    {
+      "name": "toybox-tests"
     }
   ],
   "hwasan-presubmit": [
@@ -76,6 +79,9 @@
     },
     {
       "name": "memunreachable_unit_test"
+    },
+    {
+      "name": "toybox-tests"
     }
   ]
 }
diff --git a/docs/status.md b/docs/status.md
index bf246a6..de1eed1 100644
--- a/docs/status.md
+++ b/docs/status.md
@@ -29,6 +29,7 @@
   * `gethostid`
   * `shm_open`/`shm_unlink`
   * `sockatmark`
+  * `ualarm`
 
 Missing functionality:
   * `<aio.h>`
diff --git a/libc/BUILD b/libc/BUILD
new file mode 100644
index 0000000..e26c026
--- /dev/null
+++ b/libc/BUILD
@@ -0,0 +1,42 @@
+# 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.
+
+load("//build/bazel/rules/apis:cc_api_contribution.bzl", "cc_api_contribution")
+
+cc_api_contribution(
+  name="libc_contributions",
+  library_name="libc",
+  api=":libc.map.txt",
+  hdrs=[
+    "//bionic/libc/kernel/android:libc_kernel_android_uapi_headers",
+    "//bionic/libc/kernel/android:libc_kernel_android_scsi_headers",
+    "//bionic/libc/kernel/uapi:libc_kernel_uapi_headers",
+    "//bionic/libc/kernel/uapi:libc_kernal_uapi_asm_arm_headers", #arm
+    "//bionic/libc/kernel/uapi:libc_kernal_uapi_asm_arm64_headers", #arm64
+    "//bionic/libc/kernel/uapi:libc_kernal_uapi_asm_x86_headers", #x86
+    "//bionic/libc/kernel/uapi:libc_kernal_uapi_asm_x86_64_headers", #x86_64
+  ],
+)
diff --git a/libc/bionic/gwp_asan_wrappers.cpp b/libc/bionic/gwp_asan_wrappers.cpp
index 7e19b31..fc59c88 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* 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 @@
   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};
+  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/locale.cpp b/libc/bionic/locale.cpp
index 0b7037a..2f4d206 100644
--- a/libc/bionic/locale.cpp
+++ b/libc/bionic/locale.cpp
@@ -54,23 +54,23 @@
 
 struct __locale_t {
   size_t mb_cur_max;
-
-  explicit __locale_t(size_t mb_cur_max) : mb_cur_max(mb_cur_max) {
-  }
-
-  explicit __locale_t(const __locale_t* other) {
-    if (other == LC_GLOBAL_LOCALE) {
-      mb_cur_max = __bionic_current_locale_is_utf8 ? 4 : 1;
-    } else {
-      mb_cur_max = other->mb_cur_max;
-    }
-  }
-
-  BIONIC_DISALLOW_IMPLICIT_CONSTRUCTORS(__locale_t);
 };
 
-size_t __ctype_get_mb_cur_max() {
-  locale_t l = uselocale(nullptr);
+// Avoid using new/delete in this file, because a user may have overridden
+// new/delete, and we want to avoid making extraneous calls to them. This isn't
+// an issue for libc.so in the platform, but this file is also compiled into the
+// NDK's libandroid_support.a, and there are libc++ tests that count the number
+// of calls to new/delete.
+#pragma clang poison new delete
+
+static inline locale_t __alloc_locale(size_t mb_cur_max) {
+  auto result = static_cast<__locale_t*>(malloc(sizeof(__locale_t)));
+  if (result == nullptr) return nullptr;
+  result->mb_cur_max = mb_cur_max;
+  return result;
+}
+
+static inline size_t get_locale_mb_cur_max(locale_t l) {
   if (l == LC_GLOBAL_LOCALE) {
     return __bionic_current_locale_is_utf8 ? 4 : 1;
   } else {
@@ -78,6 +78,10 @@
   }
 }
 
+size_t __ctype_get_mb_cur_max() {
+  return get_locale_mb_cur_max(uselocale(nullptr));
+}
+
 #if !USE_TLS_SLOT
 static thread_local locale_t g_current_locale;
 #endif
@@ -133,11 +137,11 @@
 }
 
 locale_t duplocale(locale_t l) {
-  return new __locale_t(l);
+  return __alloc_locale(get_locale_mb_cur_max(l));
 }
 
 void freelocale(locale_t l) {
-  delete l;
+  free(l);
 }
 
 locale_t newlocale(int category_mask, const char* locale_name, locale_t /*base*/) {
@@ -152,7 +156,7 @@
     return nullptr;
   }
 
-  return new __locale_t(__is_utf8_locale(locale_name) ? 4 : 1);
+  return __alloc_locale(__is_utf8_locale(locale_name) ? 4 : 1);
 }
 
 char* setlocale(int category, const char* locale_name) {
diff --git a/libc/bionic/sysprop_helpers.cpp b/libc/bionic/sysprop_helpers.cpp
index edae6cc..10da3ef 100644
--- a/libc/bionic/sysprop_helpers.cpp
+++ b/libc/bionic/sysprop_helpers.cpp
@@ -53,7 +53,7 @@
         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/libc/include/android/api-level.h b/libc/include/android/api-level.h
index ecf318d..650bb95 100644
--- a/libc/include/android/api-level.h
+++ b/libc/include/android/api-level.h
@@ -154,6 +154,9 @@
 /** Names the "T" API level (33), for comparison against `__ANDROID_API__`. */
 #define __ANDROID_API_T__ 33
 
+/** Names the "U" API level (34), for comparison against `__ANDROID_API__`. */
+#define __ANDROID_API_U__ 34
+
 /* This file is included in <features.h>, and might be used from .S files. */
 #if !defined(__ASSEMBLY__)
 
diff --git a/libc/kernel/android/BUILD b/libc/kernel/android/BUILD
new file mode 100644
index 0000000..b862a6d
--- /dev/null
+++ b/libc/kernel/android/BUILD
@@ -0,0 +1,43 @@
+# 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.
+
+load("//build/bazel/rules/apis:cc_api_contribution.bzl", "cc_api_headers")
+
+package(default_visibility = ["//bionic/libc:__pkg__"])
+
+cc_api_headers(
+  name="libc_kernel_android_uapi_headers",
+  include_dir="uapi",
+  hdrs=glob(["uapi/**/*.h"]),
+  system=True,
+)
+
+cc_api_headers(
+  name="libc_kernel_android_scsi_headers",
+  include_dir="scsi",
+  hdrs=glob(["scsi/**/*.h"]),
+  system=True,
+)
diff --git a/libc/kernel/uapi/BUILD b/libc/kernel/uapi/BUILD
new file mode 100644
index 0000000..2f92464
--- /dev/null
+++ b/libc/kernel/uapi/BUILD
@@ -0,0 +1,67 @@
+# 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.
+
+load("//build/bazel/rules/apis:cc_api_contribution.bzl", "cc_api_headers")
+
+package(default_visibility = ["//bionic/libc:__pkg__"])
+
+cc_api_headers(
+  name="libc_kernel_uapi_headers",
+  hdrs=glob(["**/*.h"]),
+  system=True,
+)
+
+cc_api_headers(
+  name="libc_kernal_uapi_asm_arm_headers",
+  include_dir="asm-arm",
+  hdrs=glob(["asm-arm/**/*.h"]),
+  system=True,
+  arch="arm",
+)
+
+cc_api_headers(
+  name="libc_kernal_uapi_asm_arm64_headers",
+  include_dir="asm-arm64",
+  hdrs=glob(["asm-arm64/**/*.h"]),
+  system=True,
+  arch="arm64",
+)
+
+cc_api_headers(
+  name="libc_kernal_uapi_asm_x86_headers",
+  include_dir="asm-x86",
+  hdrs=glob(["asm-x86/**/*.h"]),
+  system=True,
+  arch="x86",
+)
+
+cc_api_headers(
+  name="libc_kernal_uapi_asm_x86_64_headers",
+  include_dir="asm-x86_64",
+  hdrs=glob(["asm-x86_64/**/*.h"]),
+  system=True,
+  arch="x86_64",
+)
diff --git a/libc/tzcode/bionic.cpp b/libc/tzcode/bionic.cpp
index e134aaa..d2b5d80 100644
--- a/libc/tzcode/bionic.cpp
+++ b/libc/tzcode/bionic.cpp
@@ -190,6 +190,18 @@
     // Give up now, and don't try fallback tzdata files. We don't log here
     // because for all we know the given olson id was nonsense.
     close(fd);
+    // This file descriptor (-1) is passed to localtime.c. In invalid fd case
+    // upstream passes errno value around methods and having 0 there will
+    // indicate that time zone was found and read successfully and localtime's
+    // internal state was properly initialized (which wasn't as we couldn't find
+    // requested time zone in the tzdata file).
+    // If we reached this point errno is unlikely to be touched. It is only
+    // close(fd) which can do it, but that is very unlikely to happen. And
+    // even if it happens we can't extract any useful insights from it.
+    // We are overriding it to ENOENT as it matches upstream expectations -
+    // time zone is absent in the tzdata file == there is no TZif file in
+    // /usr/share/zoneinfo.
+    errno = ENOENT;
     return -1;
   }
 
@@ -206,24 +218,14 @@
 int __bionic_open_tzdata(const char* olson_id, int32_t* entry_length) {
   int fd;
 
-  // Try the three locations for the tzdata file in a strict order:
-  // 1: The O-MR1 time zone updates via APK update mechanism. This is
-  //    tried first because it allows us to test that the time zone updates
-  //    via APK mechanism still works even on devices with the time zone
-  //    module.
-  //    TODO: remove this when those devices are no longer supported.
-  // 2: The time zone data module which contains the main copy. This is the
+  // Try the two locations for the tzdata file in a strict order:
+  // 1: The time zone data module which contains the main copy. This is the
   //    common case for current devices.
-  // 3: The ultimate fallback: the non-updatable copy in /system.
+  // 2: The ultimate fallback: the non-updatable copy in /system.
 
 #if defined(__ANDROID__)
   // On Android devices, bionic has to work even if exec takes place without
   // environment variables set. So, all paths are hardcoded here.
-
-  fd = __bionic_open_tzdata_path("/data/misc/zoneinfo/current/tzdata",
-                                 olson_id, entry_length);
-  if (fd >= -1) return fd;
-
   fd = __bionic_open_tzdata_path("/apex/com.android.tzdata/etc/tz/tzdata",
                                  olson_id, entry_length);
   if (fd >= -1) return fd;
@@ -233,16 +235,10 @@
   if (fd >= -1) return fd;
 #else
   // On the host, we don't expect the hard-coded locations above to exist, and
-  // we're not worried about security so we trust $ANDROID_DATA,
-  // $ANDROID_TZDATA_ROOT, and $ANDROID_ROOT to point us in the right direction
-  // instead.
+  // we're not worried about security so we trust $ANDROID_TZDATA_ROOT, and
+  // $ANDROID_ROOT to point us in the right direction instead.
 
-  char* path = make_path("ANDROID_DATA", "/misc/zoneinfo/current/tzdata");
-  fd = __bionic_open_tzdata_path(path, olson_id, entry_length);
-  free(path);
-  if (fd >= -1) return fd;
-
-  path = make_path("ANDROID_TZDATA_ROOT", "/etc/tz/tzdata");
+  char* path = make_path("ANDROID_TZDATA_ROOT", "/etc/tz/tzdata");
   fd = __bionic_open_tzdata_path(path, olson_id, entry_length);
   free(path);
   if (fd >= -1) return fd;
diff --git a/tests/Android.bp b/tests/Android.bp
index 6a41cf2..8f4ba2f 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -479,6 +479,7 @@
         "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 {
-    name: "libBionicGwpAsanTests",
-    defaults: ["bionic_tests_defaults"],
-    srcs: [
-        "gwp_asan_test.cpp",
-    ],
-    include_dirs: [
-        "bionic/libc",
-    ],
-    static_libs: ["libbase"],
-}
-
 // -----------------------------------------------------------------------------
 // Fortify tests.
 // -----------------------------------------------------------------------------
@@ -770,7 +759,6 @@
         "libBionicStandardTests",
         "libBionicElfTlsTests",
         "libBionicFramePointerTests",
-        "libBionicGwpAsanTests",
         "libfortify1-tests-clang",
         "libfortify1-new-tests-clang",
         "libfortify2-tests-clang",
@@ -872,6 +860,7 @@
         "__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 b2c7780..8e51323 100644
--- a/tests/gwp_asan_test.cpp
+++ b/tests/gwp_asan_test.cpp
@@ -28,27 +28,20 @@
 
 #include <gtest/gtest.h>
 #include <stdio.h>
+#include <sys/file.h>
 #include <string>
 
 #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 @@
   RunGwpAsanTest("malloc.*:-malloc.mallinfo*");
 }
 
+class SyspropRestorer {
+ private:
+  std::vector<std::pair<std::string, std::string>> 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<std::string> 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<std::string*>(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/time_test.cpp b/tests/time_test.cpp
index 983a823..d86550d 100644
--- a/tests/time_test.cpp
+++ b/tests/time_test.cpp
@@ -84,6 +84,16 @@
   ASSERT_EQ(1970, broken_down->tm_year + 1900);
 }
 
+TEST(time, mktime_TZ_as_UTC_and_offset) {
+  struct tm tm = {.tm_year = 70, .tm_mon = 0, .tm_mday = 1};
+
+  // This TZ value is not a valid Olson ID and is not present in tzdata file,
+  // but is a valid TZ string according to POSIX standard.
+  setenv("TZ", "UTC+08:00:00", 1);
+  tzset();
+  ASSERT_EQ(static_cast<time_t>(8 * 60 * 60), mktime(&tm));
+}
+
 static void* gmtime_no_stack_overflow_14313703_fn(void*) {
   const char* original_tz = getenv("TZ");
   // Ensure we'll actually have to enter tzload by using a time zone that doesn't exist.
diff --git a/tests/utils.cpp b/tests/utils.cpp
new file mode 100644
index 0000000..8258833
--- /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 72214c2..81869e3 100644
--- a/tests/utils.h
+++ b/tests/utils.h
@@ -19,6 +19,7 @@
 #include <dirent.h>
 #include <dlfcn.h>
 #include <fcntl.h>
+#include <gtest/gtest.h>
 #include <inttypes.h>
 #include <sys/mman.h>
 #include <sys/prctl.h>
@@ -264,6 +265,7 @@
 };
 
 void RunGwpAsanTest(const char* test_name);
+void RunSubtestNoEnv(const char* test_name);
 #endif
 
 class FdLeakChecker {