xref: /aosp_15_r20/art/dexopt_chroot_setup/dexopt_chroot_setup_test.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 #include "dexopt_chroot_setup.h"
18 
19 #include <unistd.h>
20 
21 #include <cstring>
22 #include <filesystem>
23 #include <string>
24 #include <string_view>
25 #include <utility>
26 
27 #include "aidl/com/android/server/art/BnDexoptChrootSetup.h"
28 #include "android-base/file.h"
29 #include "android-base/properties.h"
30 #include "android-base/result-gmock.h"
31 #include "android-base/scopeguard.h"
32 #include "android/binder_auto_utils.h"
33 #include "base/common_art_test.h"
34 #include "base/macros.h"
35 #include "exec_utils.h"
36 #include "gmock/gmock.h"
37 #include "gtest/gtest.h"
38 #include "tools/binder_utils.h"
39 #include "tools/cmdline_builder.h"
40 
41 namespace art {
42 namespace dexopt_chroot_setup {
43 namespace {
44 
45 using ::android::base::GetProperty;
46 using ::android::base::ScopeGuard;
47 using ::android::base::SetProperty;
48 using ::android::base::WaitForProperty;
49 using ::android::base::testing::HasError;
50 using ::android::base::testing::HasValue;
51 using ::android::base::testing::WithMessage;
52 using ::art::tools::CmdlineBuilder;
53 
54 class DexoptChrootSetupTest : public CommonArtTest {
55  protected:
SetUp()56   void SetUp() override {
57     CommonArtTest::SetUp();
58     dexopt_chroot_setup_ = ndk::SharedRefBase::make<DexoptChrootSetup>();
59 
60     // Note that if a real Pre-reboot Dexopt is kicked off after this check, the test will still
61     // fail, but that should be very rare.
62     if (std::filesystem::exists(DexoptChrootSetup::CHROOT_DIR)) {
63       GTEST_SKIP() << "A real Pre-reboot Dexopt is running";
64     }
65 
66     ASSERT_TRUE(
67         WaitForProperty("dev.bootcomplete", "1", /*relative_timeout=*/std::chrono::minutes(3)));
68 
69     test_skipped_ = false;
70 
71     scratch_dir_ = std::make_unique<ScratchDir>();
72     scratch_path_ = scratch_dir_->GetPath();
73     // Remove the trailing '/';
74     scratch_path_.resize(scratch_path_.length() - 1);
75 
76     partitions_sysprop_value_ = GetProperty(kAdditionalPartitionsSysprop, /*default_value=*/"");
77     ASSERT_TRUE(SetProperty(kAdditionalPartitionsSysprop, "odm:/odm,system_dlkm:/system_dlkm"));
78     partitions_sysprop_set_ = true;
79   }
80 
TearDown()81   void TearDown() override {
82     if (test_skipped_) {
83       return;
84     }
85     if (partitions_sysprop_set_ &&
86         !SetProperty(kAdditionalPartitionsSysprop, partitions_sysprop_value_)) {
87       LOG(ERROR) << ART_FORMAT("Failed to recover sysprop '{}'", kAdditionalPartitionsSysprop);
88     }
89     scratch_dir_.reset();
90     dexopt_chroot_setup_->tearDown(/*in_allowConcurrent=*/false);
91     CommonArtTest::TearDown();
92   }
93 
94   std::shared_ptr<DexoptChrootSetup> dexopt_chroot_setup_;
95   std::unique_ptr<ScratchDir> scratch_dir_;
96   std::string scratch_path_;
97   bool test_skipped_ = true;
98   std::string partitions_sysprop_value_;
99   bool partitions_sysprop_set_ = false;
100 };
101 
TEST_F(DexoptChrootSetupTest,Run)102 TEST_F(DexoptChrootSetupTest, Run) {
103   // We only test the Mainline update case here. There isn't an easy way to test the OTA update case
104   // in such a unit test. The OTA update case is assumed to be covered by the E2E test.
105   ASSERT_STATUS_OK(
106       dexopt_chroot_setup_->setUp(/*in_otaSlot=*/std::nullopt, /*in_mapSnapshotsForOta=*/false));
107   ASSERT_STATUS_OK(dexopt_chroot_setup_->init());
108 
109   std::string mounts;
110   ASSERT_TRUE(android::base::ReadFileToString("/proc/mounts", &mounts, /*follow_symlinks=*/true));
111 
112   // Some important dirs that should be the same as outside.
113   std::vector<const char*> same_dirs = {
114       "/",
115       "/system",
116       "/system_ext",
117       "/vendor",
118       "/product",
119       "/data",
120       "/mnt/expand",
121       "/dev",
122       "/dev/cpuctl",
123       "/dev/cpuset",
124       "/proc",
125       "/sys",
126       "/sys/fs/cgroup",
127       "/sys/fs/selinux",
128       "/metadata",
129       "/odm",
130       "/system_dlkm",
131   };
132 
133   for (const std::string& dir : same_dirs) {
134     struct stat st_outside;
135     ASSERT_EQ(stat(dir.c_str(), &st_outside), 0);
136     struct stat st_inside;
137     ASSERT_EQ(stat(PathInChroot(dir).c_str(), &st_inside), 0);
138     EXPECT_EQ(std::make_pair(st_outside.st_dev, st_outside.st_ino),
139               std::make_pair(st_inside.st_dev, st_inside.st_ino))
140         << ART_FORMAT("Unexpected different directory in chroot: '{}'\n", dir) << mounts;
141   }
142 
143   // Some important dirs that are expected to be writable.
144   std::vector<const char*> writable_dirs = {
145       "/data",
146       "/mnt/expand",
147   };
148 
149   for (const std::string& dir : writable_dirs) {
150     EXPECT_EQ(access(PathInChroot(dir).c_str(), W_OK), 0);
151   }
152 
153   // Some important dirs that are not the same as outside but should be prepared.
154   std::vector<const char*> prepared_dirs = {
155       "/apex/com.android.art",
156       "/linkerconfig/com.android.art",
157   };
158 
159   for (const std::string& dir : prepared_dirs) {
160     EXPECT_FALSE(std::filesystem::is_empty(PathInChroot(dir)));
161   }
162 
163   EXPECT_TRUE(std::filesystem::is_directory(PathInChroot("/mnt/artd_tmp")));
164 
165   // Check that the chroot environment is capable to run programs. `dex2oat` is arbitrarily picked
166   // here. The test dex file and the scratch dir in /data are the same inside the chroot as outside.
167   CmdlineBuilder args;
168   args.Add(GetArtBinDir() + "/art_exec")
169       .Add("--chroot=%s", DexoptChrootSetup::CHROOT_DIR)
170       .Add("--")
171       .Add(GetArtBinDir() + "/dex2oat" + (Is64BitInstructionSet(kRuntimeISA) ? "64" : "32"))
172       .Add("--dex-file=%s", GetTestDexFileName("Main"))
173       .Add("--oat-file=%s", scratch_path_ + "/output.odex")
174       .Add("--output-vdex=%s", scratch_path_ + "/output.vdex")
175       .Add("--compiler-filter=speed")
176       .Add("--boot-image=/nonx/boot.art");
177   std::string error_msg;
178   EXPECT_TRUE(Exec(args.Get(), &error_msg)) << error_msg;
179 
180   // Check that `setUp` can be repeatedly called, to simulate the case where an instance of the
181   // caller (typically system_server) called `setUp` and crashed later, and a new instance called
182   // `setUp` again.
183   ASSERT_STATUS_OK(
184       dexopt_chroot_setup_->setUp(/*in_otaSlot=*/std::nullopt, /*in_mapSnapshotsForOta=*/false));
185   ASSERT_STATUS_OK(dexopt_chroot_setup_->init());
186 
187   // Check that `init` cannot be repeatedly called.
188   ndk::ScopedAStatus status = dexopt_chroot_setup_->init();
189   EXPECT_FALSE(status.isOk());
190   EXPECT_EQ(status.getExceptionCode(), EX_ILLEGAL_STATE);
191   EXPECT_STREQ(status.getMessage(), "init must not be repeatedly called");
192 
193   ASSERT_STATUS_OK(dexopt_chroot_setup_->tearDown(/*in_allowConcurrent=*/false));
194 
195   EXPECT_FALSE(std::filesystem::exists(DexoptChrootSetup::CHROOT_DIR));
196 
197   // Check that `tearDown` can be repeatedly called too.
198   ASSERT_STATUS_OK(dexopt_chroot_setup_->tearDown(/*in_allowConcurrent=*/false));
199 
200   // Check that `setUp` can be followed directly by a `tearDown`.
201   ASSERT_STATUS_OK(
202       dexopt_chroot_setup_->setUp(/*in_otaSlot=*/std::nullopt, /*in_mapSnapshotsForOta=*/false));
203   ASSERT_STATUS_OK(dexopt_chroot_setup_->tearDown(/*in_allowConcurrent=*/false));
204   EXPECT_FALSE(std::filesystem::exists(DexoptChrootSetup::CHROOT_DIR));
205 }
206 
TEST(DexoptChrootSetupUnitTest,ConstructLinkerConfigCompatEnvSection)207 TEST(DexoptChrootSetupUnitTest, ConstructLinkerConfigCompatEnvSection) {
208   std::string art_linker_config_content = R"(dir.com.android.art = /apex/com.android.art/bin
209 [com.android.art]
210 additional.namespaces = com_android_art,system
211 namespace.default.isolated = true
212 namespace.default.links = com_android_art,system
213 namespace.default.link.com_android_art.allow_all_shared_libs = true
214 namespace.default.link.system.shared_libs = libartpalette-system.so:libbinder_ndk.so:libc.so:libdl.so:libdl_android.so:liblog.so:libm.so
215 namespace.com_android_art.isolated = true
216 namespace.com_android_art.visible = true
217 namespace.com_android_art.search.paths = /apex/com.android.art/${LIB}
218 namespace.com_android_art.permitted.paths = /apex/com.android.art/${LIB}
219 namespace.com_android_art.permitted.paths += /system/${LIB}
220 namespace.com_android_art.permitted.paths += /system_ext/${LIB}
221 namespace.com_android_art.permitted.paths += /data
222 namespace.com_android_art.permitted.paths += /apex/com.android.art/javalib
223 namespace.com_android_art.links = system
224 namespace.com_android_art.link.system.shared_libs = libartpalette-system.so:libbinder_ndk.so:libc.so:libdl.so:libdl_android.so:liblog.so:libm.so
225 namespace.system.isolated = true
226 namespace.system.visible = true
227 namespace.system.search.paths = /system/${LIB}
228 namespace.system.search.paths += /system_ext/${LIB}
229 namespace.system.permitted.paths = /system/${LIB}/drm:/system/${LIB}/extractors:/system/${LIB}/hw
230 namespace.system.permitted.paths += /system_ext/${LIB}
231 namespace.system.permitted.paths += /system/framework
232 namespace.system.permitted.paths += /data
233 namespace.system.permitted.paths += /apex/com.android.runtime/${LIB}/bionic
234 namespace.system.permitted.paths += /system/${LIB}/bootstrap
235 namespace.system.links = com_android_art
236 namespace.system.link.com_android_art.shared_libs = libdexfile.so:libjdwp.so:libnativebridge.so:libnativehelper.so:libnativeloader.so:libsigchain.so
237 [some_other_section]
238 )";
239 
240   std::string expected_compat_env_section = R"([com.android.art.compat]
241 additional.namespaces = com_android_art,system
242 namespace.default.isolated = true
243 namespace.default.links = com_android_art,system
244 namespace.default.link.com_android_art.allow_all_shared_libs = true
245 namespace.default.link.system.shared_libs = libartpalette-system.so:libbinder_ndk.so:libc.so:libdl.so:libdl_android.so:liblog.so:libm.so
246 namespace.com_android_art.isolated = true
247 namespace.com_android_art.visible = true
248 namespace.com_android_art.search.paths = /apex/com.android.art/${LIB}
249 namespace.com_android_art.permitted.paths = /apex/com.android.art/${LIB}
250 namespace.com_android_art.permitted.paths += /mnt/compat_env/system/${LIB}
251 namespace.com_android_art.permitted.paths += /mnt/compat_env/system_ext/${LIB}
252 namespace.com_android_art.permitted.paths += /data
253 namespace.com_android_art.permitted.paths += /apex/com.android.art/javalib
254 namespace.com_android_art.links = system
255 namespace.com_android_art.link.system.shared_libs = libartpalette-system.so:libbinder_ndk.so:libc.so:libdl.so:libdl_android.so:liblog.so:libm.so
256 namespace.system.isolated = true
257 namespace.system.visible = true
258 namespace.system.search.paths = /mnt/compat_env/system/${LIB}
259 namespace.system.search.paths += /mnt/compat_env/system_ext/${LIB}
260 namespace.system.permitted.paths = /mnt/compat_env/system/${LIB}/drm:/mnt/compat_env/system/${LIB}/extractors:/mnt/compat_env/system/${LIB}/hw
261 namespace.system.permitted.paths += /mnt/compat_env/system_ext/${LIB}
262 namespace.system.permitted.paths += /system/framework
263 namespace.system.permitted.paths += /data
264 namespace.system.permitted.paths += /apex/com.android.runtime/${LIB}/bionic
265 namespace.system.permitted.paths += /mnt/compat_env/system/${LIB}/bootstrap
266 namespace.system.links = com_android_art
267 namespace.system.link.com_android_art.shared_libs = libdexfile.so:libjdwp.so:libnativebridge.so:libnativehelper.so:libnativeloader.so:libsigchain.so
268 )";
269 
270   EXPECT_THAT(ConstructLinkerConfigCompatEnvSection(art_linker_config_content),
271               HasValue(expected_compat_env_section));
272 }
273 
TEST(DexoptChrootSetupUnitTest,ConstructLinkerConfigCompatEnvSectionNoMatch)274 TEST(DexoptChrootSetupUnitTest, ConstructLinkerConfigCompatEnvSectionNoMatch) {
275   std::string art_linker_config_content = R"(dir.com.android.art = /apex/com.android.art/bin
276 [com.android.art]
277 additional.namespaces = com_android_art,system
278 namespace.default.isolated = true
279 namespace.default.links = com_android_art,system
280 namespace.default.link.com_android_art.allow_all_shared_libs = true
281 namespace.default.link.system.shared_libs = libartpalette-system.so:libbinder_ndk.so:libc.so:libdl.so:libdl_android.so:liblog.so:libm.so
282 namespace.com_android_art.isolated = true
283 namespace.com_android_art.visible = true
284 namespace.com_android_art.search.paths = /apex/com.android.art/${LIB}
285 namespace.com_android_art.permitted.paths = /apex/com.android.art/${LIB}
286 namespace.com_android_art.permitted.paths += /foo/${LIB}
287 namespace.com_android_art.permitted.paths += /foo_ext/${LIB}
288 namespace.com_android_art.permitted.paths += /data
289 namespace.com_android_art.permitted.paths += /apex/com.android.art/javalib
290 namespace.com_android_art.links = system
291 namespace.com_android_art.link.system.shared_libs = libartpalette-system.so:libbinder_ndk.so:libc.so:libdl.so:libdl_android.so:liblog.so:libm.so
292 namespace.system.isolated = true
293 namespace.system.visible = true
294 namespace.system.search.paths = /foo/${LIB}
295 namespace.system.search.paths += /foo_ext/${LIB}
296 namespace.system.permitted.paths = /foo/${LIB}/drm:/foo/${LIB}/extractors:/foo/${LIB}/hw
297 namespace.system.permitted.paths += /foo_ext/${LIB}
298 namespace.system.permitted.paths += /system/framework
299 namespace.system.permitted.paths += /data
300 namespace.system.permitted.paths += /apex/com.android.runtime/${LIB}/bionic
301 namespace.system.permitted.paths += /foo/${LIB}/bootstrap
302 namespace.system.links = com_android_art
303 namespace.system.link.com_android_art.shared_libs = libdexfile.so:libjdwp.so:libnativebridge.so:libnativehelper.so:libnativeloader.so:libsigchain.so
304 [some_other_section]
305 )";
306 
307   EXPECT_THAT(ConstructLinkerConfigCompatEnvSection(art_linker_config_content),
308               HasError(WithMessage("No matching lines to patch in ART linker config")));
309 }
310 
311 }  // namespace
312 }  // namespace dexopt_chroot_setup
313 }  // namespace art
314