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 <linux/mount.h>
20 #include <sched.h>
21 #include <sys/mount.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24
25 #include <algorithm>
26 #include <cerrno>
27 #include <chrono>
28 #include <cstring>
29 #include <filesystem>
30 #include <iterator>
31 #include <mutex>
32 #include <optional>
33 #include <regex>
34 #include <string>
35 #include <string_view>
36 #include <system_error>
37 #include <tuple>
38 #include <vector>
39
40 #include "aidl/com/android/server/art/BnDexoptChrootSetup.h"
41 #include "android-base/errors.h"
42 #include "android-base/file.h"
43 #include "android-base/logging.h"
44 #include "android-base/no_destructor.h"
45 #include "android-base/properties.h"
46 #include "android-base/result.h"
47 #include "android-base/scopeguard.h"
48 #include "android-base/strings.h"
49 #include "android-base/unique_fd.h"
50 #include "android/binder_auto_utils.h"
51 #include "android/binder_manager.h"
52 #include "android/binder_process.h"
53 #include "base/file_utils.h"
54 #include "base/macros.h"
55 #include "base/os.h"
56 #include "base/stl_util.h"
57 #include "base/utils.h"
58 #include "exec_utils.h"
59 #include "fstab/fstab.h"
60 #include "tools/binder_utils.h"
61 #include "tools/cmdline_builder.h"
62 #include "tools/tools.h"
63
64 namespace art {
65 namespace dexopt_chroot_setup {
66
67 namespace {
68
69 using ::android::base::ConsumePrefix;
70 using ::android::base::Error;
71 using ::android::base::GetProperty;
72 using ::android::base::Join;
73 using ::android::base::make_scope_guard;
74 using ::android::base::NoDestructor;
75 using ::android::base::ReadFileToString;
76 using ::android::base::Readlink;
77 using ::android::base::Result;
78 using ::android::base::SetProperty;
79 using ::android::base::Split;
80 using ::android::base::Tokenize;
81 using ::android::base::unique_fd;
82 using ::android::base::WaitForProperty;
83 using ::android::base::WriteStringToFile;
84 using ::android::fs_mgr::FstabEntry;
85 using ::art::tools::CmdlineBuilder;
86 using ::art::tools::Fatal;
87 using ::art::tools::GetProcMountsDescendantsOfPath;
88 using ::art::tools::NonFatal;
89 using ::art::tools::PathStartsWith;
90 using ::ndk::ScopedAStatus;
91
92 constexpr const char* kServiceName = "dexopt_chroot_setup";
93 const NoDestructor<std::string> kBindMountTmpDir(
94 std::string(DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR) + "/mount_tmp");
95 const NoDestructor<std::string> kOtaSlotFile(std::string(DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR) +
96 "/ota_slot");
97 const NoDestructor<std::string> kSnapshotMappedFile(
98 std::string(DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR) + "/snapshot_mapped");
99 constexpr mode_t kChrootDefaultMode = 0755;
100 constexpr std::chrono::milliseconds kSnapshotCtlTimeout = std::chrono::seconds(60);
101 constexpr std::array<const char*, 4> kExternalLibDirs = {
102 "/system/lib", "/system/lib64", "/system_ext/lib", "/system_ext/lib64"};
103
IsOtaUpdate(const std::optional<std::string> & ota_slot)104 bool IsOtaUpdate(const std::optional<std::string>& ota_slot) { return ota_slot.has_value(); }
105
Run(std::string_view log_name,const std::vector<std::string> & args)106 Result<void> Run(std::string_view log_name, const std::vector<std::string>& args) {
107 LOG(INFO) << "Running " << log_name << ": " << Join(args, /*separator=*/" ");
108
109 std::string error_msg;
110 if (!Exec(args, &error_msg)) {
111 return Errorf("Failed to run {}: {}", log_name, error_msg);
112 }
113
114 LOG(INFO) << log_name << " returned code 0";
115 return {};
116 }
117
GetArtExecCmdlineBuilder()118 Result<CmdlineBuilder> GetArtExecCmdlineBuilder() {
119 std::string error_msg;
120 std::string art_root = GetArtRootSafe(&error_msg);
121 if (!error_msg.empty()) {
122 return Error() << error_msg;
123 }
124 CmdlineBuilder args;
125 args.Add(art_root + "/bin/art_exec")
126 .Add("--chroot=%s", DexoptChrootSetup::CHROOT_DIR)
127 .Add("--process-name-suffix=Pre-reboot Dexopt chroot");
128 return args;
129 }
130
CreateDir(const std::string & path)131 Result<void> CreateDir(const std::string& path) {
132 std::error_code ec;
133 std::filesystem::create_directory(path, ec);
134 if (ec) {
135 return Errorf("Failed to create dir '{}': {}", path, ec.message());
136 }
137 return {};
138 }
139
IsSymlink(const std::string & path)140 Result<bool> IsSymlink(const std::string& path) {
141 std::error_code ec;
142 bool res = std::filesystem::is_symlink(path, ec);
143 if (ec) {
144 return Errorf("Failed to create dir '{}': {}", path, ec.message());
145 }
146 return res;
147 }
148
IsSelfOrParentSymlink(const std::string & path)149 Result<bool> IsSelfOrParentSymlink(const std::string& path) {
150 // We don't use `Realpath` because it does a `stat(2)` call which requires the SELinux "getattr"
151 // permission. which we don't have on all mount points.
152 unique_fd fd(open(path.c_str(), O_PATH | O_CLOEXEC));
153 if (fd.get() < 0) {
154 return ErrnoErrorf("Failed to open '{}' to resolve real path", path);
155 }
156 std::string real_path;
157 if (!Readlink(ART_FORMAT("/proc/self/fd/{}", fd.get()), &real_path)) {
158 return ErrnoErrorf("Failed to resolve real path for '{}'", path);
159 }
160 return path != real_path;
161 }
162
Unmount(const std::string & target,bool logging=true)163 Result<void> Unmount(const std::string& target, bool logging = true) {
164 if (umount2(target.c_str(), UMOUNT_NOFOLLOW) == 0) {
165 LOG_IF(INFO, logging) << ART_FORMAT("Unmounted '{}'", target);
166 return {};
167 }
168 LOG(WARNING) << ART_FORMAT(
169 "Failed to umount2 '{}': {}. Retrying with MNT_DETACH", target, strerror(errno));
170 if (umount2(target.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) == 0) {
171 LOG_IF(INFO, logging) << ART_FORMAT("Unmounted '{}' with MNT_DETACH", target);
172 return {};
173 }
174 return ErrnoErrorf("Failed to umount2 '{}'", target);
175 }
176
177 // Bind-mounts `source` at `target` with the mount propagation type being "shared". You generally
178 // want to use `BindMount` instead.
179 //
180 // `BindMountDirect` is safe to use only if there is no child mount points under `target`. DO NOT
181 // mount or unmount under `target` because mount events propagate to `source`.
BindMountDirect(const std::string & source,const std::string & target)182 Result<void> BindMountDirect(const std::string& source, const std::string& target) {
183 // Don't follow symlinks.
184 CHECK(!OR_RETURN(IsSelfOrParentSymlink(target))) << target;
185 if (mount(source.c_str(),
186 target.c_str(),
187 /*fs_type=*/nullptr,
188 MS_BIND,
189 /*data=*/nullptr) != 0) {
190 return ErrnoErrorf("Failed to bind-mount '{}' at '{}'", source, target);
191 }
192 LOG(INFO) << ART_FORMAT("Bind-mounted '{}' at '{}'", source, target);
193 return {};
194 }
195
196 // Bind-mounts `source` at `target` with the mount propagation type being "slave+shared".
BindMount(const std::string & source,const std::string & target)197 Result<void> BindMount(const std::string& source, const std::string& target) {
198 // Don't bind-mount repeatedly.
199 CHECK(!PathStartsWith(source, DexoptChrootSetup::CHROOT_DIR));
200 // Don't follow symlinks.
201 CHECK(!OR_RETURN(IsSelfOrParentSymlink(target))) << target;
202 // system_server has a different mount namespace from init, and it uses slave mounts. E.g:
203 //
204 // a: init mount ns: shared(1): /foo
205 // b: init mount ns: shared(2): /mnt
206 // c: SS mount ns: slave(1): /foo
207 // d: SS mount ns: slave(2): /mnt
208 //
209 // We create our chroot setup in the init namespace but also want it to appear inside the
210 // system_server one, since we need to access some files in it from system_server (in particular
211 // service-art.jar).
212 //
213 // Hence we want the mount propagation type to be "slave+shared": Slave of the init namespace so
214 // that unmounts in the chroot doesn't affect the rest of the system, while at the same time
215 // shared with the system_server namespace so that it gets the same mounts recursively in the
216 // chroot tree. This can be achieved in 4 steps:
217 //
218 // 1. Bind-mount /foo at a temp mount point /mnt/pre_reboot_dexopt/mount_tmp.
219 // a: init mount ns: shared(1): /foo
220 // b: init mount ns: shared(2): /mnt
221 // e: init mount ns: shared(1): /mnt/pre_reboot_dexopt/mount_tmp
222 // c: SS mount ns: slave(1): /foo
223 // d: SS mount ns: slave(2): /mnt
224 // f: SS mount ns: slave(1): /mnt/pre_reboot_dexopt/mount_tmp
225 //
226 // 2. Make the temp mount point slave.
227 // a: init mount ns: shared(1): /foo
228 // b: init mount ns: shared(2): /mnt
229 // e: init mount ns: slave(1): /mnt/pre_reboot_dexopt/mount_tmp
230 // c: SS mount ns: slave(1): /foo
231 // d: SS mount ns: slave(2): /mnt
232 // f: SS mount ns: slave(1): /mnt/pre_reboot_dexopt/mount_tmp
233 //
234 // 3. Bind-mount the temp mount point at /mnt/pre_reboot_dexopt/chroot/foo. (The new mount point
235 // gets "slave+shared". It gets "slave" because the source (`e`) is "slave", and it gets
236 // "shared" because the dest (`b`) is "shared".)
237 // a: init mount ns: shared(1): /foo
238 // b: init mount ns: shared(2): /mnt
239 // e: init mount ns: slave(1): /mnt/pre_reboot_dexopt/mount_tmp
240 // g: init mount ns: slave(1),shared(3): /mnt/pre_reboot_dexopt/chroot/foo
241 // b: SS mount ns: slave(1): /foo
242 // d: SS mount ns: slave(2): /mnt
243 // f: SS mount ns: slave(1): /mnt/pre_reboot_dexopt/mount_tmp
244 // h: SS mount ns: slave(3): /mnt/pre_reboot_dexopt/chroot/foo
245 //
246 // 4. Unmount the temp mount point.
247 // a: init mount ns: shared(1): /foo
248 // b: init mount ns: shared(2): /mnt
249 // g: init mount ns: slave(1),shared(3): /mnt/pre_reboot_dexopt/chroot/foo
250 // b: SS mount ns: slave(1): /foo
251 // d: SS mount ns: slave(2): /mnt
252 // h: SS mount ns: slave(3): /mnt/pre_reboot_dexopt/chroot/foo
253 //
254 // At this point, we have achieved what we want. `g` is a slave of `a` so that unmounts in `g`
255 // doesn't affect `a`, and `g` is shared with `h` so that mounts in `g` are propagated to `h`.
256 OR_RETURN(CreateDir(*kBindMountTmpDir));
257 if (mount(source.c_str(),
258 kBindMountTmpDir->c_str(),
259 /*fs_type=*/nullptr,
260 MS_BIND,
261 /*data=*/nullptr) != 0) {
262 return ErrnoErrorf("Failed to bind-mount '{}' at '{}' ('{}' -> '{}')",
263 source,
264 *kBindMountTmpDir,
265 source,
266 target);
267 }
268 auto cleanup = make_scope_guard([&]() {
269 Result<void> result = Unmount(*kBindMountTmpDir, /*logging=*/false);
270 if (!result.ok()) {
271 LOG(ERROR) << result.error().message();
272 }
273 });
274 if (mount(/*source=*/nullptr,
275 kBindMountTmpDir->c_str(),
276 /*fs_type=*/nullptr,
277 MS_SLAVE,
278 /*data=*/nullptr) != 0) {
279 return ErrnoErrorf(
280 "Failed to make mount slave for '{}' ('{}' -> '{}')", *kBindMountTmpDir, source, target);
281 }
282 if (mount(kBindMountTmpDir->c_str(),
283 target.c_str(),
284 /*fs_type=*/nullptr,
285 MS_BIND,
286 /*data=*/nullptr) != 0) {
287 return ErrnoErrorf("Failed to bind-mount '{}' at '{}' ('{}' -> '{}')",
288 *kBindMountTmpDir,
289 target,
290 source,
291 target);
292 }
293 LOG(INFO) << ART_FORMAT("Bind-mounted '{}' at '{}'", source, target);
294 return {};
295 }
296
BindMountRecursive(const std::string & source,const std::string & target)297 Result<void> BindMountRecursive(const std::string& source, const std::string& target) {
298 CHECK(!source.ends_with('/'));
299 OR_RETURN(BindMount(source, target));
300
301 // Mount and make slave one by one. Do not use MS_REC because we don't want to mount a child if
302 // the parent cannot be slave (i.e., is shared). Otherwise, unmount events will be undesirably
303 // propagated to the source. For example, if "/dev" and "/dev/pts" are mounted at "/chroot/dev"
304 // and "/chroot/dev/pts" respectively, and "/chroot/dev" is shared, then unmounting
305 // "/chroot/dev/pts" will also unmount "/dev/pts".
306 //
307 // The list is in mount order.
308 std::vector<FstabEntry> entries = OR_RETURN(GetProcMountsDescendantsOfPath(source));
309 for (const FstabEntry& entry : entries) {
310 CHECK(!entry.mount_point.ends_with('/'));
311 std::string_view sub_dir = entry.mount_point;
312 CHECK(ConsumePrefix(&sub_dir, source));
313 if (sub_dir.empty()) {
314 // `source` itself. Already mounted.
315 continue;
316 }
317 if (Result<void> result = BindMount(entry.mount_point, std::string(target).append(sub_dir));
318 !result.ok()) {
319 // Match paths for the "u:object_r:apk_tmp_file:s0" file context in
320 // system/sepolicy/private/file_contexts.
321 std::regex apk_tmp_file_re(R"re((/data|/mnt/expand/[^/]+)/app/vmdl[^/]+\.tmp(/.*)?)re");
322 std::smatch match;
323 if (std::regex_match(entry.mount_point, match, apk_tmp_file_re)) {
324 // Don't bother. The mount point is a temporary directory created by Package Manager during
325 // app install. We won't be able to dexopt the app there anyway because it's not in the
326 // Package Manager's snapshot.
327 LOG(INFO) << ART_FORMAT("Skipped temporary mount point '{}'", entry.mount_point);
328 continue;
329 }
330 return result;
331 }
332 }
333 return {};
334 }
335
GetBlockDeviceName(const std::string & partition,const std::string & slot)336 std::string GetBlockDeviceName(const std::string& partition, const std::string& slot) {
337 return ART_FORMAT("/dev/block/mapper/{}{}", partition, slot);
338 }
339
GetSupportedFilesystems()340 Result<std::vector<std::string>> GetSupportedFilesystems() {
341 std::string content;
342 if (!ReadFileToString("/proc/filesystems", &content)) {
343 return ErrnoErrorf("Failed to read '/proc/filesystems'");
344 }
345 std::vector<std::string> filesystems;
346 for (const std::string& line : Split(content, "\n")) {
347 std::vector<std::string> tokens = Tokenize(line, " \t");
348 // If there are two tokens, the first token is a "nodev" mark, meaning it's not for a block
349 // device, so we skip it.
350 if (tokens.size() == 1) {
351 filesystems.push_back(tokens[0]);
352 }
353 }
354 // Prioritize the filesystems that are known to behave correctly, just in case some bad
355 // filesystems are unexpectedly happy to mount volumes that aren't of their types. We have never
356 // seen this case in practice though.
357 constexpr const char* kWellKnownFilesystems[] = {"erofs", "ext4"};
358 for (const char* well_known_fs : kWellKnownFilesystems) {
359 auto it = std::find(filesystems.begin(), filesystems.end(), well_known_fs);
360 if (it != filesystems.end()) {
361 filesystems.erase(it);
362 filesystems.insert(filesystems.begin(), well_known_fs);
363 }
364 }
365 return filesystems;
366 }
367
Mount(const std::string & block_device,const std::string & target,bool is_optional)368 Result<void> Mount(const std::string& block_device, const std::string& target, bool is_optional) {
369 static const NoDestructor<Result<std::vector<std::string>>> supported_filesystems(
370 GetSupportedFilesystems());
371 if (!supported_filesystems->ok()) {
372 return supported_filesystems->error();
373 }
374 std::vector<std::string> error_msgs;
375 for (const std::string& filesystem : supported_filesystems->value()) {
376 if (mount(block_device.c_str(),
377 target.c_str(),
378 filesystem.c_str(),
379 MS_RDONLY,
380 /*data=*/nullptr) == 0) {
381 // Success.
382 LOG(INFO) << ART_FORMAT(
383 "Mounted '{}' at '{}' with type '{}'", block_device, target, filesystem);
384 return {};
385 } else {
386 if (errno == ENOENT && is_optional) {
387 LOG(INFO) << ART_FORMAT("Skipped non-existing block device '{}'", block_device);
388 return {};
389 }
390 error_msgs.push_back(ART_FORMAT("Tried '{}': {}", filesystem, strerror(errno)));
391 if (errno != EINVAL && errno != EBUSY) {
392 // If the filesystem type is wrong, `errno` must be either `EINVAL` or `EBUSY`. For example,
393 // we've seen that trying to mount a device with a wrong filesystem type yields `EBUSY` if
394 // the device is also mounted elsewhere, though we can't find any document about this
395 // behavior.
396 break;
397 }
398 }
399 }
400 return Errorf("Failed to mount '{}' at '{}':\n{}", block_device, target, Join(error_msgs, '\n'));
401 }
402
MountTmpfs(const std::string & target,std::string_view se_context)403 Result<void> MountTmpfs(const std::string& target, std::string_view se_context) {
404 if (mount(/*source=*/"tmpfs",
405 target.c_str(),
406 /*fs_type=*/"tmpfs",
407 MS_NODEV | MS_NOEXEC | MS_NOSUID,
408 ART_FORMAT("mode={:#o},rootcontext={}", kChrootDefaultMode, se_context).c_str()) != 0) {
409 return ErrnoErrorf("Failed to mount tmpfs at '{}'", target);
410 }
411 return {};
412 }
413
LoadOtaSlotFile()414 Result<std::optional<std::string>> LoadOtaSlotFile() {
415 std::string content;
416 if (!ReadFileToString(*kOtaSlotFile, &content)) {
417 return ErrnoErrorf("Failed to read '{}'", *kOtaSlotFile);
418 }
419 if (content == "_a" || content == "_b") {
420 return content;
421 }
422 if (content.empty()) {
423 return std::nullopt;
424 }
425 return Errorf("Invalid content of '{}': '{}'", *kOtaSlotFile, content);
426 }
427
PatchLinkerConfigForCompatEnv()428 Result<void> PatchLinkerConfigForCompatEnv() {
429 std::string art_linker_config_content;
430 if (!ReadFileToString(PathInChroot("/linkerconfig/com.android.art/ld.config.txt"),
431 &art_linker_config_content)) {
432 return ErrnoErrorf("Failed to read ART linker config");
433 }
434
435 std::string compat_section =
436 OR_RETURN(ConstructLinkerConfigCompatEnvSection(art_linker_config_content));
437
438 // Append the patched section to the global linker config. Because the compat env path doesn't
439 // start with "/apex", the global linker config is the one that takes effect.
440 std::string global_linker_config_path = PathInChroot("/linkerconfig/ld.config.txt");
441 std::string global_linker_config_content;
442 if (!ReadFileToString(global_linker_config_path, &global_linker_config_content)) {
443 return ErrnoErrorf("Failed to read global linker config");
444 }
445
446 if (!WriteStringToFile("dir.com.android.art.compat = /mnt/compat_env/apex/com.android.art/bin\n" +
447 global_linker_config_content + compat_section,
448 global_linker_config_path)) {
449 return ErrnoErrorf("Failed to write global linker config");
450 }
451
452 LOG(INFO) << "Patched " << global_linker_config_path;
453 return {};
454 }
455
456 // Platform libraries communicate with things outside of chroot through unstable APIs. Examples are
457 // `libbinder_ndk.so` talking to `servicemanager` and `libcgrouprc.so` reading
458 // `/dev/cgroup_info/cgroup.rc`. To work around incompatibility issues, we bind-mount the old
459 // platform library directories into chroot so that both sides of a communication are old and
460 // therefore align with each other.
461 // After bind-mounting old platform libraries, the chroot environment has a combination of new
462 // modules and old platform libraries. We currently use the new linker config in such an
463 // environment, which is potentially problematic. If we start to see problems, we should consider
464 // generating a more correct linker config in a more complex way.
PrepareExternalLibDirs()465 Result<void> PrepareExternalLibDirs() {
466 std::vector<const char*> existing_lib_dirs;
467 std::copy_if(kExternalLibDirs.begin(),
468 kExternalLibDirs.end(),
469 std::back_inserter(existing_lib_dirs),
470 OS::DirectoryExists);
471 if (existing_lib_dirs.empty()) {
472 return Errorf("Unexpectedly missing platform library directories. Tried '{}'",
473 android::base::Join(kExternalLibDirs, "', '"));
474 }
475
476 // We should bind-mount all existing lib dirs or none of them. Try the first one to decide what
477 // to do next.
478 Result<void> result = BindMount(existing_lib_dirs[0], PathInChroot(existing_lib_dirs[0]));
479 if (result.ok()) {
480 for (size_t i = 1; i < existing_lib_dirs.size(); i++) {
481 OR_RETURN(BindMount(existing_lib_dirs[i], PathInChroot(existing_lib_dirs[i])));
482 }
483 } else if (result.error().code() == EACCES) {
484 // We don't have the permission to do so on V. Fall back to bind-mounting elsewhere.
485 LOG(WARNING) << result.error().message();
486
487 OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env")));
488 OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env/system")));
489 OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env/system_ext")));
490 OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env/apex")));
491 OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env/apex/com.android.art")));
492 OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env/apex/com.android.art/bin")));
493 OR_RETURN(BindMountDirect(PathInChroot("/apex/com.android.art/bin"),
494 PathInChroot("/mnt/compat_env/apex/com.android.art/bin")));
495 for (const char* lib_dir : existing_lib_dirs) {
496 OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env") + lib_dir));
497 OR_RETURN(BindMountDirect(lib_dir, PathInChroot("/mnt/compat_env") + lib_dir));
498 }
499
500 OR_RETURN(PatchLinkerConfigForCompatEnv());
501 } else {
502 return result;
503 }
504
505 return {};
506 }
507
508 } // namespace
509
setUp(const std::optional<std::string> & in_otaSlot,bool in_mapSnapshotsForOta)510 ScopedAStatus DexoptChrootSetup::setUp(const std::optional<std::string>& in_otaSlot,
511 bool in_mapSnapshotsForOta) {
512 if (!mu_.try_lock()) {
513 return Fatal("Unexpected concurrent calls");
514 }
515 std::lock_guard<std::mutex> lock(mu_, std::adopt_lock);
516
517 if (in_otaSlot.has_value() && (in_otaSlot.value() != "_a" && in_otaSlot.value() != "_b")) {
518 return Fatal(ART_FORMAT("Invalid OTA slot '{}'", in_otaSlot.value()));
519 }
520 OR_RETURN_NON_FATAL(SetUpChroot(in_otaSlot, in_mapSnapshotsForOta));
521 return ScopedAStatus::ok();
522 }
523
init()524 ScopedAStatus DexoptChrootSetup::init() {
525 if (!mu_.try_lock()) {
526 return Fatal("Unexpected concurrent calls");
527 }
528 std::lock_guard<std::mutex> lock(mu_, std::adopt_lock);
529
530 if (OS::FileExists(PathInChroot("/linkerconfig/ld.config.txt").c_str())) {
531 return Fatal("init must not be repeatedly called");
532 }
533
534 OR_RETURN_NON_FATAL(InitChroot());
535 return ScopedAStatus::ok();
536 }
537
tearDown(bool in_allowConcurrent)538 ScopedAStatus DexoptChrootSetup::tearDown(bool in_allowConcurrent) {
539 if (in_allowConcurrent) {
540 // Normally, we don't expect concurrent calls, but this method may be called upon system server
541 // restart when another call initiated by the previous system_server instance is still being
542 // processed.
543 mu_.lock();
544 } else {
545 if (!mu_.try_lock()) {
546 return Fatal("Unexpected concurrent calls");
547 }
548 }
549 std::lock_guard<std::mutex> lock(mu_, std::adopt_lock);
550
551 OR_RETURN_NON_FATAL(TearDownChroot());
552 return ScopedAStatus::ok();
553 }
554
Start()555 Result<void> DexoptChrootSetup::Start() {
556 ScopedAStatus status = ScopedAStatus::fromStatus(
557 AServiceManager_registerLazyService(this->asBinder().get(), kServiceName));
558 if (!status.isOk()) {
559 return Error() << status.getDescription();
560 }
561
562 ABinderProcess_startThreadPool();
563
564 return {};
565 }
566
SetUpChroot(const std::optional<std::string> & ota_slot,bool map_snapshots_for_ota) const567 Result<void> DexoptChrootSetup::SetUpChroot(const std::optional<std::string>& ota_slot,
568 bool map_snapshots_for_ota) const {
569 // Set the default permission mode for new files and dirs to be `kChrootDefaultMode`.
570 umask(~kChrootDefaultMode & 0777);
571
572 // In case there is some leftover.
573 OR_RETURN(TearDownChroot());
574
575 // Prepare the root dir of chroot. The parent directory has been created by init (see `init.rc`).
576 OR_RETURN(CreateDir(CHROOT_DIR));
577 LOG(INFO) << ART_FORMAT("Created '{}'", CHROOT_DIR);
578
579 std::vector<std::tuple<std::string, std::string>> additional_system_partitions = {
580 {"system_ext", "/system_ext"},
581 {"vendor", "/vendor"},
582 {"product", "/product"},
583 };
584
585 std::string partitions_from_sysprop =
586 GetProperty(kAdditionalPartitionsSysprop, /*default_value=*/"");
587 std::vector<std::string_view> partitions_from_sysprop_entries;
588 art::Split(partitions_from_sysprop, ',', &partitions_from_sysprop_entries);
589 for (std::string_view entry : partitions_from_sysprop_entries) {
590 std::vector<std::string_view> pair;
591 art::Split(entry, ':', &pair);
592 if (pair.size() != 2 || pair[0].empty() || pair[1].empty() || !pair[1].starts_with('/')) {
593 return Errorf("Malformed entry in '{}': '{}'", kAdditionalPartitionsSysprop, entry);
594 }
595 additional_system_partitions.emplace_back(std::string(pair[0]), std::string(pair[1]));
596 }
597
598 if (!IsOtaUpdate(ota_slot)) { // Mainline update
599 OR_RETURN(BindMount("/", CHROOT_DIR));
600 // Normally, we don't need to bind-mount "/system" because it's a part of the image mounted at
601 // "/". However, when readonly partitions are remounted read-write, an overlay is created at
602 // "/system", so we need to bind-mount "/system" to handle this case. On devices where readonly
603 // partitions are not remounted, bind-mounting "/system" doesn't hurt.
604 OR_RETURN(BindMount("/system", PathInChroot("/system")));
605 for (const auto& [partition, mount_point] : additional_system_partitions) {
606 // Some additional partitions are optional. On a device where an additional partition doesn't
607 // exist, the mount point of the partition is a symlink to a directory inside /system.
608 if (!OR_RETURN(IsSymlink(mount_point))) {
609 OR_RETURN(BindMount(mount_point, PathInChroot(mount_point)));
610 }
611 }
612 } else {
613 CHECK(ota_slot.value() == "_a" || ota_slot.value() == "_b");
614
615 if (map_snapshots_for_ota) {
616 // Write the file early in case `snapshotctl map` fails in the middle, leaving some devices
617 // mapped. We don't assume that `snapshotctl map` is transactional.
618 if (!WriteStringToFile("", *kSnapshotMappedFile)) {
619 return ErrnoErrorf("Failed to write '{}'", *kSnapshotMappedFile);
620 }
621
622 // Run `snapshotctl map` through init to map block devices. We can't run it ourselves because
623 // it requires the UID to be 0. See `sys.snapshotctl.map` in `init.rc`.
624 if (!SetProperty("sys.snapshotctl.map", "requested")) {
625 return Errorf("Failed to request snapshotctl map");
626 }
627 if (!WaitForProperty("sys.snapshotctl.map", "finished", kSnapshotCtlTimeout)) {
628 return Errorf("snapshotctl timed out");
629 }
630
631 // We don't know whether snapshotctl succeeded or not, but if it failed, the mount operation
632 // below will fail with `ENOENT`.
633 OR_RETURN(
634 Mount(GetBlockDeviceName("system", ota_slot.value()), CHROOT_DIR, /*is_optional=*/false));
635 } else {
636 // update_engine has mounted `system` at `/postinstall` for us.
637 OR_RETURN(BindMount("/postinstall", CHROOT_DIR));
638 }
639
640 for (const auto& [partition, mount_point] : additional_system_partitions) {
641 OR_RETURN(Mount(GetBlockDeviceName(partition, ota_slot.value()),
642 PathInChroot(mount_point),
643 /*is_optional=*/true));
644 }
645 }
646
647 OR_RETURN(MountTmpfs(PathInChroot("/apex"), "u:object_r:apex_mnt_dir:s0"));
648 OR_RETURN(MountTmpfs(PathInChroot("/linkerconfig"), "u:object_r:linkerconfig_file:s0"));
649 OR_RETURN(MountTmpfs(PathInChroot("/mnt"), "u:object_r:pre_reboot_dexopt_file:s0"));
650 OR_RETURN(CreateDir(PathInChroot("/mnt/artd_tmp")));
651 OR_RETURN(MountTmpfs(PathInChroot("/mnt/artd_tmp"), "u:object_r:pre_reboot_dexopt_artd_file:s0"));
652 OR_RETURN(CreateDir(PathInChroot("/mnt/expand")));
653
654 std::vector<std::string> bind_mount_srcs = {
655 // Data partitions.
656 "/data",
657 "/mnt/expand",
658 // Linux API filesystems.
659 "/dev",
660 "/proc",
661 "/sys",
662 // For apexd to query staged APEX sessions.
663 "/metadata",
664 };
665
666 for (const std::string& src : bind_mount_srcs) {
667 OR_RETURN(BindMountRecursive(src, PathInChroot(src)));
668 }
669
670 if (!WriteStringToFile(ota_slot.value_or(""), *kOtaSlotFile)) {
671 return ErrnoErrorf("Failed to write '{}'", *kOtaSlotFile);
672 }
673
674 return {};
675 }
676
InitChroot() const677 Result<void> DexoptChrootSetup::InitChroot() const {
678 std::optional<std::string> ota_slot = OR_RETURN(LoadOtaSlotFile());
679
680 // Generate empty linker config to suppress warnings.
681 if (!android::base::WriteStringToFile("", PathInChroot("/linkerconfig/ld.config.txt"))) {
682 PLOG(WARNING) << "Failed to generate empty linker config to suppress warnings";
683 }
684
685 CmdlineBuilder args = OR_RETURN(GetArtExecCmdlineBuilder());
686 args.Add("--")
687 .Add("/system/bin/apexd")
688 .Add("--otachroot-bootstrap")
689 .AddIf(!IsOtaUpdate(ota_slot), "--also-include-staged-apexes");
690 OR_RETURN(Run("apexd", args.Get()));
691
692 args = OR_RETURN(GetArtExecCmdlineBuilder());
693 args.Add("--drop-capabilities")
694 .Add("--")
695 .Add("/apex/com.android.runtime/bin/linkerconfig")
696 .Add("--target")
697 .Add("/linkerconfig");
698 OR_RETURN(Run("linkerconfig", args.Get()));
699
700 if (IsOtaUpdate(ota_slot)) {
701 OR_RETURN(PrepareExternalLibDirs());
702 }
703
704 return {};
705 }
706
TearDownChroot() const707 Result<void> DexoptChrootSetup::TearDownChroot() const {
708 // For mount points in `kExternalLibDirs`, make sure we have unmounted them before running apexd,
709 // as apexd expects new libraries.
710 // For mount points under "/mnt/compat_env", make sure we have unmounted them before running
711 // apexd, as apexd doesn't expect apexes to be in-use.
712 std::vector<FstabEntry> entries = OR_RETURN(GetProcMountsDescendantsOfPath(CHROOT_DIR));
713 for (const FstabEntry entry : entries) {
714 std::string_view mount_point_in_chroot = entry.mount_point;
715 CHECK(ConsumePrefix(&mount_point_in_chroot, CHROOT_DIR));
716 if (mount_point_in_chroot.empty()) {
717 continue; // The root mount.
718 }
719 if (ContainsElement(kExternalLibDirs, mount_point_in_chroot) ||
720 PathStartsWith(mount_point_in_chroot, "/mnt/compat_env")) {
721 OR_RETURN(Unmount(entry.mount_point));
722 }
723 }
724
725 std::vector<FstabEntry> apex_entries =
726 OR_RETURN(GetProcMountsDescendantsOfPath(PathInChroot("/apex")));
727 // If there is only one entry, it's /apex itself.
728 bool has_apex = apex_entries.size() > 1;
729
730 if (has_apex && OS::FileExists(PathInChroot("/system/bin/apexd").c_str())) {
731 // Delegate to apexd to unmount all APEXes. It also cleans up loop devices.
732 CmdlineBuilder args = OR_RETURN(GetArtExecCmdlineBuilder());
733 args.Add("--")
734 .Add("/system/bin/apexd")
735 .Add("--unmount-all")
736 .Add("--also-include-staged-apexes");
737 OR_RETURN(Run("apexd", args.Get()));
738 }
739
740 // Double check to make sure all APEXes are unmounted, just in case apexd incorrectly reported
741 // success.
742 apex_entries = OR_RETURN(GetProcMountsDescendantsOfPath(PathInChroot("/apex")));
743 for (const FstabEntry& entry : apex_entries) {
744 if (entry.mount_point != PathInChroot("/apex")) {
745 return Errorf("apexd didn't unmount '{}'. See logs for details", entry.mount_point);
746 }
747 }
748
749 // The list is in mount order.
750 entries = OR_RETURN(GetProcMountsDescendantsOfPath(CHROOT_DIR));
751 for (auto it = entries.rbegin(); it != entries.rend(); it++) {
752 OR_RETURN(Unmount(it->mount_point));
753 }
754
755 std::error_code ec;
756 std::uintmax_t removed = std::filesystem::remove_all(CHROOT_DIR, ec);
757 if (ec) {
758 return Errorf("Failed to remove dir '{}': {}", CHROOT_DIR, ec.message());
759 }
760 if (removed > 0) {
761 LOG(INFO) << ART_FORMAT("Removed '{}'", CHROOT_DIR);
762 }
763
764 if (!OR_RETURN(GetProcMountsDescendantsOfPath(*kBindMountTmpDir)).empty()) {
765 OR_RETURN(Unmount(*kBindMountTmpDir));
766 }
767
768 std::filesystem::remove_all(*kBindMountTmpDir, ec);
769 if (ec) {
770 return Errorf("Failed to remove dir '{}': {}", *kBindMountTmpDir, ec.message());
771 }
772
773 std::filesystem::remove(*kOtaSlotFile, ec);
774 if (ec) {
775 return Errorf("Failed to remove file '{}': {}", *kOtaSlotFile, ec.message());
776 }
777
778 if (OS::FileExists(kSnapshotMappedFile->c_str())) {
779 if (!SetProperty("sys.snapshotctl.unmap", "requested")) {
780 return Errorf("Failed to request snapshotctl unmap");
781 }
782 if (!WaitForProperty("sys.snapshotctl.unmap", "finished", kSnapshotCtlTimeout)) {
783 return Errorf("snapshotctl timed out");
784 }
785 std::filesystem::remove(*kSnapshotMappedFile, ec);
786 if (ec) {
787 return Errorf("Failed to remove file '{}': {}", *kSnapshotMappedFile, ec.message());
788 }
789 }
790
791 return {};
792 }
793
PathInChroot(std::string_view path)794 std::string PathInChroot(std::string_view path) {
795 return std::string(DexoptChrootSetup::CHROOT_DIR).append(path);
796 }
797
ConstructLinkerConfigCompatEnvSection(const std::string & art_linker_config_content)798 Result<std::string> ConstructLinkerConfigCompatEnvSection(
799 const std::string& art_linker_config_content) {
800 std::regex system_lib_re(R"re((=\s*|:)/(system(?:_ext)?/\$\{LIB\}))re");
801 constexpr const char* kSystemLibFmt = "$1/mnt/compat_env/$2";
802
803 // Make a copy of the [com.android.art] section and patch particular lines.
804 std::string compat_section;
805 bool is_in_art_section = false;
806 bool replaced = false;
807 std::vector<std::string_view> art_linker_config_lines;
808 art::Split(art_linker_config_content, '\n', &art_linker_config_lines);
809 for (std::string_view line : art_linker_config_lines) {
810 if (!is_in_art_section && line == "[com.android.art]") {
811 is_in_art_section = true;
812 } else if (is_in_art_section && line.starts_with('[')) {
813 is_in_art_section = false;
814 }
815
816 if (is_in_art_section) {
817 if (line == "[com.android.art]") {
818 compat_section += "[com.android.art.compat]\n";
819 } else {
820 std::string patched_line =
821 std::regex_replace(std::string(line), system_lib_re, kSystemLibFmt);
822 if (line != patched_line) {
823 LOG(DEBUG) << ART_FORMAT("Replacing '{}' with '{}'", line, patched_line);
824 replaced = true;
825 }
826 compat_section += patched_line;
827 compat_section += '\n';
828 }
829 }
830 }
831 if (!replaced) {
832 return Errorf("No matching lines to patch in ART linker config");
833 }
834 return compat_section;
835 }
836
837 } // namespace dexopt_chroot_setup
838 } // namespace art
839