| // Copyright 2016 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "arc/setup/arc_setup.h" |
| |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <linux/magic.h> |
| #include <sched.h> |
| #include <selinux/selinux.h> |
| #include <stdlib.h> |
| #include <sys/mount.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/vfs.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <limits> |
| #include <memory> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <base/command_line.h> |
| #include <base/environment.h> |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/macros.h> |
| #include <base/memory/ptr_util.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/system/sys_info.h> |
| #include <base/threading/platform_thread.h> |
| #include <base/time/time.h> |
| #include <base/timer/elapsed_timer.h> |
| #include <brillo/cryptohome.h> |
| #include <brillo/file_utils.h> |
| #include <brillo/files/safe_fd.h> |
| #include <brillo/scoped_mount_namespace.h> |
| #include <chromeos-config/libcros_config/cros_config.h> |
| #include <chromeos/patchpanel/client.h> |
| #include <crypto/random.h> |
| #include <metrics/bootstat.h> |
| #include <metrics/metrics_library.h> |
| |
| #include "arc/setup/art_container.h" |
| |
| #define EXIT_IF(f) \ |
| do { \ |
| LOG(INFO) << "Running " << (#f) << "..."; \ |
| CHECK(!(f)); \ |
| } while (false) |
| |
| #define IGNORE_ERRORS(f) \ |
| do { \ |
| LOG(INFO) << "Running " << (#f) << "..."; \ |
| LOG_IF(INFO, !(f)) << "Ignoring failures: " << (#f); \ |
| } while (false) |
| |
| // TODO(yusukes): use android_filesystem_config.h. |
| #define AID_ROOT 0 /* traditional unix root user */ |
| #define AID_SYSTEM 1000 /* system server */ |
| #define AID_LOG 1007 /* log devices */ |
| #define AID_SDCARD_RW 1015 /* external storage write access */ |
| #define AID_MEDIA_RW 1023 /* internal media storage write access */ |
| #define AID_SHELL 2000 /* adb and debug shell user */ |
| #define AID_CACHE 2001 /* cache access */ |
| #define AID_EVERYBODY 9997 /* shared between all apps in the same profile */ |
| |
| namespace arc { |
| |
| namespace { |
| |
| // Lexicographically sorted. Usually you don't have to use these constants |
| // directly. Prefer base::FilePath variables in ArcPaths instead. |
| constexpr char kAdbdMountDirectory[] = "/run/arc/adbd"; |
| constexpr char kAdbdUnixSocketMountDirectory[] = "/run/arc/adb"; |
| constexpr char kAndroidCmdline[] = "/run/arc/cmdline.android"; |
| constexpr char kAndroidGeneratedPropertiesDirectory[] = |
| "/run/arc/host_generated"; |
| constexpr char kAndroidKmsgFifo[] = "/run/arc/android.kmsg.fifo"; |
| constexpr char kAndroidMutableSource[] = |
| "/opt/google/containers/android/rootfs/android-data"; |
| constexpr char kAndroidRootfsDirectory[] = |
| "/opt/google/containers/android/rootfs/root"; |
| constexpr char kArcVmPerBoardConfigPath[] = "/run/arcvm/host_generated/oem"; |
| constexpr char kOldApkCacheDir[] = |
| "/mnt/stateful_partition/unencrypted/cache/apk"; |
| constexpr char kApkCacheDir[] = "/mnt/stateful_partition/unencrypted/apkcache"; |
| constexpr char kArcBridgeSocketContext[] = "u:object_r:arc_bridge_socket:s0"; |
| constexpr char kArcBridgeSocketPath[] = "/run/chrome/arc_bridge.sock"; |
| constexpr char kBinFmtMiscDirectory[] = "/proc/sys/fs/binfmt_misc"; |
| constexpr char kBuildPropFile[] = "/usr/share/arc/properties/build.prop"; |
| constexpr char kBuildPropFileVm[] = "/usr/share/arcvm/properties/build.prop"; |
| constexpr char kCameraProfileDir[] = |
| "/mnt/stateful_partition/encrypted/var/cache/camera"; |
| constexpr char kCrasSocketDirectory[] = "/run/cras"; |
| constexpr char kDebugfsDirectory[] = "/run/arc/debugfs"; |
| constexpr char kFakeKptrRestrict[] = "/run/arc/fake_kptr_restrict"; |
| constexpr char kFakeMmapRndBits[] = "/run/arc/fake_mmap_rnd_bits"; |
| constexpr char kFakeMmapRndCompatBits[] = "/run/arc/fake_mmap_rnd_compat_bits"; |
| constexpr char kHostSideDalvikCacheDirectoryInContainer[] = |
| "/var/run/arc/dalvik-cache"; |
| constexpr char kHostDownloadsDirectory[] = "/home/chronos/user/Downloads"; |
| constexpr char kMediaMountDirectory[] = "/run/arc/media"; |
| constexpr char kMediaMyFilesDirectory[] = "/run/arc/media/MyFiles"; |
| constexpr char kMediaMyFilesDefaultDirectory[] = |
| "/run/arc/media/MyFiles-default"; |
| constexpr char kMediaMyFilesReadDirectory[] = "/run/arc/media/MyFiles-read"; |
| constexpr char kMediaMyFilesWriteDirectory[] = "/run/arc/media/MyFiles-write"; |
| constexpr char kMediaProfileFile[] = "media_profiles.xml"; |
| constexpr char kMediaRemovableDirectory[] = "/run/arc/media/removable"; |
| constexpr char kMediaRemovableDefaultDirectory[] = |
| "/run/arc/media/removable-default"; |
| constexpr char kMediaRemovableReadDirectory[] = "/run/arc/media/removable-read"; |
| constexpr char kMediaRemovableWriteDirectory[] = |
| "/run/arc/media/removable-write"; |
| constexpr char kObbMountDirectory[] = "/run/arc/obb"; |
| constexpr char kObbRootfsDirectory[] = |
| "/opt/google/containers/arc-obb-mounter/mountpoints/container-root"; |
| constexpr char kObbRootfsImage[] = |
| "/opt/google/containers/arc-obb-mounter/rootfs.squashfs"; |
| constexpr char kOemMountDirectory[] = "/run/arc/oem"; |
| constexpr char kPlatformXmlFileRelative[] = "etc/permissions/platform.xml"; |
| constexpr char kRestoreconAllowlistSync[] = "/sys/kernel/debug/sync"; |
| constexpr char kSdcardConfigfsDirectory[] = "/sys/kernel/config/sdcardfs"; |
| constexpr char kSdcardMountDirectory[] = "/run/arc/sdcard"; |
| constexpr char kSdcardRootfsDirectory[] = |
| "/opt/google/containers/arc-sdcard/mountpoints/container-root"; |
| constexpr char kSdcardRootfsImage[] = |
| "/opt/google/containers/arc-sdcard/rootfs.squashfs"; |
| constexpr char kSharedMountDirectory[] = "/run/arc/shared_mounts"; |
| constexpr char kSysfsCpu[] = "/sys/devices/system/cpu"; |
| constexpr char kSysfsTracing[] = "/sys/kernel/debug/tracing"; |
| // TODO(niwa): Remove kSystemBinArm* when we remove container Q+ support |
| // from arc-setup. |
| constexpr char kSystemBinArmDirectoryRelative[] = "system/bin/arm"; |
| constexpr char kSystemBinArm64DirectoryRelative[] = "system/bin/arm64"; |
| constexpr char kSystemLibArmDirectoryRelative[] = "system/lib/arm"; |
| constexpr char kSystemLibArm64DirectoryRelative[] = "system/lib64/arm64"; |
| constexpr char kSystemImage[] = "/opt/google/containers/android/system.raw.img"; |
| constexpr char kUsbDevicesDirectory[] = "/dev/bus/usb"; |
| constexpr char kZygotePreloadDoneFile[] = ".preload_done"; |
| |
| // Names for possible binfmt_misc entries. |
| constexpr const char* kBinFmtMiscEntryNames[] = {"arm_dyn", "arm_exe", |
| "arm64_dyn", "arm64_exe"}; |
| |
| // These are board-specific configuration settings, which are managed through |
| // the chromeos-config architecture. |
| // For details, see: |
| // https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/master/chromeos-config/#arc |
| // |
| // Board-specific config files are automatically managed/generated via project |
| // config repos. For details, see: |
| // https://chromium.googlesource.com/chromiumos/config/ |
| // For an example, see: |
| // https://chromium.googlesource.com/chromiumos/config/+/refs/heads/master/test/project/fake/fake/sw_build_config/platform/chromeos-config/generated/arc/ |
| constexpr char kHardwareFeaturesSetting[] = "/arc/hardware-features"; |
| constexpr char kMediaProfilesSetting[] = "/arc/media-profiles"; |
| constexpr char kSystemPath[] = "system-path"; |
| |
| constexpr uid_t kHostRootUid = 0; |
| constexpr gid_t kHostRootGid = 0; |
| |
| constexpr uid_t kHostChronosUid = 1000; |
| constexpr gid_t kHostChronosGid = 1000; |
| |
| constexpr uid_t kHostArcCameraUid = 603; |
| constexpr gid_t kHostArcCameraGid = 603; |
| |
| constexpr uid_t kShiftUid = 655360; |
| constexpr gid_t kShiftGid = 655360; |
| constexpr uid_t kRootUid = AID_ROOT + kShiftUid; |
| constexpr gid_t kRootGid = AID_ROOT + kShiftGid; |
| constexpr uid_t kSystemUid = AID_SYSTEM + kShiftUid; |
| constexpr gid_t kSystemGid = AID_SYSTEM + kShiftGid; |
| constexpr uid_t kMediaUid = AID_MEDIA_RW + kShiftUid; |
| constexpr gid_t kMediaGid = AID_MEDIA_RW + kShiftGid; |
| constexpr uid_t kShellUid = AID_SHELL + kShiftUid; |
| constexpr uid_t kShellGid = AID_SHELL + kShiftGid; |
| constexpr gid_t kSdcardRwGid = AID_SDCARD_RW + kShiftGid; |
| constexpr gid_t kEverybodyGid = AID_EVERYBODY + kShiftGid; |
| |
| // The maximum time to wait for /data/media setup. |
| constexpr base::TimeDelta kInstalldTimeout = base::TimeDelta::FromSeconds(60); |
| |
| // Property name for fingerprint. |
| constexpr char kFingerprintProp[] = "ro.build.fingerprint"; |
| |
| // System salt and arc salt file size. |
| constexpr size_t kSaltFileSize = 16; |
| |
| bool RegisterAllBinFmtMiscEntries(ArcMounter* mounter, |
| const base::FilePath& entry_directory, |
| const base::FilePath& binfmt_misc_directory) { |
| std::unique_ptr<ScopedMount> binfmt_misc_mount = |
| ScopedMount::CreateScopedMount(mounter, "binfmt_misc", |
| binfmt_misc_directory, "binfmt_misc", |
| MS_NOSUID | MS_NODEV | MS_NOEXEC, nullptr); |
| if (!binfmt_misc_mount) |
| return false; |
| |
| const base::FilePath binfmt_misc_register_path = |
| binfmt_misc_directory.Append("register"); |
| for (auto entry_name : kBinFmtMiscEntryNames) { |
| const base::FilePath entry_path = entry_directory.Append(entry_name); |
| // arm64_{dyn,exe} are only available on some boards/configurations. Only |
| // install them if they are present. |
| if (!base::PathExists(entry_path)) |
| continue; |
| const base::FilePath format_path = binfmt_misc_directory.Append(entry_name); |
| if (base::PathExists(format_path)) { |
| // If we had already registered this format earlier and failed |
| // unregistering it for some reason, the next operation will fail. |
| LOG(WARNING) << "Skipping re-registration of " << entry_path.value(); |
| continue; |
| } |
| if (!base::CopyFile(entry_path, binfmt_misc_register_path)) { |
| PLOG(ERROR) << "Failed to register " << entry_path.value(); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| void UnregisterBinFmtMiscEntry(const base::FilePath& entry_path) { |
| // This function is for Mode::STOP. Ignore errors to make sure to run all |
| // clean up code. |
| base::File entry(entry_path, base::File::FLAG_OPEN | base::File::FLAG_WRITE); |
| if (!entry.IsValid()) { |
| PLOG(INFO) << "Ignoring failure: Failed to open " << entry_path.value(); |
| return; |
| } |
| constexpr char kBinfmtMiscUnregister[] = "-1"; |
| IGNORE_ERRORS( |
| entry.Write(0, kBinfmtMiscUnregister, sizeof(kBinfmtMiscUnregister) - 1)); |
| } |
| |
| // Prepends |path_to_prepend| to each element in [first, last), and returns the |
| // result as a vector. |
| template <typename It> |
| std::vector<base::FilePath> PrependPath(It first, |
| It last, |
| const base::FilePath& path_to_prepend) { |
| std::vector<base::FilePath> result; |
| std::transform(first, last, std::back_inserter(result), |
| [&path_to_prepend](const char* path) { |
| return path_to_prepend.Append(path); |
| }); |
| return result; |
| } |
| |
| // Returns SDK version upgrade type to be sent to UMA. |
| ArcSdkVersionUpgradeType GetUpgradeType(AndroidSdkVersion system_sdk_version, |
| AndroidSdkVersion data_sdk_version) { |
| if (data_sdk_version == AndroidSdkVersion::UNKNOWN || // First boot |
| data_sdk_version == system_sdk_version) { |
| return ArcSdkVersionUpgradeType::NO_UPGRADE; |
| } |
| if (data_sdk_version == AndroidSdkVersion::ANDROID_M) { |
| if (system_sdk_version == AndroidSdkVersion::ANDROID_P) |
| return ArcSdkVersionUpgradeType::M_TO_P; |
| } |
| if (data_sdk_version == AndroidSdkVersion::ANDROID_N_MR1 && |
| system_sdk_version == AndroidSdkVersion::ANDROID_P) { |
| return ArcSdkVersionUpgradeType::N_TO_P; |
| } |
| if (data_sdk_version < system_sdk_version) { |
| LOG(ERROR) << "Unexpected Upgrade: data_sdk_version=" |
| << static_cast<int>(data_sdk_version) << " system_sdk_version=" |
| << static_cast<int>(system_sdk_version); |
| return ArcSdkVersionUpgradeType::UNKNOWN_UPGRADE; |
| } |
| LOG(ERROR) << "Unexpected Downgrade: data_sdk_version=" |
| << static_cast<int>(data_sdk_version) |
| << " system_sdk_version=" << static_cast<int>(system_sdk_version); |
| return ArcSdkVersionUpgradeType::UNKNOWN_DOWNGRADE; |
| } |
| |
| void CheckProcessIsAliveOrExit(const std::string& pid_str) { |
| pid_t pid; |
| EXIT_IF(!base::StringToInt(pid_str, &pid)); |
| if (!IsProcessAlive(pid)) { |
| LOG(ERROR) << "Process " << pid << " is NOT alive"; |
| exit(EXIT_FAILURE); |
| } |
| LOG(INFO) << "Process " << pid << " is still alive, at least as a zombie"; |
| // TODO(yusukes): Check if the PID is a zombie or not, and log accordingly. |
| } |
| |
| void CheckNamespacesAvailableOrExit(const std::string& pid_str) { |
| const base::FilePath proc("/proc"); |
| const base::FilePath ns = proc.Append(pid_str).Append("ns"); |
| EXIT_IF(!base::PathExists(ns)); |
| for (const char* entry : |
| {"cgroup", "ipc", "mnt", "net", "pid", "user", "uts"}) { |
| // Use the same syscall, open, as nsenter. Other syscalls like lstat may |
| // succeed when open doesn't. |
| const base::FilePath path_to_check = ns.Append(entry); |
| base::ScopedFD fd(open(path_to_check.value().c_str(), O_RDONLY)); |
| if (!fd.is_valid()) { |
| PLOG(ERROR) << "Failed to open " << path_to_check.value(); |
| exit(EXIT_FAILURE); |
| } |
| } |
| LOG(INFO) << "Process " << pid_str << " still has all namespace entries"; |
| } |
| |
| void CheckOtherProcEntriesOrExit(const std::string& pid_str) { |
| const base::FilePath proc("/proc"); |
| const base::FilePath proc_pid = proc.Append(pid_str); |
| for (const char* entry : {"cwd", "root"}) { |
| // Use open for the same reason as CheckNamespacesAvailableOrExit(). |
| const base::FilePath path_to_check = proc_pid.Append(entry); |
| base::ScopedFD fd(open(path_to_check.value().c_str(), O_RDONLY)); |
| if (!fd.is_valid()) { |
| PLOG(ERROR) << "Failed to open " << path_to_check.value(); |
| exit(EXIT_FAILURE); |
| } |
| } |
| LOG(INFO) << "Process " << pid_str << " still has other proc entries"; |
| } |
| |
| // Creates subdirectories under dalvik-cache directory if not exists. |
| bool CreateArtContainerDataDirectory( |
| const base::FilePath& art_dalvik_cache_directory) { |
| for (const std::string& isa : ArtContainer::GetIsas()) { |
| base::FilePath isa_directory = art_dalvik_cache_directory.Append(isa); |
| // Use the same permissions as the ones used in maybeCreateDalvikCache() in |
| // framework/base/cmds/app_process/app_main.cpp |
| if (!InstallDirectory(0711, kRootUid, kRootGid, isa_directory)) { |
| PLOG(ERROR) << "Failed to create art container data dir: " |
| << isa_directory.value(); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Stores relative path, mode_t for sdcard mounts. |
| // mode is an octal mask for file persmissions here. |
| struct EsdfsMount { |
| const char* relative_path; |
| mode_t mode; |
| gid_t gid; |
| }; |
| |
| const std::vector<EsdfsMount> GetEsdfsMounts(AndroidSdkVersion version) { |
| std::vector<EsdfsMount> mounts{ |
| {"default/emulated", 0006, kSdcardRwGid}, |
| {"read/emulated", 0027, kEverybodyGid}, |
| {"write/emulated", 0007, kEverybodyGid}, |
| }; |
| return mounts; |
| } |
| |
| // Esdfs mount options: |
| // -------------------- |
| // fsuid, fsgid : Lower filesystem's uid/gid. |
| // |
| // derive_gid : Changes uid/gid values on the lower filesystem for tracking |
| // storage user by apps and various categories. |
| // |
| // default_normal: Does not treat the default mount (using gid AID_SDCARD_RW) |
| // differently. Without this, the gid presented by the upper |
| // filesystem does not include the user, and would allow shell |
| // users to access all user’s data. |
| // |
| // mask : Masks away permissions. |
| // |
| // gid : Upper filesystem's group id. |
| // |
| // ns_fd : Namespace file descriptor used to set the base namespace for |
| // the esdfs mount, similar to the argument to setns(2). |
| // |
| // dl_uid, dl_gid: Downloads integration uid/gid. |
| // |
| // dl_loc : The Android download directory acts as an overlay on dl_loc. |
| |
| std::string CreateEsdfsMountOpts(uid_t fsuid, |
| gid_t fsgid, |
| mode_t mask, |
| uid_t userid, |
| gid_t gid, |
| int container_userns_fd) { |
| std::string opts = base::StringPrintf( |
| "fsuid=%d,fsgid=%d,derive_gid,default_normal,mask=%d,multiuser," |
| "gid=%d,dl_loc=%s,dl_uid=%d,dl_gid=%d,ns_fd=%d", |
| fsuid, fsgid, mask, gid, kHostDownloadsDirectory, kHostChronosUid, |
| kHostChronosGid, container_userns_fd); |
| LOG(INFO) << "Esdfs mount options: " << opts; |
| return opts; |
| } |
| |
| // Return path of layout_version based on |sdk_version|. |
| std::string GetInstalldLayoutRelativePath(AndroidSdkVersion sdk_version) { |
| return "data/.layout_version"; |
| } |
| |
| // Wait upto kInstalldTimeout for the sdcard source directory to be setup. |
| // On failure, exit. |
| bool WaitForSdcardSource(const base::FilePath& android_root, |
| AndroidSdkVersion sdk_version) { |
| bool ret; |
| base::TimeDelta elapsed; |
| // <android_root>/data path to synchronize with installd |
| const base::FilePath fs_version = |
| android_root.Append(GetInstalldLayoutRelativePath(sdk_version)); |
| |
| LOG(INFO) << "Waiting upto " << kInstalldTimeout |
| << " for installd to complete setting up /data."; |
| ret = WaitForPaths({fs_version}, kInstalldTimeout, &elapsed); |
| |
| LOG(INFO) << "Waiting for installd took " << elapsed.InSeconds() << "s"; |
| if (!ret) |
| LOG(ERROR) << "Timed out waiting for /data setup."; |
| |
| return ret; |
| } |
| |
| // Don't use this, use GetOrCreateArcSalt instead. Reads the 16-byte per-machine |
| // random salt. The salt is created once when the machine is first used, and |
| // wiped/regenerated on powerwash/recovery. When it's not available yet (which |
| // could happen only on OOBE boot), returns an empty string. |
| // TODO(yusukes): Remove this function after M73. |
| std::string GetSystemSalt() { |
| constexpr char kSaltFile[] = "/home/.shadow/salt"; |
| |
| std::string per_machine_salt; |
| if (!base::ReadFileToString(base::FilePath(kSaltFile), &per_machine_salt) || |
| per_machine_salt.size() < kSaltFileSize) { |
| LOG(WARNING) << kSaltFile << " is not available yet. OOBE boot?"; |
| return std::string(); |
| } |
| if (per_machine_salt.size() != kSaltFileSize) { |
| LOG(WARNING) << "Unexpected " << kSaltFile |
| << " size: " << per_machine_salt.size(); |
| } |
| return per_machine_salt; |
| } |
| |
| // Reads a random number for the container from /var/lib/misc/arc_salt. If |
| // the file does not exist, generates a new one. This file will be cleared |
| // and regenerated after powerwash. |
| std::string GetOrCreateArcSalt() { |
| constexpr char kArcSaltFile[] = "/var/lib/misc/arc_salt"; |
| constexpr mode_t kArcSaltFilePermissions = 0400; |
| |
| std::string arc_salt; |
| const base::FilePath arc_salt_file(kArcSaltFile); |
| if (!base::ReadFileToString(arc_salt_file, &arc_salt) || |
| arc_salt.size() != kSaltFileSize) { |
| // If system salt value is available, reuse the system salt to avoid |
| // clearing existing relocated boot*.art code. |
| arc_salt = GetSystemSalt(); |
| if (arc_salt.size() != kSaltFileSize) { |
| char rand_value[kSaltFileSize]; |
| crypto::RandBytes(rand_value, kSaltFileSize); |
| arc_salt = std::string(rand_value, kSaltFileSize); |
| } |
| if (!brillo::WriteToFileAtomic(arc_salt_file, arc_salt.data(), |
| arc_salt.size(), kArcSaltFilePermissions)) { |
| LOG(ERROR) << "Failed to write arc salt file."; |
| return std::string(); |
| } |
| } |
| return arc_salt; |
| } |
| |
| bool IsChromeOSUserAvailable(Mode mode) { |
| switch (mode) { |
| case Mode::BOOT_CONTINUE: |
| case Mode::CREATE_DATA: |
| case Mode::REMOVE_DATA: |
| case Mode::REMOVE_STALE_DATA: |
| return true; |
| case Mode::APPLY_PER_BOARD_CONFIG: |
| case Mode::SETUP: |
| case Mode::STOP: |
| case Mode::ONETIME_SETUP: |
| case Mode::ONETIME_STOP: |
| case Mode::PRE_CHROOT: |
| case Mode::MOUNT_SDCARD: |
| case Mode::UNMOUNT_SDCARD: |
| case Mode::UPDATE_RESTORECON_LAST: |
| case Mode::UNKNOWN: |
| return false; |
| } |
| } |
| |
| } // namespace |
| |
| // A struct that holds all the FilePaths ArcSetup uses. |
| struct ArcPaths { |
| static std::unique_ptr<ArcPaths> Create(Mode mode, const Config& config) { |
| base::FilePath android_data; |
| base::FilePath android_data_old; |
| |
| if (IsChromeOSUserAvailable(mode)) { |
| std::string chromeos_user = config.GetStringOrDie("CHROMEOS_USER"); |
| const base::FilePath root_path = |
| brillo::cryptohome::home::GetRootPath(chromeos_user); |
| |
| // Ensure the user directory exists. |
| EXIT_IF(root_path.empty() || !base::DirectoryExists(root_path)); |
| |
| android_data = root_path.Append("android-data"); |
| android_data_old = root_path.Append("android-data-old"); |
| } |
| return base::WrapUnique(new ArcPaths(android_data, android_data_old)); |
| } |
| |
| // Lexicographically sorted. |
| const base::FilePath adbd_mount_directory{kAdbdMountDirectory}; |
| const base::FilePath adbd_unix_socket_mount_directory{ |
| kAdbdUnixSocketMountDirectory}; |
| const base::FilePath android_cmdline{kAndroidCmdline}; |
| const base::FilePath android_generated_properties_directory{ |
| kAndroidGeneratedPropertiesDirectory}; |
| const base::FilePath android_kmsg_fifo{kAndroidKmsgFifo}; |
| const base::FilePath android_mutable_source{kAndroidMutableSource}; |
| const base::FilePath android_rootfs_directory{kAndroidRootfsDirectory}; |
| const base::FilePath arc_bridge_socket_path{kArcBridgeSocketPath}; |
| const base::FilePath old_apk_cache_dir{kOldApkCacheDir}; |
| const base::FilePath apk_cache_dir{kApkCacheDir}; |
| const base::FilePath art_dalvik_cache_directory{kArtDalvikCacheDirectory}; |
| const base::FilePath binfmt_misc_directory{kBinFmtMiscDirectory}; |
| const base::FilePath camera_profile_dir{kCameraProfileDir}; |
| const base::FilePath cras_socket_directory{kCrasSocketDirectory}; |
| const base::FilePath debugfs_directory{kDebugfsDirectory}; |
| const base::FilePath fake_kptr_restrict{kFakeKptrRestrict}; |
| const base::FilePath fake_mmap_rnd_bits{kFakeMmapRndBits}; |
| const base::FilePath fake_mmap_rnd_compat_bits{kFakeMmapRndCompatBits}; |
| const base::FilePath host_side_dalvik_cache_directory_in_container{ |
| kHostSideDalvikCacheDirectoryInContainer}; |
| const base::FilePath media_mount_directory{kMediaMountDirectory}; |
| const base::FilePath media_myfiles_directory{kMediaMyFilesDirectory}; |
| const base::FilePath media_myfiles_default_directory{ |
| kMediaMyFilesDefaultDirectory}; |
| const base::FilePath media_myfiles_read_directory{kMediaMyFilesReadDirectory}; |
| const base::FilePath media_myfiles_write_directory{ |
| kMediaMyFilesWriteDirectory}; |
| const base::FilePath media_profile_file{kMediaProfileFile}; |
| const base::FilePath media_removable_directory{kMediaRemovableDirectory}; |
| const base::FilePath media_removable_default_directory{ |
| kMediaRemovableDefaultDirectory}; |
| const base::FilePath media_removable_read_directory{ |
| kMediaRemovableReadDirectory}; |
| const base::FilePath media_removable_write_directory{ |
| kMediaRemovableWriteDirectory}; |
| const base::FilePath obb_mount_directory{kObbMountDirectory}; |
| const base::FilePath obb_rootfs_directory{kObbRootfsDirectory}; |
| const base::FilePath oem_mount_directory{kOemMountDirectory}; |
| const base::FilePath platform_xml_file_relative{kPlatformXmlFileRelative}; |
| const base::FilePath sdcard_configfs_directory{kSdcardConfigfsDirectory}; |
| const base::FilePath sdcard_mount_directory{kSdcardMountDirectory}; |
| const base::FilePath sdcard_rootfs_directory{kSdcardRootfsDirectory}; |
| const base::FilePath shared_mount_directory{kSharedMountDirectory}; |
| const base::FilePath sysfs_cpu{kSysfsCpu}; |
| const base::FilePath sysfs_tracing{kSysfsTracing}; |
| const base::FilePath system_bin_arm_directory_relative{ |
| kSystemBinArmDirectoryRelative}; |
| const base::FilePath system_bin_arm64_directory_relative{ |
| kSystemBinArm64DirectoryRelative}; |
| const base::FilePath system_lib_arm_directory_relative{ |
| kSystemLibArmDirectoryRelative}; |
| const base::FilePath system_lib64_arm64_directory_relative{ |
| kSystemLibArm64DirectoryRelative}; |
| const base::FilePath usb_devices_directory{kUsbDevicesDirectory}; |
| |
| const base::FilePath restorecon_allowlist_sync{kRestoreconAllowlistSync}; |
| |
| const base::FilePath android_data_directory; |
| const base::FilePath android_data_old_directory; |
| |
| private: |
| ArcPaths(const base::FilePath& android_data_directory, |
| const base::FilePath& android_data_old_directory) |
| : android_data_directory(android_data_directory), |
| android_data_old_directory(android_data_old_directory) {} |
| |
| DISALLOW_COPY_AND_ASSIGN(ArcPaths); |
| }; |
| |
| ArcSetup::ArcSetup(Mode mode, const base::FilePath& config_json) |
| : mode_(mode), |
| config_(config_json, base::Environment::Create()), |
| arc_mounter_(GetDefaultMounter()), |
| arc_paths_(ArcPaths::Create(mode_, config_)), |
| arc_setup_metrics_(std::make_unique<ArcSetupMetrics>()) { |
| CHECK(mode == Mode::APPLY_PER_BOARD_CONFIG || mode == Mode::CREATE_DATA || |
| mode == Mode::REMOVE_DATA || mode == Mode::REMOVE_STALE_DATA || |
| !config_json.empty()); |
| } |
| |
| ArcSetup::~ArcSetup() = default; |
| |
| // static |
| std::string ArcSetup::GetPlayStoreAutoUpdateParam( |
| PlayStoreAutoUpdate play_store_auto_update) { |
| switch (play_store_auto_update) { |
| case PlayStoreAutoUpdate::kDefault: |
| return std::string(); |
| case PlayStoreAutoUpdate::kOn: |
| case PlayStoreAutoUpdate::kOff: |
| return base::StringPrintf( |
| "androidboot.play_store_auto_update=%d ", |
| play_store_auto_update == PlayStoreAutoUpdate::kOn); |
| } |
| } |
| |
| // Note: This function has to be in sync with Android's arc-boot-type-detector. |
| // arc-boot-type-detector's DeleteExecutableFilesInData() function is very |
| // similar to this. |
| void ArcSetup::DeleteExecutableFilesInData( |
| bool should_delete_data_dalvik_cache_directory, |
| bool should_delete_data_app_executables) { |
| if (!should_delete_data_dalvik_cache_directory && |
| !should_delete_data_app_executables) { |
| return; |
| } |
| |
| // Move data/dalvik-cache. |
| if (should_delete_data_dalvik_cache_directory) { |
| MoveDirIntoDataOldDir(arc_paths_->android_data_directory.Append( |
| base::FilePath("data/dalvik-cache")), |
| arc_paths_->android_data_old_directory); |
| } |
| |
| // Move data/app/*/oat cache. |
| const base::FilePath app_directory = |
| arc_paths_->android_data_directory.Append(base::FilePath("data/app")); |
| if (should_delete_data_app_executables && base::PathExists(app_directory)) { |
| base::ElapsedTimer timer; |
| |
| base::FileEnumerator dir_enum(app_directory, false /* recursive */, |
| base::FileEnumerator::DIRECTORIES); |
| for (base::FilePath pkg_directory_name = dir_enum.Next(); |
| !pkg_directory_name.empty(); pkg_directory_name = dir_enum.Next()) { |
| MoveDirIntoDataOldDir(pkg_directory_name.Append("oat"), |
| arc_paths_->android_data_old_directory); |
| } |
| LOG(INFO) << "Moving data/app/<package_name>/oat took " |
| << timer.Elapsed().InMillisecondsRoundedUp() << "ms"; |
| } |
| } |
| |
| void ArcSetup::WaitForRtLimitsJob() { |
| constexpr base::TimeDelta kWaitForRtLimitsJobTimeOut = |
| base::TimeDelta::FromSeconds(10); |
| constexpr base::TimeDelta kSleepInterval = |
| base::TimeDelta::FromMilliseconds(100); |
| constexpr char kCgroupFilePath[] = |
| "/sys/fs/cgroup/cpu/session_manager_containers/cpu.rt_runtime_us"; |
| |
| base::ElapsedTimer timer; |
| const base::FilePath cgroup_file(kCgroupFilePath); |
| while (true) { |
| if (base::PathExists(cgroup_file)) { |
| std::string rt_runtime_us_str; |
| EXIT_IF(!base::ReadFileToString(cgroup_file, &rt_runtime_us_str)); |
| int rt_runtime_us = 0; |
| base::StringToInt(rt_runtime_us_str, &rt_runtime_us); |
| if (rt_runtime_us > 0) { |
| LOG(INFO) << cgroup_file.value() << " is set to " << rt_runtime_us; |
| break; |
| } |
| } |
| base::PlatformThread::Sleep(kSleepInterval); |
| CHECK_GE(kWaitForRtLimitsJobTimeOut, timer.Elapsed()) |
| << ": rt-limits job didn't start in " << kWaitForRtLimitsJobTimeOut; |
| } |
| |
| LOG(INFO) << "rt-limits job is ready in " |
| << timer.Elapsed().InMillisecondsRoundedUp() << " ms"; |
| } |
| |
| ArcBinaryTranslationType ArcSetup::IdentifyBinaryTranslationType() { |
| bool is_houdini_available = kUseHoudini || kUseHoudini64; |
| bool is_ndk_translation_available = kUseNdkTranslation; |
| |
| if (!base::PathExists(arc_paths_->android_rootfs_directory.Append( |
| "system/lib/libndk_translation.so"))) { |
| // Allow developers to use custom android build |
| // without ndk-translation in it. |
| is_ndk_translation_available = false; |
| } |
| |
| if (!is_houdini_available && !is_ndk_translation_available) |
| return ArcBinaryTranslationType::NONE; |
| |
| const bool prefer_ndk_translation = |
| (!is_houdini_available || |
| config_.GetBoolOrDie("NATIVE_BRIDGE_EXPERIMENT")); |
| |
| if (is_ndk_translation_available && prefer_ndk_translation) |
| return ArcBinaryTranslationType::NDK_TRANSLATION; |
| |
| return ArcBinaryTranslationType::HOUDINI; |
| } |
| |
| void ArcSetup::SetUpBinFmtMisc(ArcBinaryTranslationType bin_type) { |
| const std::string system_arch = base::SysInfo::OperatingSystemArchitecture(); |
| if (system_arch != "x86_64") |
| return; |
| |
| base::FilePath root_directory; |
| |
| switch (bin_type) { |
| case ArcBinaryTranslationType::NONE: { |
| // No binary translation at all, neither Houdini nor NDK translation. |
| return; |
| } |
| case ArcBinaryTranslationType::HOUDINI: { |
| root_directory = arc_paths_->android_rootfs_directory.Append("vendor"); |
| break; |
| } |
| case ArcBinaryTranslationType::NDK_TRANSLATION: { |
| root_directory = arc_paths_->android_rootfs_directory.Append("system"); |
| break; |
| } |
| } |
| |
| EXIT_IF(!RegisterAllBinFmtMiscEntries( |
| arc_mounter_.get(), root_directory.Append("etc/binfmt_misc"), |
| arc_paths_->binfmt_misc_directory)); |
| } |
| |
| void ArcSetup::SetUpAndroidData(const base::FilePath& bind_target) { |
| mode_t android_data_mode = 0700; |
| gid_t android_data_gid = kRootGid; |
| if (USE_ARCVM) { |
| // When ARCVM is enabled on the board, allow vm_concierge to access the |
| // directory. Note that vm_concierge runs as ugid crosvm in minijail. |
| uid_t dummy_uid; |
| EXIT_IF(!GetUserId("crosvm", &dummy_uid, &android_data_gid)); |
| android_data_mode = 0750; |
| } |
| EXIT_IF(!InstallDirectory(android_data_mode, kRootUid, android_data_gid, |
| arc_paths_->android_data_directory)); |
| |
| // match android/system/core/rootdir/init.rc |
| EXIT_IF(!InstallDirectory(0771, kSystemUid, kSystemGid, |
| arc_paths_->android_data_directory.Append("data"))); |
| |
| if (USE_ARCVM) { |
| // For ARCVM, create /data/media too since crosvm exports the directory via |
| // virtio-fs. |
| EXIT_IF(!InstallDirectory( |
| 0770, kMediaUid, kMediaGid, |
| arc_paths_->android_data_directory.Append("data").Append("media"))); |
| } |
| |
| // To make our bind-mount business easier, we first bind-mount the real |
| // android-data directory to bind_target (usually $ANDROID_MUTABLE_SOURCE). |
| // Then we do not need to pass the android-data path to other processes. |
| EXIT_IF(!arc_mounter_->BindMount(arc_paths_->android_data_directory, |
| bind_target)); |
| } |
| |
| void ArcSetup::UnmountSdcard() { |
| // We unmount here in both the ESDFS and the FUSE cases in order to |
| // clean up after Android's /system/bin/sdcard. However, the paths |
| // must be the same in both cases. |
| for (const auto& mount : GetEsdfsMounts(GetSdkVersion())) { |
| base::FilePath kDestDirectory = |
| arc_paths_->sdcard_mount_directory.Append(mount.relative_path); |
| IGNORE_ERRORS(arc_mounter_->Umount(kDestDirectory)); |
| } |
| |
| LOG(INFO) << "Unmount sdcard complete."; |
| } |
| |
| void ArcSetup::CreateContainerFilesAndDirectories() { |
| // If the log file exists, change the UID/GID here. We used to use |
| // android-root for the file, but now we use just root. The Upstart |
| // job does not (and cannot efficiently) do it. |
| // TODO(yusukes): This is a temporary migration code. Remove it once |
| // we hit M68. |
| const base::FilePath android_kmsg("/var/log/android.kmsg"); |
| if (base::PathExists(android_kmsg)) |
| EXIT_IF(!Chown(kHostRootUid, kHostRootGid, android_kmsg)); |
| |
| // Create the FIFO file and start its reader job. |
| RemoveAndroidKmsgFifo(); |
| EXIT_IF(mkfifo(arc_paths_->android_kmsg_fifo.value().c_str(), 0644) < 0); |
| { |
| base::ScopedFD fd = |
| brillo::OpenFifoSafely(arc_paths_->android_kmsg_fifo, O_RDONLY, 0); |
| EXIT_IF(!fd.is_valid()); |
| EXIT_IF(fchown(fd.get(), kRootUid, kRootGid) < 0); |
| } |
| EXIT_IF(!LaunchAndWait( |
| {"/sbin/initctl", "start", "--no-wait", "arc-kmsg-logger"})); |
| } |
| |
| void ArcSetup::ApplyPerBoardConfigurations() { |
| EXIT_IF(!brillo::MkdirRecursively( |
| arc_paths_->oem_mount_directory.Append("etc"), 0755) |
| .is_valid()); |
| |
| EXIT_IF(!arc_mounter_->Mount("tmpfs", arc_paths_->oem_mount_directory, |
| "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC, |
| "mode=0755")); |
| EXIT_IF(!brillo::MkdirRecursively( |
| arc_paths_->oem_mount_directory.Append("etc/permissions"), 0755) |
| .is_valid()); |
| |
| ApplyPerBoardConfigurationsInternal(arc_paths_->oem_mount_directory); |
| } |
| |
| void ArcSetup::ApplyPerBoardConfigurationsInternal( |
| const base::FilePath& oem_mount_directory) { |
| auto config = std::make_unique<brillo::CrosConfig>(); |
| config->Init(); |
| |
| base::FilePath media_profile_xml = |
| base::FilePath(arc_paths_->camera_profile_dir) |
| .Append(arc_paths_->media_profile_file); |
| |
| std::string media_profile_setting; |
| if (config->GetString(kMediaProfilesSetting, kSystemPath, |
| &media_profile_setting)) { |
| media_profile_xml = base::FilePath(media_profile_setting); |
| } else { |
| // TODO(chromium:1083652) Remove dynamic shell scripts once all overlays |
| // are migrated to static XML config. |
| const base::FilePath generate_camera_profile( |
| "/usr/bin/generate_camera_profile"); |
| if (base::PathExists(generate_camera_profile)) |
| EXIT_IF(!LaunchAndWait({generate_camera_profile.value()})); |
| } |
| |
| if (base::PathExists(media_profile_xml)) { |
| const base::FilePath new_media_profile_xml = |
| base::FilePath(oem_mount_directory) |
| .Append("etc") |
| .Append(arc_paths_->media_profile_file); |
| EXIT_IF( |
| !base::CopyFile(media_profile_xml, new_media_profile_xml)); |
| EXIT_IF( |
| !Chown(kHostArcCameraUid, kHostArcCameraGid, new_media_profile_xml)); |
| } |
| |
| base::FilePath hardware_features_xml("/etc/hardware_features.xml"); |
| |
| std::string hw_feature_setting; |
| if (config->GetString(kHardwareFeaturesSetting, kSystemPath, |
| &hw_feature_setting)) { |
| hardware_features_xml = base::FilePath(hw_feature_setting); |
| } |
| |
| if (!base::PathExists(hardware_features_xml)) |
| return; |
| |
| const base::FilePath platform_xml_file = |
| base::FilePath(oem_mount_directory) |
| .Append(arc_paths_->platform_xml_file_relative); |
| EXIT_IF(!base::CopyFile(hardware_features_xml, platform_xml_file)); |
| |
| // TODO(chromium:1083652) Remove dynamic shell scripts once all overlays |
| // are migrated to static XML config. |
| const base::FilePath board_hardware_features( |
| "/usr/sbin/board_hardware_features"); |
| if (!base::PathExists(board_hardware_features)) |
| return; |
| |
| // The board_hardware_features is usually made by shell script and should |
| // receive platform XML file argument in absolute path to avoid unexpected |
| // environment issues. |
| EXIT_IF(!LaunchAndWait( |
| {board_hardware_features.value(), platform_xml_file.value()})); |
| } |
| |
| void ArcSetup::SetUpSdcard() { |
| constexpr unsigned int mount_flags = |
| MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME; |
| const base::FilePath source_directory = |
| arc_paths_->android_mutable_source.Append("data/media"); |
| |
| const bool is_esdfs_supported = config_.GetBoolOrDie("USE_ESDFS"); |
| |
| // Get the container's user namespace file descriptor. |
| const int container_pid = config_.GetIntOrDie("CONTAINER_PID"); |
| base::ScopedFD container_userns_fd(HANDLE_EINTR( |
| open(base::StringPrintf("/proc/%d/ns/user", container_pid).c_str(), |
| O_RDONLY))); |
| |
| // SetUpSdcard can only be called from arc-sdcard is USE_ESDFS is enabled. |
| CHECK(is_esdfs_supported); |
| |
| // Installd setups up the user data directory skeleton on first-time boot. |
| // Wait for setup |
| EXIT_IF(!WaitForSdcardSource(arc_paths_->android_mutable_source, |
| GetSdkVersion())); |
| |
| for (const auto& mount : GetEsdfsMounts(GetSdkVersion())) { |
| base::FilePath dest_directory = |
| arc_paths_->sdcard_mount_directory.Append(mount.relative_path); |
| EXIT_IF(!arc_mounter_->Mount( |
| source_directory.value(), dest_directory, "esdfs", mount_flags, |
| CreateEsdfsMountOpts(kMediaUid, kMediaGid, mount.mode, kRootUid, |
| mount.gid, container_userns_fd.get()) |
| .c_str())); |
| } |
| |
| LOG(INFO) << "Esdfs setup complete."; |
| } |
| |
| void ArcSetup::SetUpSharedTmpfsForExternalStorage() { |
| EXIT_IF(!arc_mounter_->UmountIfExists(arc_paths_->sdcard_mount_directory)); |
| EXIT_IF(!brillo::MkdirRecursively(arc_paths_->sdcard_mount_directory, 0755) |
| .is_valid()); |
| EXIT_IF(!arc_mounter_->Mount("tmpfs", arc_paths_->sdcard_mount_directory, |
| "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC, |
| "mode=0755")); |
| EXIT_IF(!arc_mounter_->SharedMount(arc_paths_->sdcard_mount_directory)); |
| EXIT_IF( |
| !InstallDirectory(0755, kRootUid, kRootGid, |
| arc_paths_->sdcard_mount_directory.Append("default"))); |
| EXIT_IF(!InstallDirectory(0755, kRootUid, kRootGid, |
| arc_paths_->sdcard_mount_directory.Append("read"))); |
| EXIT_IF( |
| !InstallDirectory(0755, kRootUid, kRootGid, |
| arc_paths_->sdcard_mount_directory.Append("write"))); |
| |
| // Create the mount directories. In original Android, these are created in |
| // EmulatedVolume.cpp just before /system/bin/sdcard is fork()/exec()'ed. |
| // Following code just emulates it. The directories are owned by Android's |
| // root. |
| // Note that, these creation should be conceptually done in arc-sdcard |
| // container, but to keep it simpler, here create the directories instead. |
| EXIT_IF(!InstallDirectory( |
| 0755, kRootUid, kRootGid, |
| arc_paths_->sdcard_mount_directory.Append("default/emulated"))); |
| EXIT_IF(!InstallDirectory( |
| 0755, kRootUid, kRootGid, |
| arc_paths_->sdcard_mount_directory.Append("read/emulated"))); |
| EXIT_IF(!InstallDirectory( |
| 0755, kRootUid, kRootGid, |
| arc_paths_->sdcard_mount_directory.Append("write/emulated"))); |
| } |
| |
| void ArcSetup::SetUpFilesystemForObbMounter() { |
| EXIT_IF(!arc_mounter_->UmountIfExists(arc_paths_->obb_mount_directory)); |
| EXIT_IF(!brillo::MkdirRecursively(arc_paths_->obb_mount_directory, 0755) |
| .is_valid()); |
| EXIT_IF(!arc_mounter_->Mount("tmpfs", arc_paths_->obb_mount_directory, |
| "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC, |
| "mode=0755")); |
| EXIT_IF(!arc_mounter_->SharedMount(arc_paths_->obb_mount_directory)); |
| } |
| |
| bool ArcSetup::GenerateHostSideCodeInternal( |
| const base::FilePath& host_dalvik_cache_directory, |
| ArcCodeRelocationResult* result) { |
| *result = ArcCodeRelocationResult::ERROR_UNABLE_TO_RELOCATE; |
| base::ElapsedTimer timer; |
| std::unique_ptr<ArtContainer> art_container = |
| ArtContainer::CreateContainer(arc_mounter_.get(), GetSdkVersion()); |
| if (!art_container) { |
| LOG(ERROR) << "Failed to create art container"; |
| return false; |
| } |
| const std::string salt = GetOrCreateArcSalt(); |
| if (salt.empty()) { |
| *result = ArcCodeRelocationResult::SALT_EMPTY; |
| return false; |
| } |
| |
| const uint64_t offset_seed = GetArtCompilationOffsetSeed( |
| GetSystemBuildPropertyOrDie(kFingerprintProp), salt); |
| if (!art_container->PatchImage(offset_seed)) { |
| LOG(ERROR) << "Failed to relocate boot images"; |
| return false; |
| } |
| *result = ArcCodeRelocationResult::SUCCESS; |
| arc_setup_metrics_->SendCodeRelocationTime(timer.Elapsed()); |
| return true; |
| } |
| |
| bool ArcSetup::GenerateHostSideCode( |
| const base::FilePath& host_dalvik_cache_directory) { |
| ArcCodeRelocationResult result; |
| base::ElapsedTimer timer; |
| if (!GenerateHostSideCodeInternal(host_dalvik_cache_directory, &result)) { |
| // If anything fails, delete code in cache. |
| LOG(INFO) << "Failed to generate host-side code. Deleting existing code in " |
| << host_dalvik_cache_directory.value(); |
| DeleteFilesInDir(host_dalvik_cache_directory); |
| } |
| base::TimeDelta time_delta = timer.Elapsed(); |
| LOG(INFO) << "GenerateHostSideCode took " |
| << time_delta.InMillisecondsRoundedUp() << "ms"; |
| arc_setup_metrics_->SendCodeRelocationResult(result); |
| |
| return result == ArcCodeRelocationResult::SUCCESS; |
| } |
| |
| bool ArcSetup::InstallLinksToHostSideCodeInternal( |
| const base::FilePath& src_isa_directory, |
| const base::FilePath& dest_isa_directory, |
| const std::string& isa) { |
| constexpr char kDalvikCacheSELinuxContext[] = |
| "u:object_r:dalvikcache_data_file:s0"; |
| bool src_file_exists = false; |
| LOG(INFO) << "Adding symlinks to " << dest_isa_directory.value(); |
| |
| // Do the same as maybeCreateDalvikCache() in |
| // framework/base/cmds/app_process/app_main.cpp. |
| EXIT_IF(!InstallDirectory(0711, kRootUid, kRootGid, dest_isa_directory)); |
| EXIT_IF(!Chcon(kDalvikCacheSELinuxContext, dest_isa_directory)); |
| |
| base::FileEnumerator src_file_iter( |
| src_isa_directory, false /* recursive */, |
| base::FileEnumerator::FILES | base::FileEnumerator::SHOW_SYM_LINKS); |
| for (auto src_file = src_file_iter.Next(); !src_file.empty(); |
| src_file = src_file_iter.Next()) { |
| const base::FilePath base_name = src_file.BaseName(); |
| LOG(INFO) << "Processing " << base_name.value(); |
| |
| base::FilePath link_target; |
| if (S_ISLNK(src_file_iter.GetInfo().stat().st_mode)) { |
| // *boot*.oat files in |src_isa_directory| are links to /system. Create a |
| // link to /system. |
| EXIT_IF(!base::ReadSymbolicLink(src_file, &link_target)); |
| } else { |
| // Create a link to a host-side *boot*.art file. |
| link_target = |
| arc_paths_->host_side_dalvik_cache_directory_in_container.Append(isa) |
| .Append(base_name); |
| } |
| |
| const base::FilePath dest_file = dest_isa_directory.Append(base_name); |
| // Remove |dest_file| first when it exists. When |dest_file| is a symlink, |
| // this deletes the link itself. |
| IGNORE_ERRORS(base::DeleteFile(dest_file, false /* recursive */)); |
| EXIT_IF(!base::CreateSymbolicLink(link_target, dest_file)); |
| EXIT_IF(lchown(dest_file.value().c_str(), kRootUid, kRootGid) != 0); |
| EXIT_IF(!Chcon(kDalvikCacheSELinuxContext, dest_file)); |
| |
| LOG(INFO) << "Created a link to " << link_target.value(); |
| src_file_exists = true; |
| } |
| |
| return src_file_exists; |
| } |
| |
| bool ArcSetup::InstallLinksToHostSideCode() { |
| bool result = true; |
| base::ElapsedTimer timer; |
| const base::FilePath& src_directory = arc_paths_->art_dalvik_cache_directory; |
| const base::FilePath dest_directory = |
| arc_paths_->android_data_directory.Append("data/dalvik-cache"); |
| |
| EXIT_IF(!InstallDirectory(0771, kRootUid, kRootGid, dest_directory)); |
| // Iterate through each isa sub directory. For example, dalvik-cache/x86 and |
| // dalvik-cache/x86_64 |
| base::FileEnumerator src_directory_iter(src_directory, false, |
| base::FileEnumerator::DIRECTORIES); |
| for (auto src_isa_directory = src_directory_iter.Next(); |
| !src_isa_directory.empty(); |
| src_isa_directory = src_directory_iter.Next()) { |
| if (IsDirectoryEmpty(src_isa_directory)) |
| continue; |
| const std::string isa = src_isa_directory.BaseName().value(); |
| if (!InstallLinksToHostSideCodeInternal(src_isa_directory, |
| dest_directory.Append(isa), isa)) { |
| result = false; |
| LOG(ERROR) << "InstallLinksToHostSideCodeInternal() for " << isa |
| << " failed. " |
| << "Deleting container's /data/dalvik-cache..."; |
| DeleteExecutableFilesInData(true, /* delete dalvik cache */ |
| false /* delete data app executables */); |
| break; |
| } |
| } |
| |
| LOG(INFO) << "InstallLinksToHostSideCode() took " |
| << timer.Elapsed().InMillisecondsRoundedUp() << "ms"; |
| return result; |
| } |
| |
| void ArcSetup::CreateAndroidCmdlineFile( |
| bool is_dev_mode, |
| bool is_inside_vm, |
| bool is_debuggable, |
| PlayStoreAutoUpdate play_store_auto_update, |
| bool disable_system_default_app) { |
| const base::FilePath lsb_release_file_path("/etc/lsb-release"); |
| LOG(INFO) << "Developer mode is " << is_dev_mode; |
| LOG(INFO) << "Inside VM is " << is_inside_vm; |
| LOG(INFO) << "Debuggable is " << is_debuggable; |
| const std::string chromeos_channel = |
| GetChromeOsChannelFromFile(lsb_release_file_path); |
| LOG(INFO) << "ChromeOS channel is \"" << chromeos_channel << "\""; |
| const int arc_lcd_density = config_.GetIntOrDie("ARC_LCD_DENSITY"); |
| LOG(INFO) << "lcd_density is " << arc_lcd_density; |
| const int arc_file_picker = config_.GetIntOrDie("ARC_FILE_PICKER_EXPERIMENT"); |
| LOG(INFO) << "arc_file_picker is " << arc_file_picker; |
| const int arc_custom_tabs = config_.GetIntOrDie("ARC_CUSTOM_TABS_EXPERIMENT"); |
| LOG(INFO) << "arc_custom_tabs is " << arc_custom_tabs; |
| const std::string arc_camera_mode = "arcbridge"; |
| LOG(INFO) << "arc_camera_mode is " << arc_camera_mode; |
| |
| std::string native_bridge; |
| switch (IdentifyBinaryTranslationType()) { |
| case ArcBinaryTranslationType::NONE: |
| native_bridge = "0"; |
| break; |
| case ArcBinaryTranslationType::HOUDINI: |
| native_bridge = "libhoudini.so"; |
| break; |
| case ArcBinaryTranslationType::NDK_TRANSLATION: |
| native_bridge = "libndk_translation.so"; |
| break; |
| } |
| LOG(INFO) << "native_bridge is \"" << native_bridge << "\""; |
| |
| // Get the CLOCK_BOOTTIME offset and send it to the container as the at which |
| // the container "booted". Given that there is no way to namespace time in |
| // Linux, we need to communicate this in a userspace-only way. |
| // |
| // For the time being, the only component that uses this is bootstat. It uses |
| // it to timeshift all readings from CLOCK_BOOTTIME and be able to more |
| // accurately report the time against "Android boot". |
| struct timespec ts; |
| EXIT_IF(clock_gettime(CLOCK_BOOTTIME, &ts) != 0); |
| |
| // Note that we are intentionally not setting the ro.kernel.qemu property |
| // since that is tied to running the Android emulator, which has a few key |
| // differences: |
| // * It assumes that ADB is connected through the qemu pipe, which is not |
| // true in Chrome OS' case. |
| // * It controls whether the emulated GLES implementation should be used |
| // (but can be overriden by setting ro.kernel.qemu.gles to -1). |
| // * It disables a bunch of pixel formats and uses only RGB565. |
| // * It disables Bluetooth (which we might do regardless). |
| const std::string content = base::StringPrintf( |
| "androidboot.hardware=cheets " |
| "androidboot.container=1 " |
| "androidboot.dev_mode=%d " |
| "androidboot.disable_runas=%d " |
| "androidboot.host_is_in_vm=%d " |
| "androidboot.debuggable=%d " |
| "androidboot.lcd_density=%d " |
| "androidboot.native_bridge=%s " |
| "androidboot.arc_file_picker=%d " |
| "androidboot.arc_custom_tabs=%d " |
| "androidboot.arc_camera_mode=%s " |
| "androidboot.chromeos_channel=%s " |
| "%s" /* Play Store auto-update mode */ |
| "androidboot.disable_system_default_app=%d " |
| "androidboot.boottime_offset=%" PRId64 "\n" /* in nanoseconds */, |
| is_dev_mode, !is_dev_mode, is_inside_vm, is_debuggable, arc_lcd_density, |
| native_bridge.c_str(), arc_file_picker, arc_custom_tabs, |
| arc_camera_mode.c_str(), chromeos_channel.c_str(), |
| GetPlayStoreAutoUpdateParam(play_store_auto_update).c_str(), |
| disable_system_default_app, |
| ts.tv_sec * base::Time::kNanosecondsPerSecond + ts.tv_nsec); |
| |
| EXIT_IF(!WriteToFile(arc_paths_->android_cmdline, 0644, content)); |
| } |
| |
| void ArcSetup::CreateFakeProcfsFiles() { |
| // Android attempts to modify these files in procfs during init |
| // Since these files on the host side require root permissions to modify (real |
| // root, not android-root), we need to present fake versions to Android. |
| constexpr char kProcSecurityContext[] = "u:object_r:proc_security:s0"; |
| |
| EXIT_IF(!WriteToFile(arc_paths_->fake_kptr_restrict, 0644, "2\n")); |
| EXIT_IF(!Chown(kRootUid, kRootGid, arc_paths_->fake_kptr_restrict)); |
| EXIT_IF(!Chcon(kProcSecurityContext, arc_paths_->fake_kptr_restrict)); |
| |
| EXIT_IF(!WriteToFile(arc_paths_->fake_mmap_rnd_bits, 0644, "32\n")); |
| EXIT_IF(!Chown(kRootUid, kRootGid, arc_paths_->fake_mmap_rnd_bits)); |
| EXIT_IF(!Chcon(kProcSecurityContext, arc_paths_->fake_mmap_rnd_bits)); |
| |
| EXIT_IF(!WriteToFile(arc_paths_->fake_mmap_rnd_compat_bits, 0644, "16\n")); |
| EXIT_IF(!Chown(kRootUid, kRootGid, arc_paths_->fake_mmap_rnd_compat_bits)); |
| EXIT_IF(!Chcon(kProcSecurityContext, arc_paths_->fake_mmap_rnd_compat_bits)); |
| } |
| |
| void ArcSetup::SetUpMountPointForDebugFilesystem(bool is_dev_mode) { |
| const base::FilePath sync_mount_directory = |
| arc_paths_->debugfs_directory.Append("sync"); |
| |
| EXIT_IF(!InstallDirectory(0755, kHostRootUid, kHostRootGid, |
| arc_paths_->debugfs_directory)); |
| |
| // debug/sync does not exist on all kernels |
| EXIT_IF(!arc_mounter_->UmountIfExists(sync_mount_directory)); |
| |
| EXIT_IF( |
| !InstallDirectory(0755, kSystemUid, kSystemGid, sync_mount_directory)); |
| |
| const base::FilePath sync_directory("/sys/kernel/debug/sync"); |
| |
| if (base::DirectoryExists(sync_directory)) { |
| EXIT_IF(!Chown(kSystemUid, kSystemGid, sync_directory)); |
| EXIT_IF(!Chown(kSystemUid, kSystemGid, sync_directory.Append("info"))); |
| // Kernel change that introduces sw_sync is follows sync/info |
| if (base::PathExists(sync_directory.Append("sw_sync"))) |
| EXIT_IF(!Chown(kSystemUid, kSystemGid, sync_directory.Append("sw_sync"))); |
| |
| EXIT_IF(!arc_mounter_->BindMount(sync_directory, sync_mount_directory)); |
| } |
| |
| const base::FilePath tracing_mount_directory = |
| arc_paths_->debugfs_directory.Append("tracing"); |
| |
| EXIT_IF(!arc_mounter_->UmountIfExists(tracing_mount_directory)); |
| EXIT_IF(!InstallDirectory(0755, kHostRootUid, kHostRootGid, |
| tracing_mount_directory)); |
| |
| if (!is_dev_mode) |
| return; |
| |
| const base::FilePath tracing_directory("/sys/kernel/debug/tracing"); |
| EXIT_IF(!arc_mounter_->BindMount(tracing_directory, tracing_mount_directory)); |
| } |
| |
| void ArcSetup::MountDemoApps(const base::FilePath& demo_apps_image, |
| const base::FilePath& demo_apps_mount_directory) { |
| // Verify that the demo apps image is under an imageloader mount point. |
| EXIT_IF(demo_apps_image.ReferencesParent()); |
| EXIT_IF(!base::FilePath("/run/imageloader").IsParent(demo_apps_image)); |
| |
| // Create the target mount point directory. |
| EXIT_IF(!InstallDirectory(0700, kHostRootUid, kHostRootGid, |
| demo_apps_mount_directory)); |
| |
| // imageloader securely verifies images before mounting them, so we can trust |
| // the provided image and can mount it without MS_NOEXEC. |
| EXIT_IF(!arc_mounter_->LoopMount(demo_apps_image.value(), |
| demo_apps_mount_directory, |
| MS_RDONLY | MS_NODEV)); |
| } |
| |
| void ArcSetup::SetUpMountPointsForMedia() { |
| EXIT_IF(!arc_mounter_->UmountIfExists(arc_paths_->media_mount_directory)); |
| EXIT_IF(!InstallDirectory(0755, kRootUid, kSystemGid, |
| arc_paths_->media_mount_directory)); |
| |
| const std::string media_mount_options = |
| base::StringPrintf("mode=0755,uid=%u,gid=%u", kRootUid, kSystemGid); |
| EXIT_IF(!arc_mounter_->Mount("tmpfs", arc_paths_->media_mount_directory, |
| "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC, |
| media_mount_options.c_str())); |
| EXIT_IF(!arc_mounter_->SharedMount(arc_paths_->media_mount_directory)); |
| for (auto* directory : |
| {"removable", "removable-default", "removable-read", "removable-write", |
| "MyFiles", "MyFiles-default", "MyFiles-read", "MyFiles-write"}) { |
| EXIT_IF( |
| !InstallDirectory(0755, kMediaUid, kMediaGid, |
| arc_paths_->media_mount_directory.Append(directory))); |
| } |
| } |
| |
| void ArcSetup::SetUpMountPointForAdbd() { |
| EXIT_IF(!arc_mounter_->UmountIfExists(arc_paths_->adbd_mount_directory)); |
| EXIT_IF(!InstallDirectory(0770, kShellUid, kShellGid, |
| arc_paths_->adbd_mount_directory)); |
| |
| const std::string adbd_mount_options = |
| base::StringPrintf("mode=0770,uid=%u,gid=%u", kShellUid, kShellGid); |
| EXIT_IF(!arc_mounter_->Mount("tmpfs", arc_paths_->adbd_mount_directory, |
| "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC, |
| adbd_mount_options.c_str())); |
| EXIT_IF(!arc_mounter_->SharedMount(arc_paths_->adbd_mount_directory)); |
| } |
| |
| // Setup mount point for ADB over Unix sockets. This is used to enforce |
| // permission of the Unix sockets through SELinux. The mount is needed for |
| // ARC++ container whenever ADB debugging is enabled. |
| void ArcSetup::SetUpMountPointForAdbdUnixSocket() { |
| EXIT_IF(!arc_mounter_->UmountIfExists( |
| arc_paths_->adbd_unix_socket_mount_directory)); |
| EXIT_IF(!InstallDirectory(0775, kShellUid, kShellGid, |
| arc_paths_->adbd_unix_socket_mount_directory)); |
| const std::string adbd_unix_socket_mount_options = |
| base::StringPrintf("mode=0775,uid=%u,gid=%u", kShellUid, kShellGid); |
| EXIT_IF(!arc_mounter_->Mount("tmpfs", |
| arc_paths_->adbd_unix_socket_mount_directory, |
| "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC, |
| adbd_unix_socket_mount_options.c_str())); |
| EXIT_IF( |
| !arc_mounter_->SharedMount(arc_paths_->adbd_unix_socket_mount_directory)); |
| } |
| |
| void ArcSetup::CleanUpStaleMountPoints() { |
| EXIT_IF(!arc_mounter_->UmountIfExists(arc_paths_->media_myfiles_directory)); |
| EXIT_IF(!arc_mounter_->UmountIfExists( |
| arc_paths_->media_myfiles_default_directory)); |
| EXIT_IF( |
| !arc_mounter_->UmountIfExists(arc_paths_->media_myfiles_read_directory)); |
| EXIT_IF( |
| !arc_mounter_->UmountIfExists(arc_paths_->media_myfiles_write_directory)); |
| EXIT_IF(!arc_mounter_->UmountIfExists(arc_paths_->media_removable_directory)); |
| EXIT_IF(!arc_mounter_->UmountIfExists( |
| arc_paths_->media_removable_default_directory)); |
| EXIT_IF(!arc_mounter_->UmountIfExists( |
| arc_paths_->media_removable_read_directory)); |
| EXIT_IF(!arc_mounter_->UmountIfExists( |
| arc_paths_->media_removable_write_directory)); |
| // If the android_mutable_source path cannot be unmounted below continue |
| // anyway. This allows the mini-container to start and allows tests to |
| // exercise the mini-container (b/148185982). |
| IGNORE_ERRORS( |
| arc_mounter_->UmountIfExists(arc_paths_->android_mutable_source)); |
| } |
| |
| void ArcSetup::SetUpSharedMountPoints() { |
| EXIT_IF(!arc_mounter_->UmountIfExists(arc_paths_->shared_mount_directory)); |
| EXIT_IF(!InstallDirectory(0755, kRootUid, kRootGid, |
| arc_paths_->shared_mount_directory)); |
| // Use 0755 to make sure only the real root user can write to the shared |
| // mount point. |
| EXIT_IF(!arc_mounter_->Mount("tmpfs", arc_paths_->shared_mount_directory, |
| "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC, |
| "mode=0755")); |
| EXIT_IF(!arc_mounter_->SharedMount(arc_paths_->shared_mount_directory)); |
| } |
| |
| void ArcSetup::SetUpOwnershipForSdcardConfigfs() { |
| // Make sure <configfs>/sdcardfs/ and <configfs>/sdcardfs/extensions} are |
| // owned by android-root. |
| const base::FilePath extensions_dir = |
| arc_paths_->sdcard_configfs_directory.Append("extensions"); |
| if (base::PathExists(extensions_dir)) { |
| EXIT_IF(!Chown(kRootUid, kRootGid, arc_paths_->sdcard_configfs_directory)); |
| EXIT_IF(!Chown(kRootUid, kRootGid, extensions_dir)); |
| } |
| } |
| |
| void ArcSetup::RestoreContext() { |
| std::vector<base::FilePath> directories = { |
| // Restore the label for the file now since this is the only place to do |
| // so. |
| // The file is bind-mounted in the container as /proc/cmdline, but unlike |
| // /run/arc and /run/camera, the file cannot have the "mount_outside" |
| // option |
| // because /proc for the container is always mounted inside the container, |
| // and cmdline file has to be mounted on top of that. |
| arc_paths_->android_cmdline, |
| |
| arc_paths_->debugfs_directory, arc_paths_->obb_mount_directory, |
| arc_paths_->sdcard_mount_directory, arc_paths_->sysfs_cpu, |
| arc_paths_->sysfs_tracing, |
| }; |
| if (base::DirectoryExists(arc_paths_->restorecon_allowlist_sync)) |
| directories.push_back(arc_paths_->restorecon_allowlist_sync); |
| // usbfs does not exist on test VMs without any USB emulation, skip it there. |
| if (base::DirectoryExists(arc_paths_->usb_devices_directory)) |
| directories.push_back(arc_paths_->usb_devices_directory); |
| |
| EXIT_IF(!RestoreconRecursively(directories)); |
| } |
| |
| void ArcSetup::SetUpGraphicsSysfsContext() { |
| const base::FilePath sysfs_drm_path("/sys/class/drm"); |
| const std::string sysfs_drm_context("u:object_r:gpu_device:s0"); |
| const std::string render_node_pattern("renderD*"); |
| const std::vector<std::string> attrs{ |
| "uevent", "config", "vendor", "device", |
| "subsystem_vendor", "subsystem_device", "drm"}; |
| |
| base::FileEnumerator drm_directory( |
| sysfs_drm_path, false, |
| base::FileEnumerator::FileType::FILES | |
| base::FileEnumerator::FileType::DIRECTORIES | |
| base::FileEnumerator::FileType::SHOW_SYM_LINKS, |
| render_node_pattern); |
| |
| for (auto dev = drm_directory.Next(); !dev.empty(); |
| dev = drm_directory.Next()) { |
| auto device = Realpath(dev.Append("device")); |
| // If it's virtio gpu, actually the PCI device |
| // directory should be the parent directory. |
| if (device.BaseName().value().find("virtio") == 0) |
| device = device.DirName(); |
| for (const auto& attr : attrs) { |
| auto attr_path = device.Append(attr); |
| if (base::PathExists(attr_path)) |
| EXIT_IF(!Chcon(sysfs_drm_context, Realpath(attr_path))); |
| } |
| } |
| } |
| |
| void ArcSetup::SetUpPowerSysfsContext() { |
| const base::FilePath sysfs_power_supply_path("/sys/class/power_supply"); |
| const std::string sysfs_batteryinfo_context( |
| "u:object_r:sysfs_batteryinfo:s0"); |
| |
| base::FileEnumerator power_supply_dir( |
| sysfs_power_supply_path, false /* recursive */, |
| base::FileEnumerator::FileType::DIRECTORIES); |
| |
| for (auto power_supply = power_supply_dir.Next(); !power_supply.empty(); |
| power_supply = power_supply_dir.Next()) { |
| base::FileEnumerator power_supply_attrs( |
| power_supply, false /* recursive */, |
| base::FileEnumerator::FileType::FILES); |
| |
| for (auto attr = power_supply_attrs.Next(); !attr.empty(); |
| attr = power_supply_attrs.Next()) { |
| EXIT_IF(!Chcon(sysfs_batteryinfo_context, Realpath(attr))); |
| } |
| } |
| } |
| |
| void ArcSetup::MakeMountPointsReadOnly() { |
| // NOLINTNEXTLINE(runtime/int) |
| constexpr unsigned long remount_flags = |
| MS_RDONLY | MS_NOSUID | MS_NODEV | MS_NOEXEC; |
| constexpr char kMountOptions[] = "seclabel,mode=0755"; |
| |
| const std::string media_mount_options = |
| base::StringPrintf("mode=0755,uid=%u,gid=%u", kRootUid, kSystemGid); |
| |
| // Make these mount points readonly so that Android container cannot modify |
| // files/directories inside these filesystem. Android container cannot remove |
| // the readonly flag because it is run in user namespace. |
| // These directories are also bind-mounted as read-write into either the |
| // SDCARD or arc-obb-mounter container, which runs outside of the user |
| // namespace so that such a daemon can modify files/directories inside the |
| // filesystem (See also arc-sdcard.conf and arc-obb-mounter.conf). |
| EXIT_IF(!arc_mounter_->Remount(arc_paths_->sdcard_mount_directory, |
| remount_flags, kMountOptions)); |
| EXIT_IF(!arc_mounter_->Remount(arc_paths_->obb_mount_directory, remount_flags, |
| kMountOptions)); |
| EXIT_IF(!arc_mounter_->Remount(arc_paths_->media_mount_directory, |
| remount_flags, media_mount_options.c_str())); |
| } |
| |
| void ArcSetup::SetUpCameraProperty(const base::FilePath& build_prop) { |
| // Camera HAL V3 needs two properties from build.prop for picture taking. |
| // Copy the information to /var/cache. |
| const base::FilePath camera_prop_directory("/var/cache/camera"); |
| const base::FilePath camera_prop_file = |
| base::FilePath(camera_prop_directory).Append("camera.prop"); |
| EXIT_IF(!brillo::MkdirRecursively(camera_prop_directory, 0755).is_valid()); |
| |
| std::string content; |
| EXIT_IF(!base::ReadFileToString(build_prop, &content)); |
| |
| std::vector<std::string> properties = base::SplitString( |
| content, "\n", base::WhitespaceHandling::TRIM_WHITESPACE, |
| base::SplitResult::SPLIT_WANT_ALL); |
| const std::string kSystemManufacturer = "ro.product.system.manufacturer="; |
| const std::string kManufacturer = "ro.product.manufacturer="; |
| const std::string kSystemModel = "ro.product.system.model="; |
| const std::string kModel = "ro.product.model="; |
| std::string camera_properties; |
| for (const auto& property : properties) { |
| if (!property.compare(0, kManufacturer.length(), kManufacturer) || |
| !property.compare(0, kModel.length(), kModel)) { |
| // For Android P. |
| camera_properties += property + "\n"; |
| } else if (!property.compare(0, kSystemManufacturer.length(), |
| kSystemManufacturer)) { |
| // Android Q+ only has |kSystemManufacturer| in /system/build.prop, and |
| // |kSystemManufacturer| is copied to |kManufacturer| at boot time. Do |
| // the same here. |
| camera_properties += |
| kManufacturer + property.substr(kSystemManufacturer.length()) + "\n"; |
| } else if (!property.compare(0, kSystemModel.length(), kSystemModel)) { |
| // Do the same for |kSystemModel| for Android Q+. |
| camera_properties += |
| kModel + property.substr(kSystemModel.length()) + "\n"; |
| } |
| } |
| EXIT_IF(!WriteToFile(camera_prop_file, 0644, camera_properties)); |
| } |
| |
| void ArcSetup::SetUpSharedApkDirectory() { |
| // TODO(yusuks): Remove the migration code in M68. |
| if (base::PathExists(arc_paths_->old_apk_cache_dir)) { |
| // The old directory is found. Move it to the new location. Still call |
| // InstallDirectory() to make sure permissions, uid, and gid are all |
| // correct. |
| EXIT_IF( |
| !base::Move(arc_paths_->old_apk_cache_dir, arc_paths_->apk_cache_dir)); |
| } |
| |
| EXIT_IF(!InstallDirectory(0700, kSystemUid, kSystemGid, |
| arc_paths_->apk_cache_dir)); |
| } |
| |
| void ArcSetup::CleanUpBinFmtMiscSetUp() { |
| const std::string system_arch = base::SysInfo::OperatingSystemArchitecture(); |
| if (system_arch != "x86_64") |
| return; |
| std::unique_ptr<ScopedMount> binfmt_misc_mount = |
| ScopedMount::CreateScopedMount( |
| arc_mounter_.get(), "binfmt_misc", arc_paths_->binfmt_misc_directory, |
| "binfmt_misc", MS_NOSUID | MS_NODEV | MS_NOEXEC, nullptr); |
| // This function is for Mode::STOP. Ignore errors to make sure to run all |
| // clean up code. |
| if (!binfmt_misc_mount) { |
| PLOG(INFO) << "Ignoring failure: Failed to mount binfmt_misc"; |
| return; |
| } |
| |
| for (auto entry_name : kBinFmtMiscEntryNames) { |
| UnregisterBinFmtMiscEntry( |
| arc_paths_->binfmt_misc_directory.Append(entry_name)); |
| } |
| } |
| |
| AndroidSdkVersion ArcSetup::SdkVersionFromString( |
| const std::string& version_str) { |
| const std::string version_codename_str = |
| GetSystemBuildPropertyOrDie("ro.build.version.codename"); |
| if (version_codename_str != "REL") { |
| LOG(INFO) << "Not a release version; classifying as Android Unknown."; |
| return AndroidSdkVersion::UNKNOWN; |
| } |
| int version; |
| if (base::StringToInt(version_str, &version)) { |
| switch (version) { |
| case 23: |
| return AndroidSdkVersion::ANDROID_M; |
| case 25: |
| return AndroidSdkVersion::ANDROID_N_MR1; |
| case 28: |
| return AndroidSdkVersion::ANDROID_P; |
| } |
| } |
| |
| LOG(ERROR) << "Unknown SDK version : " << version_str; |
| return AndroidSdkVersion::UNKNOWN; |
| } |
| |
| AndroidSdkVersion ArcSetup::GetSdkVersion() { |
| const std::string version_str = |
| GetSystemBuildPropertyOrDie("ro.build.version.sdk"); |
| LOG(INFO) << "SDK version string: " << version_str; |
| |
| const AndroidSdkVersion version = SdkVersionFromString(version_str); |
| if (version == AndroidSdkVersion::UNKNOWN) |
| LOG(FATAL) << "Unknown SDK version: " << version_str; |
| return version; |
| } |
| |
| void ArcSetup::UnmountOnStop() { |
| // This function is for Mode::STOP. Use IGNORE_ERRORS to make sure to run all |
| // clean up code. |
| IGNORE_ERRORS(arc_mounter_->UmountIfExists( |
| arc_paths_->shared_mount_directory.Append("cache"))); |
| IGNORE_ERRORS(arc_mounter_->UmountIfExists( |
| arc_paths_->shared_mount_directory.Append("data"))); |
| IGNORE_ERRORS(arc_mounter_->LoopUmountIfExists( |
| arc_paths_->shared_mount_directory.Append("demo_apps"))); |
| IGNORE_ERRORS(arc_mounter_->UmountIfExists(arc_paths_->adbd_mount_directory)); |
| IGNORE_ERRORS( |
| arc_mounter_->UmountIfExists(arc_paths_->media_myfiles_directory)); |
| IGNORE_ERRORS(arc_mounter_->UmountIfExists( |
| arc_paths_->media_myfiles_default_directory)); |
| IGNORE_ERRORS( |
| arc_mounter_->UmountIfExists(arc_paths_->media_myfiles_read_directory)); |
| IGNORE_ERRORS( |
| arc_mounter_->UmountIfExists(arc_paths_->media_myfiles_write_directory)); |
| IGNORE_ERRORS( |
| arc_mounter_->UmountIfExists(arc_paths_->media_removable_directory)); |
| IGNORE_ERRORS(arc_mounter_->UmountIfExists( |
| arc_paths_->media_removable_default_directory)); |
| IGNORE_ERRORS( |
| arc_mounter_->UmountIfExists(arc_paths_->media_removable_read_directory)); |
| IGNORE_ERRORS(arc_mounter_->UmountIfExists( |
| arc_paths_->media_removable_write_directory)); |
| IGNORE_ERRORS( |
| arc_mounter_->UmountIfExists(arc_paths_->media_mount_directory)); |
| IGNORE_ERRORS(arc_mounter_->Umount(arc_paths_->sdcard_mount_directory)); |
| IGNORE_ERRORS( |
| arc_mounter_->UmountIfExists(arc_paths_->shared_mount_directory)); |
| IGNORE_ERRORS(arc_mounter_->Umount(arc_paths_->obb_mount_directory)); |
| IGNORE_ERRORS(arc_mounter_->Umount(arc_paths_->oem_mount_directory)); |
| IGNORE_ERRORS( |
| arc_mounter_->UmountIfExists(arc_paths_->android_mutable_source)); |
| IGNORE_ERRORS(arc_mounter_->UmountIfExists( |
| arc_paths_->debugfs_directory.Append("sync"))); |
| IGNORE_ERRORS(arc_mounter_->UmountIfExists( |
| arc_paths_->debugfs_directory.Append("tracing"))); |
| // Clean up in case this was not unmounted. |
| IGNORE_ERRORS( |
| arc_mounter_->UmountIfExists(arc_paths_->binfmt_misc_directory)); |
| IGNORE_ERRORS( |
| arc_mounter_->UmountIfExists(arc_paths_->android_rootfs_directory.Append( |
| arc_paths_->system_bin_arm_directory_relative))); |
| IGNORE_ERRORS( |
| arc_mounter_->UmountIfExists(arc_paths_->android_rootfs_directory.Append( |
| arc_paths_->system_bin_arm64_directory_relative))); |
| IGNORE_ERRORS( |
| arc_mounter_->UmountIfExists(arc_paths_->android_rootfs_directory.Append( |
| arc_paths_->system_lib_arm_directory_relative))); |
| IGNORE_ERRORS( |
| arc_mounter_->UmountIfExists(arc_paths_->android_rootfs_directory.Append( |
| arc_paths_->system_lib64_arm64_directory_relative))); |
| } |
| |
| void ArcSetup::RemoveAndroidKmsgFifo() { |
| // This function is for Mode::STOP. Use IGNORE_ERRORS to make sure to run all |
| // clean up code. |
| IGNORE_ERRORS( |
| base::DeleteFile(arc_paths_->android_kmsg_fifo, false /* recursive */)); |
| } |
| |
| // Note: This function has to be in sync with Android's arc-boot-type-detector. |
| // arc-boot-type-detector's main() function is very similar to this. |
| void ArcSetup::GetBootTypeAndDataSdkVersion( |
| ArcBootType* out_boot_type, AndroidSdkVersion* out_data_sdk_version) { |
| const std::string system_fingerprint = |
| GetSystemBuildPropertyOrDie(kFingerprintProp); |
| |
| // Note: The XML file name has to match com.android.server.pm.Settings's |
| // mSettingsFilename. This will be very unlikely to change, but we still need |
| // to be careful. |
| const base::FilePath packages_xml = |
| arc_paths_->android_data_directory.Append("data/system/packages.xml"); |
| |
| if (!base::PathExists(packages_xml)) { |
| // This path is taken when /data is empty, which is not an error. |
| LOG(INFO) << packages_xml.value() |
| << " does not exist. This is the very first boot aka opt-in."; |
| *out_boot_type = ArcBootType::FIRST_BOOT; |
| *out_data_sdk_version = AndroidSdkVersion::UNKNOWN; |
| return; |
| } |
| |
| // Get a fingerprint from /data/system/packages.xml. |
| std::string data_fingerprint; |
| std::string data_sdk_version; |
| if (!GetFingerprintAndSdkVersionFromPackagesXml( |
| packages_xml, &data_fingerprint, &data_sdk_version)) { |
| LOG(ERROR) << "Failed to get a fingerprint from " << packages_xml.value(); |
| // Return kFirstBootAfterUpdate so the caller invalidates art/oat files |
| // which is safer than returning kRegularBoot. |
| *out_boot_type = ArcBootType::FIRST_BOOT_AFTER_UPDATE; |
| *out_data_sdk_version = AndroidSdkVersion::UNKNOWN; |
| return; |
| } |
| |
| // If two fingerprints don't match, this is the first boot after OTA. |
| // Android's PackageManagerService.isUpgrade(), at least on M, N, and |
| // O releases, does exactly the same to detect OTA. |
| const bool ota_detected = system_fingerprint != data_fingerprint; |
| if (!ota_detected) { |
| LOG(INFO) << "This is regular boot."; |
| } else { |
| LOG(INFO) << "This is the first boot after OTA: system=" |
| << system_fingerprint << ", data=" << data_fingerprint; |
| } |
| LOG(INFO) << "Data SDK version: " << data_sdk_version; |
| LOG(INFO) << "System SDK version: " << static_cast<int>(GetSdkVersion()); |
| *out_boot_type = ota_detected ? ArcBootType::FIRST_BOOT_AFTER_UPDATE |
| : ArcBootType::REGULAR_BOOT; |
| *out_data_sdk_version = SdkVersionFromString(data_sdk_version); |
| } |
| |
| void ArcSetup::ShouldDeleteDataExecutables( |
| ArcBootType boot_type, |
| bool* out_should_delete_data_dalvik_cache_directory, |
| bool* out_should_delete_data_app_executables) { |
| if (boot_type == ArcBootType::FIRST_BOOT_AFTER_UPDATE) { |
| // Delete /data/dalvik-cache and /data/app/<package_name>/oat before the |
| // container starts since this is the first boot after OTA. |
| *out_should_delete_data_dalvik_cache_directory = true; |
| *out_should_delete_data_app_executables = true; |
| return; |
| } |
| // Otherwise, clear neither /data/dalvik-cache nor /data/app/*/oat. |
| *out_should_delete_data_dalvik_cache_directory = false; |
| *out_should_delete_data_app_executables = false; |
| } |
| |
| std::string ArcSetup::GetSerialNumber() { |
| const std::string chromeos_user = config_.GetStringOrDie("CHROMEOS_USER"); |
| const std::string salt = GetOrCreateArcSalt(); |
| EXIT_IF(salt.empty()); // at this point, the salt file should always exist. |
| return arc::GenerateFakeSerialNumber(chromeos_user, salt); |
| } |
| |
| // TODO(yusukes): Delete this function in M85. |
| void ArcSetup::DeleteUnusedCacheDirectory() { |
| // /home/.../android-data/cache is bind-mounted to /cache on N in |
| // MountSharedAndroidDirectories, but it is no longer bind-mounted on P. |
| EXIT_IF( |
| !MoveDirIntoDataOldDir(arc_paths_->android_data_directory.Append("cache"), |
| arc_paths_->android_data_old_directory)); |
| } |
| |
| void ArcSetup::MountSharedAndroidDirectories() { |
| const base::FilePath cache_directory = |
| arc_paths_->android_data_directory.Append("cache"); |
| const base::FilePath data_directory = |
| arc_paths_->android_data_directory.Append("data"); |
| |
| const base::FilePath shared_cache_directory = |
| arc_paths_->shared_mount_directory.Append("cache"); |
| const base::FilePath shared_data_directory = |
| arc_paths_->shared_mount_directory.Append("data"); |
| |
| if (!base::PathExists(shared_data_directory)) { |
| EXIT_IF(!InstallDirectory(0700, kHostRootUid, kHostRootGid, |
| shared_data_directory)); |
| } |
| |
| // First, make the original data directory a mount point and also make it |
| // executable. This has to be done *before* passing the directory into |
| // the shared mount point because the new flags won't be propagated if the |
| // mount point has already been shared with the MS_SLAVE one. |
| EXIT_IF(!arc_mounter_->BindMount(data_directory, data_directory)); |
| EXIT_IF( |
| !arc_mounter_->Remount(data_directory, MS_NOSUID | MS_NODEV, "seclabel")); |
| |
| // Finally, bind-mount /data to the shared mount point. |
| EXIT_IF(!arc_mounter_->Mount(data_directory.value(), shared_data_directory, |
| nullptr, MS_BIND, nullptr)); |
| |
| const std::string demo_session_apps = |
| config_.GetStringOrDie("DEMO_SESSION_APPS_PATH"); |
| if (!demo_session_apps.empty()) { |
| MountDemoApps(base::FilePath(demo_session_apps), |
| arc_paths_->shared_mount_directory.Append("demo_apps")); |
| } |
| } |
| |
| void ArcSetup::UnmountSharedAndroidDirectories() { |
| const base::FilePath data_directory = |
| arc_paths_->android_data_directory.Append("data"); |
| const base::FilePath shared_cache_directory = |
| arc_paths_->shared_mount_directory.Append("cache"); |
| const base::FilePath shared_data_directory = |
| arc_paths_->shared_mount_directory.Append("data"); |
| const base::FilePath shared_demo_apps_directory = |
| arc_paths_->shared_mount_directory.Append("demo_apps"); |
| |
| IGNORE_ERRORS(arc_mounter_->Umount(data_directory)); |
| IGNORE_ERRORS(arc_mounter_->UmountIfExists(shared_cache_directory)); |
| IGNORE_ERRORS(arc_mounter_->Umount(shared_data_directory)); |
| IGNORE_ERRORS(arc_mounter_->LoopUmountIfExists(shared_demo_apps_directory)); |
| IGNORE_ERRORS(arc_mounter_->Umount(arc_paths_->shared_mount_directory)); |
| } |
| |
| void ArcSetup::MaybeStartAdbdProxy(bool is_dev_mode, |
| bool is_inside_vm, |
| const std::string& serialnumber) { |
| if (!is_dev_mode || is_inside_vm) |
| return; |
| const base::FilePath adbd_config_path("/etc/arc/adbd.json"); |
| if (!base::PathExists(adbd_config_path)) |
| return; |
| // Poll the firmware to determine whether UDC is enabled or not. We're only |
| // stopping the process if it's explicitly disabled because some systems (like |
| // ARM) do not have this signal wired in and just rely on the presence of |
| // adbd.json. |
| if (LaunchAndWait({"/usr/bin/crossystem", "dev_enable_udc?0"})) |
| return; |
| |
| // Now that we have identified that the system is capable of continuing, touch |
| // the path where the FIFO will be located. |
| const base::FilePath control_endpoint_path("/run/arc/adbd/ep0"); |
| EXIT_IF(!CreateOrTruncate(control_endpoint_path, 0600)); |
| EXIT_IF(!Chown(kShellUid, kShellGid, control_endpoint_path)); |
| |
| EXIT_IF(!LaunchAndWait( |
| {"/sbin/initctl", "start", "--no-wait", "arc-adbd", |
| base::StringPrintf("SERIALNUMBER=%s", serialnumber.c_str())})); |
| } |
| |
| void ArcSetup::ContinueContainerBoot(ArcBootType boot_type, |
| const std::string& serialnumber) { |
| constexpr char kCommand[] = "/system/bin/arcbootcontinue"; |
| |
| const bool mount_demo_apps = |
| !config_.GetStringOrDie("DEMO_SESSION_APPS_PATH").empty(); |
| |
| std::string copy_packages_cache; |
| if (config_.GetBoolOrDie("SKIP_PACKAGES_CACHE_SETUP")) { |
| copy_packages_cache = "2"; |
| } else if (config_.GetBoolOrDie("COPY_PACKAGES_CACHE")) { |
| copy_packages_cache = "1"; |
| } else { |
| copy_packages_cache = "0"; |
| } |
| |
| // Run |kCommand| on the container side. The binary does the following: |
| // * Bind-mount the actual cache and data in /var/arc/shared_mounts to /cache |
| // and /data. |
| // * Set ro.boot.serialno and others. |
| // * Then, set ro.data_mounted=1 to ask /init to start the processes in the |
| // "main" class. |
| // We don't use -S (set UID), -G (set GID), and /system/bin/runcon here and |
| // instead run the command with UID 0 (host's root) because our goal is to |
| // remove or reduce [u]mount operations from the container, especially from |
| // its /init, and then to enforce it with SELinux. |
| const std::string pid_str = config_.GetStringOrDie("CONTAINER_PID"); |
| const std::vector<std::string> command_line = { |
| "/usr/bin/nsenter", "-t", pid_str, |
| "-m", // enter mount namespace |
| "-U", // enter user namespace |
| "-i", // enter System V IPC namespace |
| "-n", // enter network namespace |
| "-p", // enter pid namespace |
| "-r", // set the root directory |
| "-w", // set the working directory |
| "--", kCommand, "--serialno", serialnumber, "--disable-boot-completed", |
| config_.GetStringOrDie("DISABLE_BOOT_COMPLETED_BROADCAST"), |
| "--container-boot-type", std::to_string(static_cast<int>(boot_type)), |
| // When copy_packages_cache is set to "0" or "1", arccachesetup copies |
| // /system/etc/packages_cache.xml to /data/system/packages.xml. If it is |
| // set to "2", arccachesetup skips copying. When copy_packages_cache is |
| // "1" or "2", SystemServer copies /data/system/packages.xml |
| // to /data/system/packages_copy.xml after the initialization stage of |
| // PackageManagerService. |
| "--copy-packages-cache", copy_packages_cache, |
| "--skip-gms-core-cache-setup", |
| config_.GetStringOrDie("SKIP_GMS_CORE_CACHE_SETUP"), "--mount-demo-apps", |
| mount_demo_apps ? "1" : "0", "--is-demo-session", |
| config_.GetStringOrDie("IS_DEMO_SESSION"), "--locale", |
| config_.GetStringOrDie("LOCALE"), "--preferred-languages", |
| config_.GetStringOrDie("PREFERRED_LANGUAGES"), |
| // Whether ARC should transition the supervision setup |
| // "0": No transition necessary. |
| // "1": Child -> regular transition, should disable supervision. |
| // "2": Regular -> child transition, should enable supervision. |
| "--supervision-transition", |
| config_.GetStringOrDie("SUPERVISION_TRANSITION"), |
| "--enable-adb-sideloading", |
| config_.GetStringOrDie("ENABLE_ADB_SIDELOAD")}; |
| |
| base::ElapsedTimer timer; |
| if (!LaunchAndWait(command_line)) { |
| auto elapsed = timer.Elapsed().InMillisecondsRoundedUp(); |
| // ContinueContainerBoot() failed. Try to find out why it failed and log |
| // messages accordingly. If one of these functions calls exit(), it means |
| // that '/usr/bin/nsenter' is very likely the command that failed (rather |
| // than '/system/bin/arcbootcontinue'.) |
| CheckProcessIsAliveOrExit(pid_str); |
| CheckNamespacesAvailableOrExit(pid_str); |
| CheckOtherProcEntriesOrExit(pid_str); |
| |
| // Either nsenter or arcbootcontinue failed, but we don't know which. For |
| // example, arcbootcontinue may fail if it tries to set a property while |
| // init is being shut down or crashing. |
| LOG(ERROR) << kCommand << " failed for unknown reason after " << elapsed |
| << "ms"; |
| exit(EXIT_FAILURE); |
| } |
| LOG(INFO) << "Running " << kCommand << " took " |
| << timer.Elapsed().InMillisecondsRoundedUp() << "ms"; |
| |
| StartNetworking(); |
| } |
| |
| void ArcSetup::EnsureContainerDirectories() { |
| // uid/gid will be modified by cras.conf later. |
| // FIXME(b/64553266): Work around push_to_device/deploy_vendor_image running |
| // arc_setup after cras.conf by skipping the setup if the directory exists. |
| if (!base::DirectoryExists(arc_paths_->cras_socket_directory)) |
| EXIT_IF(!InstallDirectory(01770, kHostRootUid, kHostRootGid, |
| arc_paths_->cras_socket_directory)); |
| |
| // Chrome writes to /run/arc/host_generated even before starting the mini |
| // container. |
| EXIT_IF(!InstallDirectory(0755, kHostRootUid, kHostRootGid, |
| base::FilePath("/run/arc"))); |
| EXIT_IF(!InstallDirectory(0775, kHostRootUid, kHostChronosGid, |
| base::FilePath("/run/arc/host_generated"))); |
| } |
| |
| void ArcSetup::StartNetworking() { |
| if (!patchpanel::Client::New()->NotifyArcStartup( |
| config_.GetIntOrDie("CONTAINER_PID"))) { |
| LOG(ERROR) << "Failed to notify network service"; |
| } |
| } |
| |
| void ArcSetup::StopNetworking() { |
| // The container pid isn't available at this point. |
| if (!patchpanel::Client::New()->NotifyArcShutdown()) |
| LOG(ERROR) << "Failed to notify network service"; |
| } |
| |
| void ArcSetup::MountOnOnetimeSetup() { |
| const bool is_writable = config_.GetBoolOrDie("WRITABLE_MOUNT"); |
| const unsigned long writable_flag = // NOLINT(runtime/int) |
| is_writable ? 0 : MS_RDONLY; |
| |
| if (is_writable) |
| EXIT_IF(!arc_mounter_->Remount(base::FilePath("/"), 0 /* rw */, nullptr)); |
| |
| // Try to drop as many privileges as possible. If we end up starting ARC, |
| // we'll bind-mount the rootfs directory in the container-side with the |
| // appropriate flags. |
| EXIT_IF(!arc_mounter_->LoopMount( |
| kSystemImage, arc_paths_->android_rootfs_directory, |
| MS_NOEXEC | MS_NOSUID | MS_NODEV | writable_flag)); |
| |
| unsigned long kBaseFlags = // NOLINT(runtime/int) |
| writable_flag | MS_NOEXEC | MS_NOSUID; |
| |
| // Though we can technically mount these in mount namespace with minijail, |
| // we do not bother to handle loopback mounts by ourselves but just mount it |
| // in host namespace. Unlike system.raw.img, these images are always squashfs. |
| // Unlike system.raw.img, we don't remount them as exec either. The images do |
| // not contain any executables. |
| EXIT_IF(!arc_mounter_->LoopMount( |
| kSdcardRootfsImage, arc_paths_->sdcard_rootfs_directory, kBaseFlags)); |
| EXIT_IF(!arc_mounter_->LoopMount( |
| kObbRootfsImage, arc_paths_->obb_rootfs_directory, kBaseFlags)); |
| } |
| |
| void ArcSetup::UnmountOnOnetimeStop() { |
| IGNORE_ERRORS(arc_mounter_->LoopUmount(arc_paths_->obb_rootfs_directory)); |
| IGNORE_ERRORS(arc_mounter_->LoopUmount(arc_paths_->sdcard_rootfs_directory)); |
| IGNORE_ERRORS(arc_mounter_->LoopUmount(arc_paths_->android_rootfs_directory)); |
| } |
| |
| void ArcSetup::BindMountInContainerNamespaceOnPreChroot( |
| const base::FilePath& rootfs, |
| const ArcBinaryTranslationType binary_translation_type) { |
| if (binary_translation_type == ArcBinaryTranslationType::HOUDINI) { |
| // system_lib_arm either is empty or contains ndk-translation's libraries. |
| // Since houdini is selected bind-mount its libraries instead. |
| EXIT_IF(!arc_mounter_->BindMount( |
| rootfs.Append("vendor/lib/arm"), |
| rootfs.Append(arc_paths_->system_lib_arm_directory_relative))); |
| |
| if (kUseHoudini64) { |
| // Bind mount arm64 directory for houdini64. |
| EXIT_IF(!arc_mounter_->BindMount( |
| rootfs.Append("vendor/lib64/arm64"), |
| rootfs.Append(arc_paths_->system_lib64_arm64_directory_relative))); |
| } |
| } |
| |
| const base::FilePath proc_rnd_compat = |
| rootfs.Append("proc/sys/vm/mmap_rnd_compat_bits"); |
| |
| if (base::PathExists(proc_rnd_compat)) { |
| EXIT_IF(!arc_mounter_->BindMount(arc_paths_->fake_mmap_rnd_compat_bits, |
| proc_rnd_compat)); |
| } |
| } |
| |
| void ArcSetup::RestoreContextOnPreChroot(const base::FilePath& rootfs) { |
| { |
| // The list of container directories that need to be recursively re-labeled. |
| // Note that "var/run" (the parent directory) is not in the list because |
| // some of entries in the directory are on a read-only filesystem. |
| // Note: The array is for directories. Do no add files to the array. Add |
| // them to |kPaths| below instead. |
| std::vector<const char*> directories{"dev", |
| "oem/etc", |
| "var/run/arc/adb", |
| "var/run/arc/apkcache", |
| "var/run/arc/dalvik-cache", |
| "var/run/chrome", |
| "var/run/cras"}; |
| |
| // Transform |kDirectories| because the mount points are visible only in |
| // |rootfs|. Note that Chrome OS' file_contexts does recognize paths with |
| // the |rootfs| prefix. |
| EXIT_IF(!RestoreconRecursively( |
| PrependPath(directories.cbegin(), directories.cend(), rootfs))); |
| } |
| |
| { |
| // Do the same as above for files and directories but in a non-recursive |
| // way. |
| constexpr std::array<const char*, 5> kPaths{ |
| "default.prop", "sys/kernel/debug", "system/build.prop", "var/run/arc", |
| "vendor/build.prop"}; |
| EXIT_IF(!Restorecon(PrependPath(kPaths.cbegin(), kPaths.cend(), rootfs))); |
| } |
| } |
| |
| void ArcSetup::CreateDevColdbootDoneOnPreChroot(const base::FilePath& rootfs) { |
| const base::FilePath coldboot_done = rootfs.Append("dev/.coldboot_done"); |
| EXIT_IF(!CreateOrTruncate(coldboot_done, 0755)); |
| EXIT_IF(!Chown(kRootUid, kRootGid, coldboot_done)); |
| } |
| |
| void ArcSetup::OnSetup() { |
| const bool is_dev_mode = config_.GetBoolOrDie("CHROMEOS_DEV_MODE"); |
| const bool is_inside_vm = config_.GetBoolOrDie("CHROMEOS_INSIDE_VM"); |
| const bool is_debuggable = config_.GetBoolOrDie("ANDROID_DEBUGGABLE"); |
| const bool disable_system_default_app = |
| config_.GetBoolOrDie("DISABLE_SYSTEM_DEFAULT_APP"); |
| |
| // The host-side dalvik-cache directory is mounted into the container |
| // via the json file. Create it regardless of whether the code integrity |
| // feature is enabled. |
| EXIT_IF( |
| !CreateArtContainerDataDirectory(arc_paths_->art_dalvik_cache_directory)); |
| |
| // Mount host-compiled and host-verified .art and .oat files. The container |
| // will see these files, but other than that, the /data and /cache |
| // directories are empty and read-only which is the best for security. |
| |
| // Unconditionally generate host-side code here. |
| { |
| base::ElapsedTimer timer; |
| EXIT_IF(!GenerateHostSideCode(arc_paths_->art_dalvik_cache_directory)); |
| EXIT_IF(!Chown(kRootUid, kRootGid, arc_paths_->art_dalvik_cache_directory)); |
| // Remove the file zygote may have created. |
| IGNORE_ERRORS(base::DeleteFile( |
| arc_paths_->art_dalvik_cache_directory.Append(kZygotePreloadDoneFile), |
| false /* recursive */)); |
| |
| // For now, integrity checking time is the time needed to relocate |
| // boot*.art files because of b/67912719. Once TPM is enabled, this will |
| // report the total time spend on code verification + [relocation + sign] |
| arc_setup_metrics_->SendCodeIntegrityCheckingTotalTime(timer.Elapsed()); |
| } |
| |
| // Make sure directories for all ISA are there just to make config.json happy. |
| for (const auto* isa : {"arm", "arm64", "x86", "x86_64"}) { |
| EXIT_IF(!brillo::MkdirRecursively( |
| arc_paths_->art_dalvik_cache_directory.Append(isa), 0755) |
| .is_valid()); |
| } |
| |
| PlayStoreAutoUpdate play_store_auto_update; |
| bool play_store_auto_update_on; |
| // PLAY_AUTO_UPDATE forces Play Store auto-update feature to on or off. If not |
| // set, its state is left unchanged. |
| if (config_.GetBool("PLAY_STORE_AUTO_UPDATE", &play_store_auto_update_on)) { |
| play_store_auto_update = play_store_auto_update_on |
| ? PlayStoreAutoUpdate::kOn |
| : PlayStoreAutoUpdate::kOff; |
| } else { |
| play_store_auto_update = PlayStoreAutoUpdate::kDefault; |
| } |
| |
| SetUpSharedMountPoints(); |
| CreateContainerFilesAndDirectories(); |
| ApplyPerBoardConfigurations(); |
| SetUpSharedTmpfsForExternalStorage(); |
| SetUpFilesystemForObbMounter(); |
| CreateAndroidCmdlineFile(is_dev_mode, is_inside_vm, is_debuggable, |
| play_store_auto_update, disable_system_default_app); |
| CreateFakeProcfsFiles(); |
| SetUpMountPointForDebugFilesystem(is_dev_mode); |
| SetUpMountPointsForMedia(); |
| SetUpMountPointForAdbd(); |
| SetUpMountPointForAdbdUnixSocket(); |
| CleanUpStaleMountPoints(); |
| RestoreContext(); |
| SetUpGraphicsSysfsContext(); |
| SetUpPowerSysfsContext(); |
| MakeMountPointsReadOnly(); |
| SetUpCameraProperty(base::FilePath(kBuildPropFile)); |
| SetUpSharedApkDirectory(); |
| |
| // These should be the last thing OnSetup() does because the job and |
| // directories are not needed for arc-setup. Only the container's startup code |
| // (in session_manager side) requires the job and directories. |
| WaitForRtLimitsJob(); |
| } |
| |
| void ArcSetup::OnBootContinue() { |
| const bool is_dev_mode = config_.GetBoolOrDie("CHROMEOS_DEV_MODE"); |
| const bool is_inside_vm = config_.GetBoolOrDie("CHROMEOS_INSIDE_VM"); |
| const std::string serialnumber = GetSerialNumber(); |
| |
| ArcBootType boot_type; |
| AndroidSdkVersion data_sdk_version; |
| GetBootTypeAndDataSdkVersion(&boot_type, &data_sdk_version); |
| |
| arc_setup_metrics_->SendSdkVersionUpgradeType( |
| GetUpgradeType(GetSdkVersion(), data_sdk_version)); |
| |
| if (ShouldDeleteAndroidData(GetSdkVersion(), data_sdk_version)) { |
| EXIT_IF(!MoveDirIntoDataOldDir(arc_paths_->android_data_directory, |
| arc_paths_->android_data_old_directory)); |
| } |
| |
| bool should_delete_data_dalvik_cache_directory; |
| bool should_delete_data_app_executables; |
| ShouldDeleteDataExecutables(boot_type, |
| &should_delete_data_dalvik_cache_directory, |
| &should_delete_data_app_executables); |
| DeleteExecutableFilesInData(should_delete_data_dalvik_cache_directory, |
| should_delete_data_app_executables); |
| |
| // The socket isn't created when the mini-container is started, so the |
| // arc-setup --mode=pre-chroot call won't label it. Label it here instead. |
| EXIT_IF(!Chcon(kArcBridgeSocketContext, arc_paths_->arc_bridge_socket_path)); |
| |
| // Set up |android_mutable_source|. Although the container does not use |
| // the directory directly, we should still set up the directory so that |
| // session_manager can delete (to be more precise, move) the directory |
| // on opt-out. Since this creates cache and data directories when they |
| // don't exist, this has to be done before calling ShareAndroidData(). |
| SetUpAndroidData(arc_paths_->android_mutable_source); |
| |
| if (!InstallLinksToHostSideCode()) { |
| arc_setup_metrics_->SendBootContinueCodeInstallationResult( |
| ArcBootContinueCodeInstallationResult::ERROR_CANNOT_INSTALL_HOST_CODE); |
| } else { |
| arc_setup_metrics_->SendBootContinueCodeInstallationResult( |
| ArcBootContinueCodeInstallationResult::SUCCESS); |
| } |
| |
| // Set up /run/arc/shared_mounts/{cache,data,demo_apps} to expose the user's |
| // data to the container. Demo apps are setup only for demo sessions. |
| MountSharedAndroidDirectories(); |
| |
| MaybeStartAdbdProxy(is_dev_mode, is_inside_vm, serialnumber); |
| |
| // Asks the container to continue boot. |
| ContinueContainerBoot(boot_type, serialnumber); |
| |
| // Unmount /run/arc/shared_mounts and its children. They are unnecessary at |
| // this point. |
| UnmountSharedAndroidDirectories(); |
| |
| DeleteUnusedCacheDirectory(); |
| |
| const std::string env_to_pass = base::StringPrintf( |
| "CONTAINER_PID=%d", config_.GetIntOrDie("CONTAINER_PID")); |
| EXIT_IF(!LaunchAndWait( |
| {"/sbin/initctl", "start", "--no-wait", "arc-sdcard", env_to_pass})); |
| } |
| |
| void ArcSetup::OnStop() { |
| StopNetworking(); |
| CleanUpBinFmtMiscSetUp(); |
| UnmountOnStop(); |
| RemoveAndroidKmsgFifo(); |
| } |
| |
| void ArcSetup::OnOnetimeSetup() { |
| EnsureContainerDirectories(); |
| MountOnOnetimeSetup(); |
| |
| // Setup ownership for <configfs>/sdcard, if the directory exists. |
| SetUpOwnershipForSdcardConfigfs(); |
| } |
| |
| void ArcSetup::OnOnetimeStop() { |
| UnmountOnOnetimeStop(); |
| } |
| |
| void ArcSetup::OnPreChroot() { |
| // Note: Do not try to create a directory in tmpfs here. Recent (4.8+) |
| // kernel doesn't allow us to do so and returns EOVERFLOW. b/78262683 |
| |
| // binfmt_misc setup has to be done before entering container |
| // namespace below (namely before CreateScopedMountNamespaceForPid). |
| ArcBinaryTranslationType binary_translation_type = |
| IdentifyBinaryTranslationType(); |
| SetUpBinFmtMisc(binary_translation_type); |
| |
| int container_pid; |
| base::FilePath rootfs; |
| |
| EXIT_IF(!GetOciContainerState(base::FilePath("/dev/stdin"), &container_pid, |
| &rootfs)); |
| |
| // Enter the container namespace since the paths we want to re-label here |
| // are easier to access from inside of it. |
| std::unique_ptr<brillo::ScopedMountNamespace> container_mount_ns = |
| brillo::ScopedMountNamespace::CreateForPid(container_pid); |
| PLOG_IF(FATAL, !container_mount_ns) |
| << "Failed to enter the container mount namespace"; |
| |
| BindMountInContainerNamespaceOnPreChroot(rootfs, binary_translation_type); |
| RestoreContextOnPreChroot(rootfs); |
| CreateDevColdbootDoneOnPreChroot(rootfs); |
| } |
| |
| void ArcSetup::OnRemoveData() { |
| EXIT_IF(!MoveDirIntoDataOldDir(arc_paths_->android_data_directory, |
| arc_paths_->android_data_old_directory)); |
| } |
| |
| void ArcSetup::OnRemoveStaleData() { |
| // To protect itself, base::SafeFD::RmDir() uses a default maximum |
| // recursion depth of 256. In this case, we are deleting the user's |
| // arbitrary filesystem and want to be more generous. However, RmDir() |
| // uses one fd per path level when recursing so we will have the max |
| // number of fds per process as an upper bound (default 1024). Leave a |
| // 25% buffer below this default 1024 limit to give lots of room for |
| // incidental usage elsewhere in the process. Use this everywhere here |
| // for consistency. |
| constexpr int kRmdirMaxDepth = 768; |
| |
| brillo::SafeFD root = brillo::SafeFD::Root().first; |
| |
| if (USE_ARCVM) { |
| // On ARCVM, stale *.odex files are kept in /data/vendor/arc. |
| base::FilePath arcvm_stale_odex = arc_paths_->android_data_directory.Append( |
| "data/vendor/arc/old_arc_executables_pre_ota"); |
| brillo::SafeFD parent_dir = |
| root.OpenExistingDir(arcvm_stale_odex.DirName()).first; |
| brillo::SafeFD::Error err = parent_dir.Rmdir( |
| arcvm_stale_odex.BaseName().value(), true /*recursive*/, kRmdirMaxDepth, |
| true /*keep_going*/); |
| if (brillo::SafeFD::IsError(err) && |
| err != brillo::SafeFD::Error::kDoesNotExist) { |
| LOG(ERROR) << "Errors while cleaning old data from " |
| << arcvm_stale_odex.value(); |
| } |
| } |
| |
| // Moving data to android_data_old no longer has race conditions so it is safe |
| // to delete the entire directory. |
| brillo::SafeFD parent_dir = |
| root.OpenExistingDir(arc_paths_->android_data_old_directory.DirName()) |
| .first; |
| brillo::SafeFD::Error err = parent_dir.Rmdir( |
| arc_paths_->android_data_old_directory.BaseName().value(), |
| true /*recursive*/, kRmdirMaxDepth, true /*keep_going*/); |
| if (brillo::SafeFD::IsError(err) && |
| err != brillo::SafeFD::Error::kDoesNotExist) { |
| LOG(ERROR) << "Errors while cleaning old data from " |
| << arc_paths_->android_data_old_directory.value(); |
| } |
| } |
| |
| void ArcSetup::OnApplyPerBoardConfig() { |
| ApplyPerBoardConfigurationsInternal(base::FilePath(kArcVmPerBoardConfigPath)); |
| SetUpCameraProperty(base::FilePath(kBuildPropFileVm)); |
| |
| // Unlike ARC container, ARCVM's platform.xml has to be owned by chronos. |
| brillo::SafeFD fd; |
| brillo::SafeFD::Error err; |
| std::tie(fd, err) = brillo::SafeFD::Root().first.OpenExistingFile( |
| base::FilePath(kArcVmPerBoardConfigPath) |
| .Append(kPlatformXmlFileRelative)); |
| if (err == brillo::SafeFD::Error::kDoesNotExist) |
| return; // the board does not have the file. |
| EXIT_IF(!fd.is_valid()); |
| EXIT_IF(fchown(fd.get(), kHostChronosUid, kHostChronosGid)); |
| } |
| |
| void ArcSetup::OnCreateData() { |
| const base::FilePath bind_target( |
| config_.GetStringOrDie("ANDROID_MUTABLE_SOURCE")); |
| // bind_target may be already bound if arc-create-data has previously run |
| // during this session. |
| EXIT_IF(!arc_mounter_->UmountIfExists(bind_target)); |
| // Create data folder and bind to bind_target. The bind mount will be cleaned |
| // up in vm_concierge.conf's post-stop script, when the mnt_concierge |
| // namespace is unmounted. |
| SetUpAndroidData(bind_target); |
| } |
| |
| void ArcSetup::OnMountSdcard() { |
| // Set up sdcard asynchronously from arc-sdcard so that waiting on installd |
| // does not add latency to boot-continue (and result in session-manager |
| // related timeouts). |
| SetUpSdcard(); |
| } |
| |
| void ArcSetup::OnUnmountSdcard() { |
| UnmountSdcard(); |
| } |
| |
| void ArcSetup::OnUpdateRestoreconLast() { |
| // On Android, /init writes the security.restorecon_last attribute to /data |
| // (and /cache on N) after it finishes updating labels of the files in the |
| // directories, but on ARC, writing the attribute fails silently because |
| // processes in user namespace are not allowed to write arbitrary entries |
| // under security.* even with CAP_SYS_ADMIN. (b/33084415, b/33402785) |
| // As a workaround, let this command outside the container set the |
| // attribute for ARC. |
| constexpr char kRestoreconLastXattr[] = "security.restorecon_last"; |
| std::vector<base::FilePath> context_files; |
| std::vector<base::FilePath> target_directories = { |
| arc_paths_->android_mutable_source.Append("data")}; |
| |
| // The order of files to read is important. Do not reorder. |
| context_files.push_back( |
| arc_paths_->android_rootfs_directory.Append("plat_file_contexts")); |
| context_files.push_back( |
| arc_paths_->android_rootfs_directory.Append("vendor_file_contexts")); |
| |
| std::string hash; |
| EXIT_IF(!GetSha1HashOfFiles(context_files, &hash)); |
| for (const auto& target : target_directories) { |
| EXIT_IF(!SetXattr(target, kRestoreconLastXattr, hash)); |
| } |
| } |
| |
| std::string ArcSetup::GetSystemBuildPropertyOrDie(const std::string& name) { |
| if (system_properties_.empty()) { |
| const base::FilePath build_prop = |
| arc_paths_->android_generated_properties_directory.Append("build.prop"); |
| EXIT_IF(!GetPropertiesFromFile(build_prop, &system_properties_)); |
| } |
| DCHECK(!system_properties_.empty()); |
| const auto it = system_properties_.find(name); |
| CHECK(system_properties_.end() != it) << "Failed to read property: " << name; |
| CHECK(!it->second.empty()); |
| return it->second; |
| } |
| |
| void ArcSetup::Run() { |
| switch (mode_) { |
| case Mode::SETUP: |
| bootstat_log("mini-android-start"); |
| OnSetup(); |
| bootstat_log("arc-setup-for-mini-android-end"); |
| break; |
| case Mode::STOP: |
| OnStop(); |
| break; |
| case Mode::BOOT_CONTINUE: |
| bootstat_log("android-start"); |
| OnBootContinue(); |
| bootstat_log("arc-setup-end"); |
| break; |
| case Mode::ONETIME_SETUP: |
| OnOnetimeSetup(); |
| break; |
| case Mode::ONETIME_STOP: |
| OnOnetimeStop(); |
| break; |
| case Mode::PRE_CHROOT: |
| OnPreChroot(); |
| break; |
| case Mode::APPLY_PER_BOARD_CONFIG: |
| OnApplyPerBoardConfig(); |
| break; |
| case Mode::CREATE_DATA: |
| OnCreateData(); |
| break; |
| case Mode::REMOVE_DATA: |
| OnRemoveData(); |
| break; |
| case Mode::REMOVE_STALE_DATA: |
| OnRemoveStaleData(); |
| break; |
| case Mode::MOUNT_SDCARD: |
| OnMountSdcard(); |
| break; |
| case Mode::UNMOUNT_SDCARD: |
| OnUnmountSdcard(); |
| break; |
| case Mode::UPDATE_RESTORECON_LAST: |
| OnUpdateRestoreconLast(); |
| break; |
| case Mode::UNKNOWN: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| void ArcSetup::MountOnOnetimeSetupForTesting() { |
| MountOnOnetimeSetup(); |
| } |
| |
| void ArcSetup::UnmountOnOnetimeStopForTesting() { |
| UnmountOnOnetimeStop(); |
| } |
| } // namespace arc |