xref: /aosp_15_r20/art/test/standalone_test_lib_check.cc (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 // Check that the current test executable only links known exported libraries
18 // dynamically. Intended to be statically linked into standalone tests.
19 
20 #include <dlfcn.h>
21 #include <fcntl.h>
22 #include <gelf.h>
23 #include <libelf.h>
24 
25 #include <algorithm>
26 #include <filesystem>
27 #include <string>
28 #include <vector>
29 
30 #include "android-base/result-gmock.h"
31 #include "android-base/result.h"
32 #include "android-base/scopeguard.h"
33 #include "android-base/strings.h"
34 #include "android-base/unique_fd.h"
35 #include "base/stl_util.h"
36 #include "gmock/gmock.h"
37 #include "gtest/gtest.h"
38 
39 namespace {
40 
41 using ::android::base::ErrnoError;
42 using ::android::base::Error;
43 using ::android::base::Result;
44 
45 // The allow-listed libraries. Standalone tests can assume that the ART module
46 // is from the same build as the test(*), but not the platform nor any other
47 // module. Hence all dynamic libraries listed here must satisfy at least one of
48 // these conditions:
49 //
50 // -  Have a stable ABI and be available since the APEX min_sdk_version (31).
51 //    This includes NDK and system APIs.
52 // -  Be loaded from the ART APEX itself(*). Note that linker namespaces aren't
53 //    set up to allow this for libraries that aren't exported, so in practice it
54 //    is restricted to them.
55 // -  Always be pushed to device together with the test.
56 // -  Be a runtime instrumentation library or similar, e.g. for sanitizer test
57 //    builds, where everything is always built from source - platform, module,
58 //    and tests.
59 //
60 // *) (Non-MCTS) CTS tests is an exception - they must work with any future
61 // version of the module and hence restrict themselves to the exported module
62 // APIs.
63 constexpr const char* kAllowedDynamicLibDeps[] = {
64     // LLVM
65     "libclang_rt.hwasan-aarch64-android.so",
66     // Bionic
67     "libc.so",
68     "libdl.so",
69     "libdl_android.so",
70     "libm.so",
71     // Platform
72     "heapprofd_client_api.so",
73     "libbinder_ndk.so",
74     "liblog.so",
75     "libselinux.so",
76     "libz.so",
77     // Other modules
78     "libstatspull.so",
79     "libstatssocket.so",
80     // ART exported
81     "libdexfile.so",
82     "libnativebridge.so",
83     "libnativehelper.so",
84     "libnativeloader.so",
85 };
86 
GetCurrentElfObjectPath()87 Result<std::string> GetCurrentElfObjectPath() {
88   Dl_info info;
89   if (dladdr(reinterpret_cast<void*>(GetCurrentElfObjectPath), &info) == 0) {
90     return Error() << "dladdr failed to map own address to a shared object.";
91   }
92   return info.dli_fname;
93 }
94 
GetDynamicLibDeps(const std::string & filename)95 Result<std::vector<std::string>> GetDynamicLibDeps(const std::string& filename) {
96   if (elf_version(EV_CURRENT) == EV_NONE) {
97     return Errorf("libelf initialization failed: {}", elf_errmsg(-1));
98   }
99 
100   android::base::unique_fd fd(open(filename.c_str(), O_RDONLY));
101   if (fd.get() == -1) {
102     return ErrnoErrorf("Error opening {}", filename);
103   }
104 
105   Elf* elf = elf_begin(fd.get(), ELF_C_READ, /*ref=*/nullptr);
106   if (elf == nullptr) {
107     return Errorf("Error creating ELF object for {}: {}", filename, elf_errmsg(-1));
108   }
109   auto elf_cleanup = android::base::make_scope_guard([&]() { elf_end(elf); });
110 
111   std::vector<std::string> libs;
112 
113   // Find the dynamic section.
114   for (Elf_Scn* dyn_scn = nullptr; (dyn_scn = elf_nextscn(elf, dyn_scn)) != nullptr;) {
115     GElf_Shdr scn_hdr;
116     if (gelf_getshdr(dyn_scn, &scn_hdr) != &scn_hdr) {
117       return Errorf("Failed to retrieve ELF section header in {}: {}", filename, elf_errmsg(-1));
118     }
119 
120     if (scn_hdr.sh_type == SHT_DYNAMIC) {
121       Elf_Data* data = elf_getdata(dyn_scn, /*data=*/nullptr);
122 
123       // Iterate through dynamic section entries.
124       for (int i = 0; i < scn_hdr.sh_size / scn_hdr.sh_entsize; i++) {
125         GElf_Dyn dyn_entry;
126         if (gelf_getdyn(data, i, &dyn_entry) != &dyn_entry) {
127           return Errorf("Failed to get entry {} in ELF dynamic section of {}: {}",
128                         i,
129                         filename,
130                         elf_errmsg(-1));
131         }
132 
133         if (dyn_entry.d_tag == DT_NEEDED) {
134           const char* lib_name = elf_strptr(elf, scn_hdr.sh_link, dyn_entry.d_un.d_val);
135           if (lib_name == nullptr) {
136             return Errorf("Failed to get string from entry {} in ELF dynamic section of {}: {}",
137                           i,
138                           filename,
139                           elf_errmsg(-1));
140           }
141           libs.push_back(lib_name);
142         }
143       }
144       break;  // Found the dynamic section, no need to continue.
145     }
146   }
147 
148   return libs;
149 }
150 
151 }  // namespace
152 
TEST(StandaloneTestAllowedLibDeps,test)153 TEST(StandaloneTestAllowedLibDeps, test) {
154   Result<std::string> path_to_self = GetCurrentElfObjectPath();
155   ASSERT_RESULT_OK(path_to_self);
156   Result<std::vector<std::string>> dyn_lib_deps = GetDynamicLibDeps(path_to_self.value());
157   ASSERT_RESULT_OK(dyn_lib_deps);
158 
159   // Allow .so files in the same directory as the test binary, for shared libs
160   // pushed with the test using `data_libs`.
161   std::filesystem::path self_dir = std::filesystem::path(path_to_self.value()).parent_path();
162   std::vector<std::string> test_libs;
163   for (const std::filesystem::directory_entry& entry :
164        std::filesystem::directory_iterator(self_dir)) {
165     if (entry.is_regular_file() && entry.path().extension() == ".so") {
166       test_libs.push_back(entry.path().filename());
167     }
168   }
169 
170   std::vector<std::string> disallowed_libs;
171   for (const std::string& dyn_lib_dep : dyn_lib_deps.value()) {
172     if (std::find(std::begin(kAllowedDynamicLibDeps),
173                   std::end(kAllowedDynamicLibDeps),
174                   dyn_lib_dep) != std::end(kAllowedDynamicLibDeps)) {
175       continue;
176     }
177     if (art::ContainsElement(test_libs, dyn_lib_dep)) {
178       continue;
179     }
180     disallowed_libs.push_back(dyn_lib_dep);
181   }
182 
183   EXPECT_THAT(disallowed_libs, testing::IsEmpty())
184       << path_to_self.value() << " has disallowed shared library dependencies.";
185 }
186