summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Martin Stjernholm <mast@google.com> 2024-04-05 23:18:14 +0100
committer Martin Stjernholm <mast@google.com> 2024-04-16 18:00:00 +0100
commit5ab729974f972b0a0a3ddbc70f30742a69c6d612 (patch)
tree257bbb54077ba05812fd9c655a2defe61e35f18a
parent5009fef90f0a8730fadc8efb3d7788ad8770a190 (diff)
Implement a check that standalone tests only link known available libs
dynamically. Also set min_sdk_version for them to the same as the APEX. Test: atest art_libnativebridge_cts_tests art_standalone_artd_tests art_standalone_cmdline_tests art_standalone_compiler_tests art_standalone_dex2oat_cts_tests art_standalone_dex2oat_tests art_standalone_dexdump_tests art_standalone_dexlist_tests art_standalone_dexopt_chroot_setup_tests art_standalone_dexoptanalyzer_tests art_standalone_dexpreopt_tests art_standalone_disassembler_tests art_standalone_libartbase_tests art_standalone_libartpalette_tests art_standalone_libartservice_tests art_standalone_libarttools_tests art_standalone_libdexfile_external_tests art_standalone_libdexfile_support_tests art_standalone_libdexfile_tests art_standalone_libprofile_tests art_standalone_oatdump_tests art_standalone_odrefresh_tests art_standalone_profman_tests art_standalone_runtime_tests art_standalone_sigchain_tests libnativeloader_lazy_test libnativeloader_test Bug: 247108425 Change-Id: I393839b61c5254cfcd41f338fe1bd8ad03f58e05
-rw-r--r--test/Android.bp28
-rw-r--r--test/standalone_test_lib_check.cc172
2 files changed, 200 insertions, 0 deletions
diff --git a/test/Android.bp b/test/Android.bp
index 1768340530..bc71245b9b 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -178,10 +178,26 @@ filegroup {
],
}
+cc_library_static {
+ name: "standalone_test_lib_check",
+ srcs: ["standalone_test_lib_check.cc"],
+ static_libs: [
+ "libbase",
+ "libelf",
+ "libgmock",
+ "libgtest",
+ ],
+}
+
+// Defaults for tests that can run using atest against an ART APEX installed on
+// any supported platform. These tests can only depend on NDK libraries from
+// platform and exported module libraries. Any other libraries must be pushed
+// with the test or linked statically.
art_cc_defaults {
name: "art_standalone_test_defaults",
defaults: [
"art_test_common_defaults",
+ "elfutils_transitive_defaults", // For libelf
],
// Standalone ART gtests are only supported on device for now.
// TODO: Add support for host standalone ART gtests. Note that they should not differ much
@@ -206,6 +222,18 @@ art_cc_defaults {
suffix: "64",
},
},
+
+ min_sdk_version: "31", // Same as the ART APEX.
+
+ // Make standalone tests check their own NEEDED dependencies for disallowed
+ // libraries. Add standalone_test_lib_check and its dependencies, except
+ // libgtest which is expected to be added by the tests.
+ whole_static_libs: [
+ "libbase",
+ "libelf",
+ "libgmock",
+ "standalone_test_lib_check",
+ ],
}
// Properties common to `art_gtest_defaults` and `art_standalone_gtest_defaults`.
diff --git a/test/standalone_test_lib_check.cc b/test/standalone_test_lib_check.cc
new file mode 100644
index 0000000000..0d90055142
--- /dev/null
+++ b/test/standalone_test_lib_check.cc
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+// Check that the current test executable only links known exported libraries
+// dynamically. Intended to be statically linked into standalone tests.
+
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <libelf.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "android-base/result-gmock.h"
+#include "android-base/result.h"
+#include "android-base/scopeguard.h"
+#include "android-base/strings.h"
+#include "android-base/unique_fd.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using ::android::base::ErrnoError;
+using ::android::base::Error;
+using ::android::base::Result;
+
+// The allow-listed libraries. Standalone tests can assume that the ART module
+// is from the same build as the test(*), but not the platform nor any other
+// module. Hence all dynamic libraries listed here must satisfy at least one of
+// these conditions:
+//
+// - Have a stable ABI and be available since the APEX min_sdk_version (31).
+// This includes NDK and system APIs.
+// - Be loaded from the ART APEX itself(*). Note that linker namespaces aren't
+// set up to allow this for libraries that aren't exported, so in practice it
+// is restricted to them.
+// - Always be pushed to device together with the test.
+// - Be a runtime instrumentation library or similar, e.g. for sanitizer test
+// builds, where everything is always built from source - platform, module,
+// and tests.
+//
+// *) (Non-MCTS) CTS tests is an exception - they must work with any future
+// version of the module and hence restrict themselves to the exported module
+// APIs.
+constexpr const char* kAllowedDynamicLibDeps[] = {
+ // LLVM
+ "libclang_rt.hwasan-aarch64-android.so",
+ // Bionic
+ "libc.so",
+ "libdl.so",
+ "libdl_android.so",
+ "libm.so",
+ // Platform
+ "heapprofd_client_api.so",
+ "libbinder_ndk.so",
+ "liblog.so",
+ "libselinux.so",
+ "libz.so",
+ // Other modules
+ "libstatspull.so",
+ "libstatssocket.so",
+ // ART exported
+ "libdexfile.so",
+ "libnativebridge.so",
+ "libnativehelper.so",
+ "libnativeloader.so",
+ // TODO(b/333438055): Remove this when we can link libc++.so statically everywhere.
+ "libc++.so",
+ // TODO(b/247108425): Fix the ODR problem and go back to static linking for libunwindstack.
+ "libunwindstack.so",
+};
+
+Result<std::string> GetCurrentElfObjectPath() {
+ Dl_info info;
+ if (dladdr(reinterpret_cast<void*>(GetCurrentElfObjectPath), &info) == 0) {
+ return Error() << "dladdr failed to map own address to a shared object.";
+ }
+ return info.dli_fname;
+}
+
+Result<std::vector<std::string>> GetDynamicLibDeps(const std::string& filename) {
+ if (elf_version(EV_CURRENT) == EV_NONE) {
+ return Errorf("libelf initialization failed: {}", elf_errmsg(-1));
+ }
+
+ android::base::unique_fd fd(open(filename.c_str(), O_RDONLY));
+ if (fd.get() == -1) {
+ return ErrnoErrorf("Error opening {}", filename);
+ }
+
+ Elf* elf = elf_begin(fd.get(), ELF_C_READ, /*ref=*/nullptr);
+ if (elf == nullptr) {
+ return Errorf("Error creating ELF object for {}: {}", filename, elf_errmsg(-1));
+ }
+ auto elf_cleanup = android::base::make_scope_guard([&]() { elf_end(elf); });
+
+ std::vector<std::string> libs;
+
+ // Find the dynamic section.
+ for (Elf_Scn* dyn_scn = nullptr; (dyn_scn = elf_nextscn(elf, dyn_scn)) != nullptr;) {
+ GElf_Shdr scn_hdr;
+ if (gelf_getshdr(dyn_scn, &scn_hdr) != &scn_hdr) {
+ return Errorf("Failed to retrieve ELF section header in {}: {}", filename, elf_errmsg(-1));
+ }
+
+ if (scn_hdr.sh_type == SHT_DYNAMIC) {
+ Elf_Data* data = elf_getdata(dyn_scn, /*data=*/nullptr);
+
+ // Iterate through dynamic section entries.
+ for (int i = 0; i < scn_hdr.sh_size / scn_hdr.sh_entsize; i++) {
+ GElf_Dyn dyn_entry;
+ if (gelf_getdyn(data, i, &dyn_entry) != &dyn_entry) {
+ return Errorf("Failed to get entry {} in ELF dynamic section of {}: {}",
+ i,
+ filename,
+ elf_errmsg(-1));
+ }
+
+ if (dyn_entry.d_tag == DT_NEEDED) {
+ const char* lib_name = elf_strptr(elf, scn_hdr.sh_link, dyn_entry.d_un.d_val);
+ if (lib_name == nullptr) {
+ return Errorf("Failed to get string from entry {} in ELF dynamic section of {}: {}",
+ i,
+ filename,
+ elf_errmsg(-1));
+ }
+ libs.push_back(lib_name);
+ }
+ }
+ break; // Found the dynamic section, no need to continue.
+ }
+ }
+
+ return libs;
+}
+
+} // namespace
+
+TEST(StandaloneTestAllowedLibDeps, test) {
+ Result<std::string> path_to_self = GetCurrentElfObjectPath();
+ ASSERT_RESULT_OK(path_to_self);
+ Result<std::vector<std::string>> dyn_lib_deps = GetDynamicLibDeps(path_to_self.value());
+ ASSERT_RESULT_OK(dyn_lib_deps);
+
+ std::vector<std::string> disallowed_libs;
+ for (const std::string& dyn_lib_dep : dyn_lib_deps.value()) {
+ if (std::find(std::begin(kAllowedDynamicLibDeps),
+ std::end(kAllowedDynamicLibDeps),
+ dyn_lib_dep) == std::end(kAllowedDynamicLibDeps)) {
+ disallowed_libs.push_back(dyn_lib_dep);
+ }
+ }
+
+ EXPECT_THAT(disallowed_libs, testing::IsEmpty())
+ << path_to_self.value() << " has disallowed shared library dependencies.";
+}