• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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".
197 // By default, this function rejects `source` in chroot, to avoid accidental repeated bind-mounting.
198 // If you intentionally want `source` to be in chroot, set `check_source_is_not_in_chroot` to false.
BindMount(const std::string & source,const std::string & target,bool check_source_is_not_in_chroot=true)199 Result<void> BindMount(const std::string& source,
200                        const std::string& target,
201                        bool check_source_is_not_in_chroot = true) {
202   // Don't bind-mount repeatedly.
203   if (check_source_is_not_in_chroot) {
204     CHECK(!PathStartsWith(source, DexoptChrootSetup::CHROOT_DIR));
205   }
206   // Don't follow symlinks.
207   CHECK(!OR_RETURN(IsSelfOrParentSymlink(target))) << target;
208   // system_server has a different mount namespace from init, and it uses slave mounts. E.g:
209   //
210   //    a: init mount ns: shared(1):          /foo
211   //    b: init mount ns: shared(2):          /mnt
212   //    c: SS mount ns:   slave(1):           /foo
213   //    d: SS mount ns:   slave(2):           /mnt
214   //
215   // We create our chroot setup in the init namespace but also want it to appear inside the
216   // system_server one, since we need to access some files in it from system_server (in particular
217   // service-art.jar).
218   //
219   // Hence we want the mount propagation type to be "slave+shared": Slave of the init namespace so
220   // that unmounts in the chroot doesn't affect the rest of the system, while at the same time
221   // shared with the system_server namespace so that it gets the same mounts recursively in the
222   // chroot tree. This can be achieved in 4 steps:
223   //
224   // 1. Bind-mount /foo at a temp mount point /mnt/pre_reboot_dexopt/mount_tmp.
225   //    a: init mount ns: shared(1):          /foo
226   //    b: init mount ns: shared(2):          /mnt
227   //    e: init mount ns: shared(1):          /mnt/pre_reboot_dexopt/mount_tmp
228   //    c: SS mount ns:   slave(1):           /foo
229   //    d: SS mount ns:   slave(2):           /mnt
230   //    f: SS mount ns:   slave(1):           /mnt/pre_reboot_dexopt/mount_tmp
231   //
232   // 2. Make the temp mount point slave.
233   //    a: init mount ns: shared(1):          /foo
234   //    b: init mount ns: shared(2):          /mnt
235   //    e: init mount ns: slave(1):           /mnt/pre_reboot_dexopt/mount_tmp
236   //    c: SS mount ns:   slave(1):           /foo
237   //    d: SS mount ns:   slave(2):           /mnt
238   //    f: SS mount ns:   slave(1):           /mnt/pre_reboot_dexopt/mount_tmp
239   //
240   // 3. Bind-mount the temp mount point at /mnt/pre_reboot_dexopt/chroot/foo. (The new mount point
241   //    gets "slave+shared". It gets "slave" because the source (`e`) is "slave", and it gets
242   //    "shared" because the dest (`b`) is "shared".)
243   //    a: init mount ns: shared(1):          /foo
244   //    b: init mount ns: shared(2):          /mnt
245   //    e: init mount ns: slave(1):           /mnt/pre_reboot_dexopt/mount_tmp
246   //    g: init mount ns: slave(1),shared(3): /mnt/pre_reboot_dexopt/chroot/foo
247   //    b: SS mount ns:   slave(1):           /foo
248   //    d: SS mount ns:   slave(2):           /mnt
249   //    f: SS mount ns:   slave(1):           /mnt/pre_reboot_dexopt/mount_tmp
250   //    h: SS mount ns:   slave(3):           /mnt/pre_reboot_dexopt/chroot/foo
251   //
252   // 4. Unmount the temp mount point.
253   //    a: init mount ns: shared(1):          /foo
254   //    b: init mount ns: shared(2):          /mnt
255   //    g: init mount ns: slave(1),shared(3): /mnt/pre_reboot_dexopt/chroot/foo
256   //    b: SS mount ns:   slave(1):           /foo
257   //    d: SS mount ns:   slave(2):           /mnt
258   //    h: SS mount ns:   slave(3):           /mnt/pre_reboot_dexopt/chroot/foo
259   //
260   // At this point, we have achieved what we want. `g` is a slave of `a` so that unmounts in `g`
261   // doesn't affect `a`, and `g` is shared with `h` so that mounts in `g` are propagated to `h`.
262   OR_RETURN(CreateDir(*kBindMountTmpDir));
263   if (mount(source.c_str(),
264             kBindMountTmpDir->c_str(),
265             /*fs_type=*/nullptr,
266             MS_BIND,
267             /*data=*/nullptr) != 0) {
268     return ErrnoErrorf("Failed to bind-mount '{}' at '{}' ('{}' -> '{}')",
269                        source,
270                        *kBindMountTmpDir,
271                        source,
272                        target);
273   }
274   auto cleanup = make_scope_guard([&]() {
275     Result<void> result = Unmount(*kBindMountTmpDir, /*logging=*/false);
276     if (!result.ok()) {
277       LOG(ERROR) << result.error().message();
278     }
279   });
280   if (mount(/*source=*/nullptr,
281             kBindMountTmpDir->c_str(),
282             /*fs_type=*/nullptr,
283             MS_SLAVE,
284             /*data=*/nullptr) != 0) {
285     return ErrnoErrorf(
286         "Failed to make mount slave for '{}' ('{}' -> '{}')", *kBindMountTmpDir, source, target);
287   }
288   if (mount(kBindMountTmpDir->c_str(),
289             target.c_str(),
290             /*fs_type=*/nullptr,
291             MS_BIND,
292             /*data=*/nullptr) != 0) {
293     return ErrnoErrorf("Failed to bind-mount '{}' at '{}' ('{}' -> '{}')",
294                        *kBindMountTmpDir,
295                        target,
296                        source,
297                        target);
298   }
299   LOG(INFO) << ART_FORMAT("Bind-mounted '{}' at '{}'", source, target);
300   return {};
301 }
302 
BindMountRecursive(const std::string & source,const std::string & target)303 Result<void> BindMountRecursive(const std::string& source, const std::string& target) {
304   CHECK(!source.ends_with('/'));
305   OR_RETURN(BindMount(source, target));
306 
307   // Mount and make slave one by one. Do not use MS_REC because we don't want to mount a child if
308   // the parent cannot be slave (i.e., is shared). Otherwise, unmount events will be undesirably
309   // propagated to the source. For example, if "/dev" and "/dev/pts" are mounted at "/chroot/dev"
310   // and "/chroot/dev/pts" respectively, and "/chroot/dev" is shared, then unmounting
311   // "/chroot/dev/pts" will also unmount "/dev/pts".
312   //
313   // The list is in mount order.
314   std::vector<FstabEntry> entries = OR_RETURN(GetProcMountsDescendantsOfPath(source));
315   for (const FstabEntry& entry : entries) {
316     CHECK(!entry.mount_point.ends_with('/'));
317     std::string_view sub_dir = entry.mount_point;
318     CHECK(ConsumePrefix(&sub_dir, source));
319     if (sub_dir.empty()) {
320       // `source` itself. Already mounted.
321       continue;
322     }
323     if (Result<void> result = BindMount(entry.mount_point, std::string(target).append(sub_dir));
324         !result.ok()) {
325       // Match paths for the "u:object_r:apk_tmp_file:s0" file context in
326       // system/sepolicy/private/file_contexts.
327       std::regex apk_tmp_file_re(R"re((/data|/mnt/expand/[^/]+)/app/vmdl[^/]+\.tmp(/.*)?)re");
328       if (std::regex_match(entry.mount_point, apk_tmp_file_re)) {
329         // Don't bother. The mount point is a temporary directory created by Package Manager during
330         // app install. We won't be able to dexopt the app there anyway because it's not in the
331         // Package Manager's snapshot.
332         LOG(INFO) << ART_FORMAT("Skipped temporary mount point '{}'", entry.mount_point);
333         continue;
334       }
335 
336       std::regex vendor_file_re(R"re(/data/vendor(/.*)?)re");
337       if (std::regex_match(entry.mount_point, vendor_file_re)) {
338         // We can't reliably bind-mount vendor-specific files because those files can have
339         // vendor-specific SELinux file contexts, which by design cannot be referenced by
340         // `dexopt_chroot_setup.te`. In practice, we don't need to bind-mount those files because
341         // they are unlikely to contain things useful to us.
342         LOG(INFO) << ART_FORMAT("Skipped vendor mount point '{}'", entry.mount_point);
343         continue;
344       }
345 
346       return result;
347     }
348   }
349   return {};
350 }
351 
GetBlockDeviceName(const std::string & partition,const std::string & slot)352 std::string GetBlockDeviceName(const std::string& partition, const std::string& slot) {
353   return ART_FORMAT("/dev/block/mapper/{}{}", partition, slot);
354 }
355 
GetSupportedFilesystems()356 Result<std::vector<std::string>> GetSupportedFilesystems() {
357   std::string content;
358   if (!ReadFileToString("/proc/filesystems", &content)) {
359     return ErrnoErrorf("Failed to read '/proc/filesystems'");
360   }
361   std::vector<std::string> filesystems;
362   for (const std::string& line : Split(content, "\n")) {
363     std::vector<std::string> tokens = Tokenize(line, " \t");
364     // If there are two tokens, the first token is a "nodev" mark, meaning it's not for a block
365     // device, so we skip it.
366     if (tokens.size() == 1) {
367       filesystems.push_back(tokens[0]);
368     }
369   }
370   // Prioritize the filesystems that are known to behave correctly, just in case some bad
371   // filesystems are unexpectedly happy to mount volumes that aren't of their types. We have never
372   // seen this case in practice though.
373   constexpr const char* kWellKnownFilesystems[] = {"erofs", "ext4"};
374   for (const char* well_known_fs : kWellKnownFilesystems) {
375     auto it = std::find(filesystems.begin(), filesystems.end(), well_known_fs);
376     if (it != filesystems.end()) {
377       filesystems.erase(it);
378       filesystems.insert(filesystems.begin(), well_known_fs);
379     }
380   }
381   return filesystems;
382 }
383 
Mount(const std::string & block_device,const std::string & target,bool is_optional)384 Result<void> Mount(const std::string& block_device, const std::string& target, bool is_optional) {
385   static const NoDestructor<Result<std::vector<std::string>>> supported_filesystems(
386       GetSupportedFilesystems());
387   if (!supported_filesystems->ok()) {
388     return supported_filesystems->error();
389   }
390   std::vector<std::string> error_msgs;
391   for (const std::string& filesystem : supported_filesystems->value()) {
392     if (mount(block_device.c_str(),
393               target.c_str(),
394               filesystem.c_str(),
395               MS_RDONLY,
396               /*data=*/nullptr) == 0) {
397       // Success.
398       LOG(INFO) << ART_FORMAT(
399           "Mounted '{}' at '{}' with type '{}'", block_device, target, filesystem);
400       return {};
401     } else {
402       if (errno == ENOENT && is_optional) {
403         LOG(INFO) << ART_FORMAT("Skipped non-existing block device '{}'", block_device);
404         return {};
405       }
406       error_msgs.push_back(ART_FORMAT("Tried '{}': {}", filesystem, strerror(errno)));
407       if (errno != EINVAL && errno != EBUSY) {
408         // If the filesystem type is wrong, `errno` must be either `EINVAL` or `EBUSY`. For example,
409         // we've seen that trying to mount a device with a wrong filesystem type yields `EBUSY` if
410         // the device is also mounted elsewhere, though we can't find any document about this
411         // behavior.
412         break;
413       }
414     }
415   }
416   return Errorf("Failed to mount '{}' at '{}':\n{}", block_device, target, Join(error_msgs, '\n'));
417 }
418 
MountTmpfs(const std::string & target,std::string_view se_context)419 Result<void> MountTmpfs(const std::string& target, std::string_view se_context) {
420   if (mount(/*source=*/"tmpfs",
421             target.c_str(),
422             /*fs_type=*/"tmpfs",
423             MS_NODEV | MS_NOEXEC | MS_NOSUID,
424             ART_FORMAT("mode={:#o},rootcontext={}", kChrootDefaultMode, se_context).c_str()) != 0) {
425     return ErrnoErrorf("Failed to mount tmpfs at '{}'", target);
426   }
427   return {};
428 }
429 
LoadOtaSlotFile()430 Result<std::optional<std::string>> LoadOtaSlotFile() {
431   std::string content;
432   if (!ReadFileToString(*kOtaSlotFile, &content)) {
433     return ErrnoErrorf("Failed to read '{}'", *kOtaSlotFile);
434   }
435   if (content == "_a" || content == "_b") {
436     return content;
437   }
438   if (content.empty()) {
439     return std::nullopt;
440   }
441   return Errorf("Invalid content of '{}': '{}'", *kOtaSlotFile, content);
442 }
443 
PatchLinkerConfigForCompatEnv()444 Result<void> PatchLinkerConfigForCompatEnv() {
445   std::string art_linker_config_content;
446   if (!ReadFileToString(PathInChroot("/linkerconfig/com.android.art/ld.config.txt"),
447                         &art_linker_config_content)) {
448     return ErrnoErrorf("Failed to read ART linker config");
449   }
450 
451   std::string compat_section =
452       OR_RETURN(ConstructLinkerConfigCompatEnvSection(art_linker_config_content));
453 
454   // Append the patched section to the global linker config. Because the compat env path doesn't
455   // start with "/apex", the global linker config is the one that takes effect.
456   std::string global_linker_config_path = PathInChroot("/linkerconfig/ld.config.txt");
457   std::string global_linker_config_content;
458   if (!ReadFileToString(global_linker_config_path, &global_linker_config_content)) {
459     return ErrnoErrorf("Failed to read global linker config");
460   }
461 
462   if (!WriteStringToFile("dir.com.android.art.compat = /mnt/compat_env/apex/com.android.art/bin\n" +
463                              global_linker_config_content + compat_section,
464                          global_linker_config_path)) {
465     return ErrnoErrorf("Failed to write global linker config");
466   }
467 
468   LOG(INFO) << "Patched " << global_linker_config_path;
469   return {};
470 }
471 
472 // Platform libraries communicate with things outside of chroot through unstable APIs. Examples are
473 // `libbinder_ndk.so` talking to `servicemanager` and `libcgrouprc.so` reading
474 // `/dev/cgroup_info/cgroup.rc`. To work around incompatibility issues, we bind-mount the old
475 // platform library directories into chroot so that both sides of a communication are old and
476 // therefore align with each other.
477 // After bind-mounting old platform libraries, the chroot environment has a combination of new
478 // modules and old platform libraries. We currently use the new linker config in such an
479 // environment, which is potentially problematic. If we start to see problems, we should consider
480 // generating a more correct linker config in a more complex way.
PrepareExternalLibDirs()481 Result<void> PrepareExternalLibDirs() {
482   std::vector<const char*> existing_lib_dirs;
483   std::copy_if(kExternalLibDirs.begin(),
484                kExternalLibDirs.end(),
485                std::back_inserter(existing_lib_dirs),
486                OS::DirectoryExists);
487   if (existing_lib_dirs.empty()) {
488     return Errorf("Unexpectedly missing platform library directories. Tried '{}'",
489                   android::base::Join(kExternalLibDirs, "', '"));
490   }
491 
492   // We should bind-mount all existing lib dirs or none of them. Try the first one to decide what
493   // to do next.
494   Result<void> result = BindMount(existing_lib_dirs[0], PathInChroot(existing_lib_dirs[0]));
495   if (result.ok()) {
496     for (size_t i = 1; i < existing_lib_dirs.size(); ++i) {
497       OR_RETURN(BindMount(existing_lib_dirs[i], PathInChroot(existing_lib_dirs[i])));
498     }
499   } else if (result.error().code() == EACCES) {
500     // We don't have the permission to do so on V. Fall back to bind-mounting elsewhere.
501     LOG(WARNING) << result.error().message();
502 
503     OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env")));
504     OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env/system")));
505     OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env/system_ext")));
506     OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env/apex")));
507     OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env/apex/com.android.art")));
508     OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env/apex/com.android.art/bin")));
509     OR_RETURN(BindMountDirect(PathInChroot("/apex/com.android.art/bin"),
510                               PathInChroot("/mnt/compat_env/apex/com.android.art/bin")));
511     for (const char* lib_dir : existing_lib_dirs) {
512       OR_RETURN(CreateDir(PathInChroot("/mnt/compat_env") + lib_dir));
513       OR_RETURN(BindMountDirect(lib_dir, PathInChroot("/mnt/compat_env") + lib_dir));
514     }
515 
516     OR_RETURN(PatchLinkerConfigForCompatEnv());
517   } else {
518     return result;
519   }
520 
521   // Back up the new classpaths dir before bind-mounting etc dirs. We need the new classpaths dir
522   // for derive_classpath.
523   std::string classpaths_tmp_dir = PathInChroot("/mnt/classpaths");
524   OR_RETURN(CreateDir(classpaths_tmp_dir));
525   OR_RETURN(BindMount(PathInChroot("/system/etc/classpaths"),
526                       classpaths_tmp_dir,
527                       /*check_source_is_not_in_chroot=*/false));
528 
529   // Old platform libraries expect old etc dirs, so we should bind-mount them as well.
530   OR_RETURN(BindMount("/system/etc", PathInChroot("/system/etc")));
531   OR_RETURN(BindMount("/system_ext/etc", PathInChroot("/system_ext/etc")));
532   OR_RETURN(BindMount("/product/etc", PathInChroot("/product/etc")));
533   result = BindMount("/vendor/etc", PathInChroot("/vendor/etc"));
534   if (!result.ok()) {
535     if (result.error().code() == EACCES) {
536       // We don't have the permission to do so on V. That's fine because the V version of the
537       // platform libraries are fine with the B version of /vendor/etc at the time of writing. Even
538       // if it's not fine, there is nothing we can do.
539       LOG(WARNING) << result.error().message();
540     } else {
541       return result;
542     }
543   }
544 
545   // Restore the classpaths dir.
546   OR_RETURN(BindMount(classpaths_tmp_dir,
547                       PathInChroot("/system/etc/classpaths"),
548                       /*check_source_is_not_in_chroot=*/false));
549   OR_RETURN(Unmount(classpaths_tmp_dir));
550 
551   return {};
552 }
553 
554 }  // namespace
555 
setUp(const std::optional<std::string> & in_otaSlot,bool in_mapSnapshotsForOta)556 ScopedAStatus DexoptChrootSetup::setUp(const std::optional<std::string>& in_otaSlot,
557                                        bool in_mapSnapshotsForOta) {
558   if (!mu_.try_lock()) {
559     return Fatal("Unexpected concurrent calls");
560   }
561   std::lock_guard<std::mutex> lock(mu_, std::adopt_lock);
562 
563   if (in_otaSlot.has_value() && (in_otaSlot.value() != "_a" && in_otaSlot.value() != "_b")) {
564     return Fatal(ART_FORMAT("Invalid OTA slot '{}'", in_otaSlot.value()));
565   }
566   OR_RETURN_NON_FATAL(SetUpChroot(in_otaSlot, in_mapSnapshotsForOta));
567   return ScopedAStatus::ok();
568 }
569 
init()570 ScopedAStatus DexoptChrootSetup::init() {
571   if (!mu_.try_lock()) {
572     return Fatal("Unexpected concurrent calls");
573   }
574   std::lock_guard<std::mutex> lock(mu_, std::adopt_lock);
575 
576   if (OS::FileExists(PathInChroot("/linkerconfig/ld.config.txt").c_str())) {
577     return Fatal("init must not be repeatedly called");
578   }
579 
580   OR_RETURN_NON_FATAL(InitChroot());
581   return ScopedAStatus::ok();
582 }
583 
tearDown(bool in_allowConcurrent)584 ScopedAStatus DexoptChrootSetup::tearDown(bool in_allowConcurrent) {
585   if (in_allowConcurrent) {
586     // Normally, we don't expect concurrent calls, but this method may be called upon system server
587     // restart when another call initiated by the previous system_server instance is still being
588     // processed.
589     mu_.lock();
590   } else {
591     if (!mu_.try_lock()) {
592       return Fatal("Unexpected concurrent calls");
593     }
594   }
595   std::lock_guard<std::mutex> lock(mu_, std::adopt_lock);
596 
597   OR_RETURN_NON_FATAL(TearDownChroot());
598   return ScopedAStatus::ok();
599 }
600 
Start()601 Result<void> DexoptChrootSetup::Start() {
602   ScopedAStatus status = ScopedAStatus::fromStatus(
603       AServiceManager_registerLazyService(this->asBinder().get(), kServiceName));
604   if (!status.isOk()) {
605     return Error() << status.getDescription();
606   }
607 
608   ABinderProcess_startThreadPool();
609 
610   return {};
611 }
612 
SetUpChroot(const std::optional<std::string> & ota_slot,bool map_snapshots_for_ota) const613 Result<void> DexoptChrootSetup::SetUpChroot(const std::optional<std::string>& ota_slot,
614                                             bool map_snapshots_for_ota) const {
615   // Set the default permission mode for new files and dirs to be `kChrootDefaultMode`.
616   umask(~kChrootDefaultMode & 0777);
617 
618   // In case there is some leftover.
619   OR_RETURN(TearDownChroot());
620 
621   // Prepare the root dir of chroot. The parent directory has been created by init (see `init.rc`).
622   OR_RETURN(CreateDir(CHROOT_DIR));
623   LOG(INFO) << ART_FORMAT("Created '{}'", CHROOT_DIR);
624 
625   std::vector<std::tuple<std::string, std::string>> additional_system_partitions = {
626       {"system_ext", "/system_ext"},
627       {"vendor", "/vendor"},
628       {"product", "/product"},
629   };
630 
631   std::string partitions_from_sysprop =
632       GetProperty(kAdditionalPartitionsSysprop, /*default_value=*/"");
633   std::vector<std::string_view> partitions_from_sysprop_entries;
634   art::Split(partitions_from_sysprop, ',', &partitions_from_sysprop_entries);
635   for (std::string_view entry : partitions_from_sysprop_entries) {
636     std::vector<std::string_view> pair;
637     art::Split(entry, ':', &pair);
638     if (pair.size() != 2 || pair[0].empty() || pair[1].empty() || !pair[1].starts_with('/')) {
639       return Errorf("Malformed entry in '{}': '{}'", kAdditionalPartitionsSysprop, entry);
640     }
641     additional_system_partitions.emplace_back(std::string(pair[0]), std::string(pair[1]));
642   }
643 
644   if (!IsOtaUpdate(ota_slot)) {  // Mainline update
645     OR_RETURN(BindMount("/", CHROOT_DIR));
646     // Normally, we don't need to bind-mount "/system" because it's a part of the image mounted at
647     // "/". However, when readonly partitions are remounted read-write, an overlay is created at
648     // "/system", so we need to bind-mount "/system" to handle this case. On devices where readonly
649     // partitions are not remounted, bind-mounting "/system" doesn't hurt.
650     OR_RETURN(BindMount("/system", PathInChroot("/system")));
651     for (const auto& [partition, mount_point] : additional_system_partitions) {
652       // Some additional partitions are optional. On a device where an additional partition doesn't
653       // exist, the mount point of the partition is a symlink to a directory inside /system.
654       if (!OR_RETURN(IsSymlink(mount_point))) {
655         OR_RETURN(BindMount(mount_point, PathInChroot(mount_point)));
656       }
657     }
658   } else {
659     CHECK(ota_slot.value() == "_a" || ota_slot.value() == "_b");
660 
661     if (map_snapshots_for_ota) {
662       // Write the file early in case `snapshotctl map` fails in the middle, leaving some devices
663       // mapped. We don't assume that `snapshotctl map` is transactional.
664       if (!WriteStringToFile("", *kSnapshotMappedFile)) {
665         return ErrnoErrorf("Failed to write '{}'", *kSnapshotMappedFile);
666       }
667 
668       // Run `snapshotctl map` through init to map block devices. We can't run it ourselves because
669       // it requires the UID to be 0. See `sys.snapshotctl.map` in `init.rc`.
670       if (!SetProperty("sys.snapshotctl.map", "requested")) {
671         return Errorf("Failed to request snapshotctl map");
672       }
673       if (!WaitForProperty("sys.snapshotctl.map", "finished", kSnapshotCtlTimeout)) {
674         return Errorf("snapshotctl timed out");
675       }
676 
677       // We don't know whether snapshotctl succeeded or not, but if it failed, the mount operation
678       // below will fail with `ENOENT`.
679       OR_RETURN(
680           Mount(GetBlockDeviceName("system", ota_slot.value()), CHROOT_DIR, /*is_optional=*/false));
681     } else {
682       // update_engine has mounted `system` at `/postinstall` for us.
683       OR_RETURN(BindMount("/postinstall", CHROOT_DIR));
684     }
685 
686     for (const auto& [partition, mount_point] : additional_system_partitions) {
687       OR_RETURN(Mount(GetBlockDeviceName(partition, ota_slot.value()),
688                       PathInChroot(mount_point),
689                       /*is_optional=*/true));
690     }
691   }
692 
693   OR_RETURN(MountTmpfs(PathInChroot("/apex"), "u:object_r:apex_mnt_dir:s0"));
694   OR_RETURN(MountTmpfs(PathInChroot("/linkerconfig"), "u:object_r:linkerconfig_file:s0"));
695   OR_RETURN(MountTmpfs(PathInChroot("/mnt"), "u:object_r:pre_reboot_dexopt_file:s0"));
696   OR_RETURN(CreateDir(PathInChroot("/mnt/artd_tmp")));
697   OR_RETURN(MountTmpfs(PathInChroot("/mnt/artd_tmp"), "u:object_r:pre_reboot_dexopt_artd_file:s0"));
698   OR_RETURN(CreateDir(PathInChroot("/mnt/expand")));
699 
700   std::vector<std::string> bind_mount_srcs = {
701       // Data partitions.
702       "/data",
703       "/mnt/expand",
704       // Linux API filesystems.
705       "/dev",
706       "/proc",
707       "/sys",
708       // For apexd to query staged APEX sessions.
709       "/metadata",
710   };
711 
712   for (const std::string& src : bind_mount_srcs) {
713     OR_RETURN(BindMountRecursive(src, PathInChroot(src)));
714   }
715 
716   if (!WriteStringToFile(ota_slot.value_or(""), *kOtaSlotFile)) {
717     return ErrnoErrorf("Failed to write '{}'", *kOtaSlotFile);
718   }
719 
720   return {};
721 }
722 
InitChroot() const723 Result<void> DexoptChrootSetup::InitChroot() const {
724   std::optional<std::string> ota_slot = OR_RETURN(LoadOtaSlotFile());
725 
726   // Generate empty linker config to suppress warnings.
727   if (!android::base::WriteStringToFile("", PathInChroot("/linkerconfig/ld.config.txt"))) {
728     PLOG(WARNING) << "Failed to generate empty linker config to suppress warnings";
729   }
730 
731   CmdlineBuilder args = OR_RETURN(GetArtExecCmdlineBuilder());
732   args.Add("--")
733       .Add("/system/bin/apexd")
734       .Add("--otachroot-bootstrap")
735       .AddIf(!IsOtaUpdate(ota_slot), "--also-include-staged-apexes");
736   OR_RETURN(Run("apexd", args.Get()));
737 
738   args = OR_RETURN(GetArtExecCmdlineBuilder());
739   args.Add("--drop-capabilities")
740       .Add("--")
741       .Add("/apex/com.android.runtime/bin/linkerconfig")
742       .Add("--target")
743       .Add("/linkerconfig");
744   OR_RETURN(Run("linkerconfig", args.Get()));
745 
746   if (IsOtaUpdate(ota_slot)) {
747     OR_RETURN(PrepareExternalLibDirs());
748   }
749 
750   return {};
751 }
752 
TearDownChroot() const753 Result<void> DexoptChrootSetup::TearDownChroot() const {
754   // For platform library dirs and etc dirs, make sure we have unmounted them before running apexd,
755   // as apexd expects new libraries (and probably new etc dirs).
756   // For mount points under "/mnt/compat_env", make sure we have unmounted them before running
757   // apexd, as apexd doesn't expect apexes to be in-use.
758   // The list is in mount order.
759   std::vector<FstabEntry> entries = OR_RETURN(GetProcMountsDescendantsOfPath(CHROOT_DIR));
760   for (auto it = entries.rbegin(); it != entries.rend(); ++it) {
761     const FstabEntry& entry = *it;
762     std::string_view mount_point_in_chroot = entry.mount_point;
763     CHECK(ConsumePrefix(&mount_point_in_chroot, CHROOT_DIR));
764     if (mount_point_in_chroot.empty()) {
765       continue;  // The root mount.
766     }
767     if (ContainsElement(kExternalLibDirs, mount_point_in_chroot) ||
768         PathStartsWith(mount_point_in_chroot, "/mnt/compat_env") ||
769         ContainsElement({"/system/etc",
770                          "/system_ext/etc",
771                          "/product/etc",
772                          "/vendor/etc",
773                          "/system/etc/classpaths",
774                          "/mnt/classpaths"},
775                         mount_point_in_chroot)) {
776       OR_RETURN(Unmount(entry.mount_point));
777     }
778   }
779 
780   std::vector<FstabEntry> apex_entries =
781       OR_RETURN(GetProcMountsDescendantsOfPath(PathInChroot("/apex")));
782   // If there is only one entry, it's /apex itself.
783   bool has_apex = apex_entries.size() > 1;
784 
785   if (has_apex && OS::FileExists(PathInChroot("/system/bin/apexd").c_str())) {
786     // Delegate to apexd to unmount all APEXes. It also cleans up loop devices.
787     CmdlineBuilder args = OR_RETURN(GetArtExecCmdlineBuilder());
788     args.Add("--")
789         .Add("/system/bin/apexd")
790         .Add("--unmount-all")
791         .Add("--also-include-staged-apexes");
792     OR_RETURN(Run("apexd", args.Get()));
793   }
794 
795   // Double check to make sure all APEXes are unmounted, just in case apexd incorrectly reported
796   // success.
797   apex_entries = OR_RETURN(GetProcMountsDescendantsOfPath(PathInChroot("/apex")));
798   for (const FstabEntry& entry : apex_entries) {
799     if (entry.mount_point != PathInChroot("/apex")) {
800       return Errorf("apexd didn't unmount '{}'. See logs for details", entry.mount_point);
801     }
802   }
803 
804   // The list is in mount order.
805   entries = OR_RETURN(GetProcMountsDescendantsOfPath(CHROOT_DIR));
806   for (auto it = entries.rbegin(); it != entries.rend(); ++it) {
807     OR_RETURN(Unmount(it->mount_point));
808   }
809 
810   std::error_code ec;
811   std::uintmax_t removed = std::filesystem::remove_all(CHROOT_DIR, ec);
812   if (ec) {
813     return Errorf("Failed to remove dir '{}': {}", CHROOT_DIR, ec.message());
814   }
815   if (removed > 0) {
816     LOG(INFO) << ART_FORMAT("Removed '{}'", CHROOT_DIR);
817   }
818 
819   if (!OR_RETURN(GetProcMountsDescendantsOfPath(*kBindMountTmpDir)).empty()) {
820     OR_RETURN(Unmount(*kBindMountTmpDir));
821   }
822 
823   std::filesystem::remove_all(*kBindMountTmpDir, ec);
824   if (ec) {
825     return Errorf("Failed to remove dir '{}': {}", *kBindMountTmpDir, ec.message());
826   }
827 
828   std::filesystem::remove(*kOtaSlotFile, ec);
829   if (ec) {
830     return Errorf("Failed to remove file '{}': {}", *kOtaSlotFile, ec.message());
831   }
832 
833   if (OS::FileExists(kSnapshotMappedFile->c_str())) {
834     if (!SetProperty("sys.snapshotctl.unmap", "requested")) {
835       return Errorf("Failed to request snapshotctl unmap");
836     }
837     if (!WaitForProperty("sys.snapshotctl.unmap", "finished", kSnapshotCtlTimeout)) {
838       return Errorf("snapshotctl timed out");
839     }
840     std::filesystem::remove(*kSnapshotMappedFile, ec);
841     if (ec) {
842       return Errorf("Failed to remove file '{}': {}", *kSnapshotMappedFile, ec.message());
843     }
844   }
845 
846   return {};
847 }
848 
PathInChroot(std::string_view path)849 std::string PathInChroot(std::string_view path) {
850   return std::string(DexoptChrootSetup::CHROOT_DIR).append(path);
851 }
852 
ConstructLinkerConfigCompatEnvSection(const std::string & art_linker_config_content)853 Result<std::string> ConstructLinkerConfigCompatEnvSection(
854     const std::string& art_linker_config_content) {
855   std::regex system_lib_re(R"re((=\s*|:)/(system(?:_ext)?/\$\{LIB\}))re");
856   constexpr const char* kSystemLibFmt = "$1/mnt/compat_env/$2";
857 
858   // Make a copy of the [com.android.art] section and patch particular lines.
859   std::string compat_section;
860   bool is_in_art_section = false;
861   bool replaced = false;
862   std::vector<std::string_view> art_linker_config_lines;
863   art::Split(art_linker_config_content, '\n', &art_linker_config_lines);
864   for (std::string_view line : art_linker_config_lines) {
865     if (!is_in_art_section && line == "[com.android.art]") {
866       is_in_art_section = true;
867     } else if (is_in_art_section && line.starts_with('[')) {
868       is_in_art_section = false;
869     }
870 
871     if (is_in_art_section) {
872       if (line == "[com.android.art]") {
873         compat_section += "[com.android.art.compat]\n";
874       } else {
875         std::string patched_line =
876             std::regex_replace(std::string(line), system_lib_re, kSystemLibFmt);
877         if (line != patched_line) {
878           LOG(DEBUG) << ART_FORMAT("Replacing '{}' with '{}'", line, patched_line);
879           replaced = true;
880         }
881         compat_section += patched_line;
882         compat_section += '\n';
883       }
884     }
885   }
886   if (!replaced) {
887     return Errorf("No matching lines to patch in ART linker config");
888   }
889   return compat_section;
890 }
891 
892 }  // namespace dexopt_chroot_setup
893 }  // namespace art
894