Introduce hwasan mode for linker
This mode instructs the linker to search for libraries in hwasan
subdirectories of all library search paths. This is set up to contain a
hwasan-enabled copy of libc, which is needed for HWASan programs to
operate. There are two ways this mode can be enabled:
* for native binaries, by using the linker_hwasan64 symlink as its
interpreter
* for apps: by setting the LD_HWASAN environment variable in wrap.sh
Bug: 276930343
Change-Id: I0f4117a50091616f26947fbe37a28ee573b97ad0
diff --git a/apex/Android.bp b/apex/Android.bp
index 90a14b2..6201ad1 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -41,6 +41,11 @@
"libc_malloc_debug",
"libc_malloc_hooks",
],
+ arch: {
+ arm64: {
+ native_shared_libs: ["libc_hwasan", "libclang_rt.hwasan"],
+ },
+ },
binaries: [
"linkerconfig",
],
diff --git a/libc/Android.bp b/libc/Android.bp
index 93f9ce9..1a7a1af 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -1674,13 +1674,12 @@
// ========================================================
// libc.a + libc.so
// ========================================================
-cc_library {
+cc_defaults {
defaults: [
"libc_defaults",
"libc_native_allocator_defaults",
],
- name: "libc",
- static_ndk_lib: true,
+ name: "libc_library_defaults",
product_variables: {
platform_sdk_version: {
asflags: ["-DPLATFORM_SDK_VERSION=%d"],
@@ -1807,20 +1806,7 @@
},
},
- stubs: {
- symbol_file: "libc.map.txt",
- versions: [
- "29",
- "R",
- "current",
- ],
- },
- llndk: {
- symbol_file: "libc.map.txt",
- export_headers_as_system: true,
- export_preprocessed_headers: ["include"],
- export_llndk_headers: ["libc_llndk_headers"],
- },
+
apex_available: [
"//apex_available:platform",
"com.android.runtime",
@@ -1835,6 +1821,55 @@
},
}
+cc_library {
+ name: "libc",
+ defaults: [
+ "libc_library_defaults",
+ ],
+ stubs: {
+ symbol_file: "libc.map.txt",
+ versions: [
+ "29",
+ "R",
+ "current",
+ ],
+ },
+ static_ndk_lib: true,
+ llndk: {
+ symbol_file: "libc.map.txt",
+ export_headers_as_system: true,
+ export_preprocessed_headers: ["include"],
+ export_llndk_headers: ["libc_llndk_headers"],
+ },
+}
+
+cc_library {
+ name: "libc_hwasan",
+ defaults: [
+ "libc_library_defaults",
+ ],
+ sanitize: {
+ hwaddress: true,
+ },
+ enabled: false,
+ arch: {
+ arm64: {
+ enabled: true,
+ },
+ },
+ stem: "libc",
+ relative_install_path: "hwasan",
+ // We don't really need the stubs, but this needs to stay to trigger the
+ // symlink logic in soong.
+ stubs: {
+ symbol_file: "libc.map.txt",
+ },
+ native_bridge_supported: false,
+ // It is never correct to depend on this directly. This is only
+ // needed for the runtime apex, and in base_system.mk.
+ visibility: ["//bionic/apex"],
+}
+
genrule {
name: "libc.arm.map",
out: ["libc.arm.map"],
diff --git a/libc/bionic/libc_init_common.cpp b/libc/bionic/libc_init_common.cpp
index 5d5ecac..59b2ddb 100644
--- a/libc/bionic/libc_init_common.cpp
+++ b/libc/bionic/libc_init_common.cpp
@@ -289,6 +289,7 @@
"LD_DEBUG",
"LD_DEBUG_OUTPUT",
"LD_DYNAMIC_WEAK",
+ "LD_HWASAN",
"LD_LIBRARY_PATH",
"LD_ORIGIN_PATH",
"LD_PRELOAD",
diff --git a/linker/Android.bp b/linker/Android.bp
index 83077c6..020bd7d 100644
--- a/linker/Android.bp
+++ b/linker/Android.bp
@@ -374,6 +374,11 @@
],
symlinks: ["linker_asan"],
+ arch: {
+ arm64: {
+ symlinks: ["linker_hwasan"],
+ },
+ },
multilib: {
lib64: {
suffix: "64",
diff --git a/linker/linker.cpp b/linker/linker.cpp
index c5a822a..17b574f 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -34,6 +34,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/vfs.h>
@@ -133,6 +134,36 @@
nullptr
};
+#if defined(__aarch64__)
+static const char* const kHwasanSystemLibDir = "/system/lib64/hwasan";
+static const char* const kHwasanOdmLibDir = "/odm/lib64/hwasan";
+static const char* const kHwasanVendorLibDir = "/vendor/lib64/hwasan";
+
+// HWASan is only supported on aarch64.
+static const char* const kHwsanDefaultLdPaths[] = {
+ kHwasanSystemLibDir,
+ kSystemLibDir,
+ kHwasanOdmLibDir,
+ kOdmLibDir,
+ kHwasanVendorLibDir,
+ kVendorLibDir,
+ nullptr
+};
+
+// Is HWASAN enabled?
+static bool g_is_hwasan = false;
+#else
+static const char* const kHwsanDefaultLdPaths[] = {
+ kSystemLibDir,
+ kOdmLibDir,
+ kVendorLibDir,
+ nullptr
+};
+
+// Never any HWASan. Help the compiler remove the code we don't need.
+constexpr bool g_is_hwasan = false;
+#endif
+
// Is ASAN enabled?
static bool g_is_asan = false;
@@ -2134,26 +2165,46 @@
}
// End Workaround for dlopen(/system/lib/<soname>) when .so is in /apex.
- std::string asan_name_holder;
+ std::string translated_name_holder;
+ assert(!g_is_hwasan || !g_is_asan);
const char* translated_name = name;
if (g_is_asan && translated_name != nullptr && translated_name[0] == '/') {
char original_path[PATH_MAX];
if (realpath(name, original_path) != nullptr) {
- asan_name_holder = std::string(kAsanLibDirPrefix) + original_path;
- if (file_exists(asan_name_holder.c_str())) {
+ translated_name_holder = std::string(kAsanLibDirPrefix) + original_path;
+ if (file_exists(translated_name_holder.c_str())) {
soinfo* si = nullptr;
if (find_loaded_library_by_realpath(ns, original_path, true, &si)) {
PRINT("linker_asan dlopen NOT translating \"%s\" -> \"%s\": library already loaded", name,
- asan_name_holder.c_str());
+ translated_name_holder.c_str());
} else {
PRINT("linker_asan dlopen translating \"%s\" -> \"%s\"", name, translated_name);
- translated_name = asan_name_holder.c_str();
+ translated_name = translated_name_holder.c_str();
+ }
+ }
+ }
+ } else if (g_is_hwasan && translated_name != nullptr && translated_name[0] == '/') {
+ char original_path[PATH_MAX];
+ if (realpath(name, original_path) != nullptr) {
+ // Keep this the same as CreateHwasanPath in system/linkerconfig/modules/namespace.cc.
+ std::string path(original_path);
+ auto slash = path.rfind('/');
+ if (slash != std::string::npos || slash != path.size() - 1) {
+ translated_name_holder = path.substr(0, slash) + "/hwasan" + path.substr(slash);
+ }
+ if (!translated_name_holder.empty() && file_exists(translated_name_holder.c_str())) {
+ soinfo* si = nullptr;
+ if (find_loaded_library_by_realpath(ns, original_path, true, &si)) {
+ PRINT("linker_hwasan dlopen NOT translating \"%s\" -> \"%s\": library already loaded", name,
+ translated_name_holder.c_str());
+ } else {
+ PRINT("linker_hwasan dlopen translating \"%s\" -> \"%s\"", name, translated_name);
+ translated_name = translated_name_holder.c_str();
}
}
}
}
-
ProtectedDataGuard guard;
soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);
loading_trace.End();
@@ -3335,9 +3386,10 @@
return true;
}
-static std::vector<android_namespace_t*> init_default_namespace_no_config(bool is_asan) {
+static std::vector<android_namespace_t*> init_default_namespace_no_config(bool is_asan, bool is_hwasan) {
g_default_namespace.set_isolated(false);
- auto default_ld_paths = is_asan ? kAsanDefaultLdPaths : kDefaultLdPaths;
+ auto default_ld_paths = is_asan ? kAsanDefaultLdPaths : (
+ is_hwasan ? kHwsanDefaultLdPaths : kDefaultLdPaths);
char real_path[PATH_MAX];
std::vector<std::string> ld_default_paths;
@@ -3441,6 +3493,7 @@
return kLdConfigFilePath;
}
+
std::vector<android_namespace_t*> init_default_namespaces(const char* executable_path) {
g_default_namespace.set_name("(default)");
@@ -3454,6 +3507,16 @@
(strcmp(bname, "linker_asan") == 0 ||
strcmp(bname, "linker_asan64") == 0);
+#if defined(__aarch64__)
+ // HWASan is only supported on AArch64.
+ // The AT_SECURE restriction is because this is a debug feature that does
+ // not need to work on secure binaries, it doesn't hurt to disallow the
+ // environment variable for them, as it impacts the program execution.
+ char* hwasan_env = getenv("LD_HWASAN");
+ g_is_hwasan = (bname != nullptr &&
+ strcmp(bname, "linker_hwasan64") == 0) ||
+ (hwasan_env != nullptr && !getauxval(AT_SECURE) && strcmp(hwasan_env, "1") == 0);
+#endif
const Config* config = nullptr;
{
@@ -3461,7 +3524,7 @@
INFO("[ Reading linker config \"%s\" ]", ld_config_file_path.c_str());
ScopedTrace trace(("linker config " + ld_config_file_path).c_str());
std::string error_msg;
- if (!Config::read_binary_config(ld_config_file_path.c_str(), executable_path, g_is_asan,
+ if (!Config::read_binary_config(ld_config_file_path.c_str(), executable_path, g_is_asan, g_is_hwasan,
&config, &error_msg)) {
if (!error_msg.empty()) {
DL_WARN("Warning: couldn't read '%s' for '%s' (using default configuration instead): %s",
@@ -3472,7 +3535,7 @@
}
if (config == nullptr) {
- return init_default_namespace_no_config(g_is_asan);
+ return init_default_namespace_no_config(g_is_asan, g_is_hwasan);
}
const auto& namespace_configs = config->namespace_configs();
diff --git a/linker/linker_config.cpp b/linker/linker_config.cpp
index 1771e87..ad40c50 100644
--- a/linker/linker_config.cpp
+++ b/linker/linker_config.cpp
@@ -463,6 +463,7 @@
bool Config::read_binary_config(const char* ld_config_file_path,
const char* binary_realpath,
bool is_asan,
+ bool is_hwasan,
const Config** config,
std::string* error_msg) {
g_config.clear();
@@ -579,6 +580,8 @@
// these are affected by is_asan flag
if (is_asan) {
property_name_prefix += ".asan";
+ } else if (is_hwasan) {
+ property_name_prefix += ".hwasan";
}
// search paths are resolved (canonicalized). This is required mainly for
diff --git a/linker/linker_config.h b/linker/linker_config.h
index fe23ec1..09fea45 100644
--- a/linker/linker_config.h
+++ b/linker/linker_config.h
@@ -166,6 +166,7 @@
static bool read_binary_config(const char* ld_config_file_path,
const char* binary_realpath,
bool is_asan,
+ bool is_hwasan,
const Config** config,
std::string* error_msg);
diff --git a/linker/linker_config_test.cpp b/linker/linker_config_test.cpp
index acdf641..7e947f3 100644
--- a/linker/linker_config_test.cpp
+++ b/linker/linker_config_test.cpp
@@ -40,6 +40,7 @@
#include <android-base/file.h>
#include <android-base/scopeguard.h>
#include <android-base/stringprintf.h>
+#include <vector>
#if defined(__LP64__)
#define ARCH_SUFFIX "64"
@@ -64,6 +65,10 @@
"namespace.default.asan.search.paths = /data\n"
"namespace.default.asan.search.paths += /vendor/${LIB}\n"
"namespace.default.asan.permitted.paths = /data:/vendor\n"
+ "namespace.default.hwasan.search.paths = /vendor/${LIB}/hwasan\n"
+ "namespace.default.hwasan.search.paths += /vendor/${LIB}\n"
+ "namespace.default.hwasan.permitted.paths = /vendor/${LIB}/hwasan\n"
+ "namespace.default.hwasan.permitted.paths += /vendor/${LIB}\n"
"namespace.default.links = system\n"
"namespace.default.links += vndk\n"
// irregular whitespaces are added intentionally for testing purpose
@@ -77,11 +82,17 @@
"namespace.system.permitted.paths = /system/${LIB}\n"
"namespace.system.asan.search.paths = /data:/system/${LIB}\n"
"namespace.system.asan.permitted.paths = /data:/system\n"
+ "namespace.system.hwasan.search.paths = /system/${LIB}/hwasan\n"
+ "namespace.system.hwasan.search.paths += /system/${LIB}\n"
+ "namespace.system.hwasan.permitted.paths = /system/${LIB}/hwasan\n"
+ "namespace.system.hwasan.permitted.paths += /system/${LIB}\n"
"namespace.vndk.isolated = tr\n"
"namespace.vndk.isolated += ue\n" // should be ignored and return as 'false'.
"namespace.vndk.search.paths = /system/${LIB}/vndk\n"
"namespace.vndk.asan.search.paths = /data\n"
"namespace.vndk.asan.search.paths += /system/${LIB}/vndk\n"
+ "namespace.vndk.hwasan.search.paths = /system/${LIB}/vndk/hwasan\n"
+ "namespace.vndk.hwasan.search.paths += /system/${LIB}/vndk\n"
"namespace.vndk.links = default\n"
"namespace.vndk.link.default.allow_all_shared_libs = true\n"
"namespace.vndk.link.vndk_in_system.allow_all_shared_libs = true\n"
@@ -107,26 +118,50 @@
return resolved_paths;
}
-static void run_linker_config_smoke_test(bool is_asan) {
- const std::vector<std::string> kExpectedDefaultSearchPath =
- resolve_paths(is_asan ? std::vector<std::string>({ "/data", "/vendor/lib" ARCH_SUFFIX }) :
- std::vector<std::string>({ "/vendor/lib" ARCH_SUFFIX }));
+enum class SmokeTestType {
+ None,
+ Asan,
+ Hwasan,
+};
- const std::vector<std::string> kExpectedDefaultPermittedPath =
- resolve_paths(is_asan ? std::vector<std::string>({ "/data", "/vendor" }) :
- std::vector<std::string>({ "/vendor/lib" ARCH_SUFFIX }));
+static void run_linker_config_smoke_test(SmokeTestType type) {
+ std::vector<std::string> expected_default_search_path;
+ std::vector<std::string> expected_default_permitted_path;
+ std::vector<std::string> expected_system_search_path;
+ std::vector<std::string> expected_system_permitted_path;
+ std::vector<std::string> expected_vndk_search_path;
- const std::vector<std::string> kExpectedSystemSearchPath =
- resolve_paths(is_asan ? std::vector<std::string>({ "/data", "/system/lib" ARCH_SUFFIX }) :
- std::vector<std::string>({ "/system/lib" ARCH_SUFFIX }));
+ switch (type) {
+ case SmokeTestType::None:
+ expected_default_search_path = { "/vendor/lib" ARCH_SUFFIX };
+ expected_default_permitted_path = { "/vendor/lib" ARCH_SUFFIX };
+ expected_system_search_path = { "/system/lib" ARCH_SUFFIX };
+ expected_system_permitted_path = { "/system/lib" ARCH_SUFFIX };
+ expected_vndk_search_path = { "/system/lib" ARCH_SUFFIX "/vndk" };
+ break;
+ case SmokeTestType::Asan:
+ expected_default_search_path = { "/data", "/vendor/lib" ARCH_SUFFIX };
+ expected_default_permitted_path = { "/data", "/vendor" };
+ expected_system_search_path = { "/data", "/system/lib" ARCH_SUFFIX };
+ expected_system_permitted_path = { "/data", "/system" };
+ expected_vndk_search_path = { "/data", "/system/lib" ARCH_SUFFIX "/vndk" };
+ break;
+ case SmokeTestType::Hwasan:
+ expected_default_search_path = { "/vendor/lib" ARCH_SUFFIX "/hwasan", "/vendor/lib" ARCH_SUFFIX };
+ expected_default_permitted_path = { "/vendor/lib" ARCH_SUFFIX "/hwasan", "/vendor/lib" ARCH_SUFFIX };
+ expected_system_search_path = { "/system/lib" ARCH_SUFFIX "/hwasan" , "/system/lib" ARCH_SUFFIX };
+ expected_system_permitted_path = { "/system/lib" ARCH_SUFFIX "/hwasan", "/system/lib" ARCH_SUFFIX };
+ expected_vndk_search_path = { "/system/lib" ARCH_SUFFIX "/vndk/hwasan", "/system/lib" ARCH_SUFFIX "/vndk" };
+ break;
+ }
- const std::vector<std::string> kExpectedSystemPermittedPath =
- resolve_paths(is_asan ? std::vector<std::string>({ "/data", "/system" }) :
- std::vector<std::string>({ "/system/lib" ARCH_SUFFIX }));
-
- const std::vector<std::string> kExpectedVndkSearchPath =
- resolve_paths(is_asan ? std::vector<std::string>({ "/data", "/system/lib" ARCH_SUFFIX "/vndk"}) :
- std::vector<std::string>({ "/system/lib" ARCH_SUFFIX "/vndk"}));
+ expected_default_search_path = resolve_paths(expected_default_search_path);
+ // expected_default_permitted_path is skipped on purpose, permitted paths
+ // do not get resolved in linker_config.cpp
+ expected_system_search_path = resolve_paths(expected_system_search_path);
+ // expected_system_permitted_path is skipped on purpose, permitted paths
+ // do not get resolved in linker_config.cpp
+ expected_vndk_search_path = resolve_paths(expected_vndk_search_path);
TemporaryFile tmp_file;
close(tmp_file.fd);
@@ -149,7 +184,8 @@
std::string error_msg;
ASSERT_TRUE(Config::read_binary_config(tmp_file.path,
executable_path.c_str(),
- is_asan,
+ type == SmokeTestType::Asan,
+ type == SmokeTestType::Hwasan,
&config,
&error_msg)) << error_msg;
ASSERT_TRUE(config != nullptr);
@@ -162,8 +198,8 @@
ASSERT_TRUE(default_ns_config->isolated());
ASSERT_FALSE(default_ns_config->visible());
- ASSERT_EQ(kExpectedDefaultSearchPath, default_ns_config->search_paths());
- ASSERT_EQ(kExpectedDefaultPermittedPath, default_ns_config->permitted_paths());
+ ASSERT_EQ(expected_default_search_path, default_ns_config->search_paths());
+ ASSERT_EQ(expected_default_permitted_path, default_ns_config->permitted_paths());
const auto& default_ns_links = default_ns_config->links();
ASSERT_EQ(2U, default_ns_links.size());
@@ -202,14 +238,14 @@
ASSERT_TRUE(ns_system->isolated());
ASSERT_TRUE(ns_system->visible());
- ASSERT_EQ(kExpectedSystemSearchPath, ns_system->search_paths());
- ASSERT_EQ(kExpectedSystemPermittedPath, ns_system->permitted_paths());
+ ASSERT_EQ(expected_system_search_path, ns_system->search_paths());
+ ASSERT_EQ(expected_system_permitted_path, ns_system->permitted_paths());
ASSERT_TRUE(ns_vndk != nullptr) << "vndk namespace was not found";
ASSERT_FALSE(ns_vndk->isolated()); // malformed bool property
ASSERT_FALSE(ns_vndk->visible()); // undefined bool property
- ASSERT_EQ(kExpectedVndkSearchPath, ns_vndk->search_paths());
+ ASSERT_EQ(expected_vndk_search_path, ns_vndk->search_paths());
const auto& ns_vndk_links = ns_vndk->links();
ASSERT_EQ(1U, ns_vndk_links.size());
@@ -223,11 +259,15 @@
}
TEST(linker_config, smoke) {
- run_linker_config_smoke_test(false);
+ run_linker_config_smoke_test(SmokeTestType::None);
}
TEST(linker_config, asan_smoke) {
- run_linker_config_smoke_test(true);
+ run_linker_config_smoke_test(SmokeTestType::Asan);
+}
+
+TEST(linker_config, hwasan_smoke) {
+ run_linker_config_smoke_test(SmokeTestType::Hwasan);
}
TEST(linker_config, ns_link_shared_libs_invalid_settings) {
@@ -259,6 +299,7 @@
ASSERT_FALSE(Config::read_binary_config(tmp_file.path,
executable_path.c_str(),
false,
+ false,
&config,
&error_msg));
ASSERT_TRUE(config == nullptr);
@@ -304,6 +345,7 @@
ASSERT_TRUE(Config::read_binary_config(tmp_file.path,
executable_path.c_str(),
false,
+ false,
&config,
&error_msg)) << error_msg;
diff --git a/tests/Android.bp b/tests/Android.bp
index 1949079..cd20cd7 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -1112,6 +1112,30 @@
}
cc_test {
+ name: "hwasan_test",
+ enabled: false,
+ // This does not use bionic_tests_defaults because it is not supported on
+ // host.
+ arch: {
+ arm64: {
+ enabled: true,
+ },
+ },
+ sanitize: {
+ hwaddress: true,
+ },
+ srcs: [
+ "hwasan_test.cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+ data_libs: ["libtest_simple_hwasan", "libtest_simple_hwasan_nohwasan"],
+ header_libs: ["bionic_libc_platform_headers"],
+ test_suites: ["device-tests"],
+}
+
+cc_test {
name: "bionic-stress-tests",
defaults: [
"bionic_tests_defaults",
diff --git a/tests/dlfcn_test.cpp b/tests/dlfcn_test.cpp
index 3f70279..0c71b2a 100644
--- a/tests/dlfcn_test.cpp
+++ b/tests/dlfcn_test.cpp
@@ -971,9 +971,15 @@
}
#define ALTERNATE_PATH_TO_SYSTEM_LIB "/system/lib64/" ABI_STRING "/"
+#if __has_feature(hwaddress_sanitizer)
+#define PATH_TO_LIBC PATH_TO_SYSTEM_LIB "hwasan/libc.so"
+#define PATH_TO_BOOTSTRAP_LIBC PATH_TO_SYSTEM_LIB "bootstrap/hwasan/libc.so"
+#define ALTERNATE_PATH_TO_LIBC ALTERNATE_PATH_TO_SYSTEM_LIB "hwasan/libc.so"
+#else
#define PATH_TO_LIBC PATH_TO_SYSTEM_LIB "libc.so"
#define PATH_TO_BOOTSTRAP_LIBC PATH_TO_SYSTEM_LIB "bootstrap/libc.so"
#define ALTERNATE_PATH_TO_LIBC ALTERNATE_PATH_TO_SYSTEM_LIB "libc.so"
+#endif
TEST(dlfcn, dladdr_libc) {
#if defined(__GLIBC__)
diff --git a/tests/hwasan_test.cpp b/tests/hwasan_test.cpp
new file mode 100644
index 0000000..5c21495
--- /dev/null
+++ b/tests/hwasan_test.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dlfcn.h>
+#include <stdlib.h>
+
+#include <gtest/gtest.h>
+
+#include <android-base/silent_death_test.h>
+#include <android-base/test_utils.h>
+
+using HwasanDeathTest = SilentDeathTest;
+
+TEST_F(HwasanDeathTest, UseAfterFree) {
+ EXPECT_DEATH(
+ {
+ void* m = malloc(1);
+ volatile char* x = const_cast<volatile char*>(reinterpret_cast<char*>(m));
+ *x = 1;
+ free(m);
+ *x = 2;
+ },
+ "use-after-free");
+}
+
+TEST_F(HwasanDeathTest, OutOfBounds) {
+ EXPECT_DEATH(
+ {
+ void* m = malloc(1);
+ volatile char* x = const_cast<volatile char*>(reinterpret_cast<char*>(m));
+ x[1] = 1;
+ },
+ "buffer-overflow");
+}
+
+// Check whether dlopen of /foo/bar.so checks /foo/hwasan/bar.so first.
+TEST(HwasanTest, DlopenAbsolutePath) {
+ std::string path = android::base::GetExecutableDirectory() + "/libtest_simple_hwasan.so";
+ ASSERT_EQ(0, access(path.c_str(), F_OK)); // Verify test setup.
+ std::string hwasan_path =
+ android::base::GetExecutableDirectory() + "/hwasan/libtest_simple_hwasan.so";
+ ASSERT_EQ(0, access(hwasan_path.c_str(), F_OK)); // Verify test setup.
+
+ void* handle = dlopen(path.c_str(), RTLD_NOW);
+ ASSERT_TRUE(handle != nullptr);
+ uint32_t* compiled_with_hwasan =
+ reinterpret_cast<uint32_t*>(dlsym(handle, "dlopen_testlib_compiled_with_hwasan"));
+ EXPECT_TRUE(*compiled_with_hwasan);
+ dlclose(handle);
+}
+
+TEST(HwasanTest, IsRunningWithHWasan) {
+ EXPECT_TRUE(running_with_hwasan());
+}
diff --git a/tests/libs/Android.bp b/tests/libs/Android.bp
index 8ae2257..a2fbe55 100644
--- a/tests/libs/Android.bp
+++ b/tests/libs/Android.bp
@@ -243,6 +243,38 @@
}
// -----------------------------------------------------------------------------
+// Libraries used by hwasan_test
+// -----------------------------------------------------------------------------
+cc_test_library {
+ name: "libtest_simple_hwasan",
+ arch: {
+ arm64: {
+ enabled: true,
+ },
+ },
+ sanitize: {
+ hwaddress: true,
+ },
+ relative_install_path: "hwasan",
+ enabled: false,
+ srcs: ["dlopen_testlib_simple_hwasan.cpp"],
+}
+
+cc_test_library {
+ // A weird name. This is the vanilla (non-HWASan) copy of the library that
+ // is used for the hwasan test.
+ name: "libtest_simple_hwasan_nohwasan",
+ arch: {
+ arm64: {
+ enabled: true,
+ },
+ },
+ stem: "libtest_simple_hwasan",
+ enabled: false,
+ srcs: ["dlopen_testlib_simple_hwasan.cpp"],
+}
+
+// -----------------------------------------------------------------------------
// Library used by dlext direct unload on the namespace boundary tests
// -----------------------------------------------------------------------------
cc_test_library {
diff --git a/tests/libs/dlopen_testlib_simple_hwasan.cpp b/tests/libs/dlopen_testlib_simple_hwasan.cpp
new file mode 100644
index 0000000..b92e05f
--- /dev/null
+++ b/tests/libs/dlopen_testlib_simple_hwasan.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+
+#if __has_feature(hwaddress_sanitizer)
+extern "C" uint32_t dlopen_testlib_compiled_with_hwasan = true;
+#else
+extern "C" uint32_t dlopen_testlib_compiled_with_hwasan = false;
+#endif
+
+extern "C" bool dlopen_testlib_simple_hwasan_func() {
+ return true;
+}