diff options
author | 2024-04-05 23:18:14 +0100 | |
---|---|---|
committer | 2024-04-16 18:00:00 +0100 | |
commit | 5ab729974f972b0a0a3ddbc70f30742a69c6d612 (patch) | |
tree | 257bbb54077ba05812fd9c655a2defe61e35f18a | |
parent | 5009fef90f0a8730fadc8efb3d7788ad8770a190 (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.bp | 28 | ||||
-rw-r--r-- | test/standalone_test_lib_check.cc | 172 |
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."; +} |