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 
26 #include "aidl/com/android/server/art/BnDexoptChrootSetup.h"
27 #include "android-base/properties.h"
28 #include "android-base/scopeguard.h"
29 #include "android/binder_auto_utils.h"
30 #include "base/common_art_test.h"
31 #include "base/macros.h"
32 #include "exec_utils.h"
33 #include "gmock/gmock.h"
34 #include "gtest/gtest.h"
35 #include "selinux/selinux.h"
36 #include "tools/binder_utils.h"
37 #include "tools/cmdline_builder.h"
38 
39 namespace art {
40 namespace dexopt_chroot_setup {
41 namespace {
42 
43 using ::android::base::ScopeGuard;
44 using ::android::base::WaitForProperty;
45 using ::art::tools::CmdlineBuilder;
46 
47 class DexoptChrootSetupTest : public CommonArtTest {
48  protected:
SetUp()49   void SetUp() override {
50     CommonArtTest::SetUp();
51     dexopt_chroot_setup_ = ndk::SharedRefBase::make<DexoptChrootSetup>();
52 
53     // TODO(jiakaiz): Delete this one the SDK version is bumped to 35.
54     char* con;
55     if (getfilecon(DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR, &con) < 0) {
56       ASSERT_EQ(errno, ENOENT) << ART_FORMAT("Failed to getfilecon '{}': {}",
57                                              DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR,
58                                              strerror(errno));
59       GTEST_SKIP() << ART_FORMAT("This platform is too old and doesn't have directory '{}'",
60                                  DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR);
61     }
62     {
63       auto cleanup = ScopeGuard([&]() { freecon(con); });
64       constexpr std::string_view kExpectedCon = "u:object_r:pre_reboot_dexopt_file:s0";
65       if (con != kExpectedCon) {
66         GTEST_SKIP() << ART_FORMAT("This platform is too old and doesn't have SELinux context '{}'",
67                                    kExpectedCon);
68       }
69     }
70 
71     // Note that if a real Pre-reboot Dexopt is kicked off after this check, the test will still
72     // fail, but that should be very rare.
73     if (std::filesystem::exists(DexoptChrootSetup::CHROOT_DIR)) {
74       GTEST_SKIP() << "A real Pre-reboot Dexopt is running";
75     }
76 
77     ASSERT_TRUE(WaitForProperty("dev.bootcomplete", "1", /*relative_timeout=*/std::chrono::minutes(3)));
78 
79     test_skipped = false;
80 
81     scratch_dir_ = std::make_unique<ScratchDir>();
82     scratch_path_ = scratch_dir_->GetPath();
83     // Remove the trailing '/';
84     scratch_path_.resize(scratch_path_.length() - 1);
85   }
86 
TearDown()87   void TearDown() override {
88     if (test_skipped) {
89       return;
90     }
91     scratch_dir_.reset();
92     dexopt_chroot_setup_->tearDown();
93     CommonArtTest::TearDown();
94   }
95 
96   std::shared_ptr<DexoptChrootSetup> dexopt_chroot_setup_;
97   std::unique_ptr<ScratchDir> scratch_dir_;
98   std::string scratch_path_;
99   bool test_skipped = true;
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   // Some important dirs that should be the same as outside.
110   std::vector<const char*> same_dirs = {
111       "/",
112       "/system",
113       "/system_ext",
114       "/vendor",
115       "/product",
116       "/data",
117       "/mnt/expand",
118       "/dev",
119       "/dev/cpuctl",
120       "/dev/cpuset",
121       "/proc",
122       "/sys",
123       "/sys/fs/cgroup",
124       "/sys/fs/selinux",
125       "/metadata",
126   };
127 
128   for (const std::string& dir : same_dirs) {
129     struct stat st_outside;
130     ASSERT_EQ(stat(dir.c_str(), &st_outside), 0);
131     struct stat st_inside;
132     ASSERT_EQ(stat(PathInChroot(dir).c_str(), &st_inside), 0);
133     EXPECT_EQ(st_outside.st_dev, st_inside.st_dev);
134     EXPECT_EQ(st_outside.st_ino, st_inside.st_ino);
135   }
136 
137   // Some important dirs that are expected to be writable.
138   std::vector<const char*> writable_dirs = {
139       "/data",
140       "/mnt/expand",
141   };
142 
143   for (const std::string& dir : writable_dirs) {
144     EXPECT_EQ(access(PathInChroot(dir).c_str(), W_OK), 0);
145   }
146 
147   // Some important dirs that are not the same as outside but should be prepared.
148   std::vector<const char*> prepared_dirs = {
149       "/apex/com.android.art",
150       "/linkerconfig/com.android.art",
151   };
152 
153   for (const std::string& dir : prepared_dirs) {
154     EXPECT_FALSE(std::filesystem::is_empty(PathInChroot(dir)));
155   }
156 
157   EXPECT_TRUE(std::filesystem::is_directory(PathInChroot("/mnt/artd_tmp")));
158 
159   // Check that the chroot environment is capable to run programs. `dex2oat` is arbitrarily picked
160   // here. The test dex file and the scratch dir in /data are the same inside the chroot as outside.
161   CmdlineBuilder args;
162   args.Add(GetArtBinDir() + "/art_exec")
163       .Add("--chroot=%s", DexoptChrootSetup::CHROOT_DIR)
164       .Add("--")
165       .Add(GetArtBinDir() + "/dex2oat" + (Is64BitInstructionSet(kRuntimeISA) ? "64" : "32"))
166       .Add("--dex-file=%s", GetTestDexFileName("Main"))
167       .Add("--oat-file=%s", scratch_path_ + "/output.odex")
168       .Add("--output-vdex=%s", scratch_path_ + "/output.vdex")
169       .Add("--compiler-filter=speed")
170       .Add("--boot-image=/nonx/boot.art");
171   std::string error_msg;
172   EXPECT_TRUE(Exec(args.Get(), &error_msg)) << error_msg;
173 
174   // Check that `setUp` can be repetitively called, to simulate the case where an instance of the
175   // caller (typically system_server) called `setUp` and crashed later, and a new instance called
176   // `setUp` again.
177   ASSERT_STATUS_OK(
178       dexopt_chroot_setup_->setUp(/*in_otaSlot=*/std::nullopt, /*in_mapSnapshotsForOta=*/false));
179   ASSERT_STATUS_OK(dexopt_chroot_setup_->init());
180 
181   ASSERT_STATUS_OK(dexopt_chroot_setup_->tearDown());
182 
183   EXPECT_FALSE(std::filesystem::exists(DexoptChrootSetup::CHROOT_DIR));
184 
185   // Check that `tearDown` can be repetitively called too.
186   ASSERT_STATUS_OK(dexopt_chroot_setup_->tearDown());
187 
188   // Check that `setUp` can be followed directly by a `tearDown`.
189   ASSERT_STATUS_OK(
190       dexopt_chroot_setup_->setUp(/*in_otaSlot=*/std::nullopt, /*in_mapSnapshotsForOta=*/false));
191   ASSERT_STATUS_OK(dexopt_chroot_setup_->tearDown());
192   EXPECT_FALSE(std::filesystem::exists(DexoptChrootSetup::CHROOT_DIR));
193 }
194 
195 }  // namespace
196 }  // namespace dexopt_chroot_setup
197 }  // namespace art
198