From 201801ce8eb5e001de9e669e55ee507e18bcae66 Mon Sep 17 00:00:00 2001 From: Jooyung Han Date: Thu, 20 Jul 2023 17:25:47 +0900 Subject: [PATCH] Use /bootstrap-apex for bootstrap APEXes This new directory is bind-mounted to /apex in the bootstrap mount namespace so that apexd-bootstrap mounts bootstrap APEXes there via /apex. The directory is detached from /apex in the default mount namespace but still visible in case bootstrap APEXes are needed. However, there are (mostly, virtual) devices which don't need two mount namespaces. Those devices don't need to make /bootstrap-apex directory at all. Bug: 290148078 Test: atest VendorApexHostTestCases Test: atest MicrodroidTests Change-Id: I541cec71d9970b14971d46e01e4808b23590dbed --- init/builtins.cpp | 50 ++++++++++++++++++++++++++++++++++++++++ init/init.cpp | 6 +++++ init/mount_namespace.cpp | 39 +++++++++++++++++++++++-------- init/mount_namespace.h | 3 +++ init/selinux.cpp | 2 +- rootdir/Android.mk | 2 +- 6 files changed, 91 insertions(+), 11 deletions(-) diff --git a/init/builtins.cpp b/init/builtins.cpp index 2176233aa..e4f0bd040 100644 --- a/init/builtins.cpp +++ b/init/builtins.cpp @@ -1262,6 +1262,51 @@ static Result MountLinkerConfigForDefaultNamespace() { return {}; } + +static Result MountApexRootForDefaultNamespace() { + auto mount_namespace_id = GetCurrentMountNamespace(); + if (!mount_namespace_id.ok()) { + return mount_namespace_id.error(); + } + // There's nothing to do if it's still in the bootstrap mount namespace. + // This happens when we don't need to update APEXes (e.g. Microdroid) + // where bootstrap mount namespace == default mount namespace. + if (mount_namespace_id.value() == NS_BOOTSTRAP) { + return {}; + } + + // Now, we're in the "default" mount namespace and need a fresh /apex for + // the default mount namespace. + // + // At this point, there are two mounts at the same mount point: /apex + // - to tmpfs (private) + // - to /bootstrap-apex (shared) + // + // We need unmount the second mount so that /apex in the default mount + // namespace becomes RW/empty and "private" (we don't want mount events to + // propagate to the bootstrap mount namespace). + // + // Likewise, we don't want the unmount event itself to propagate to the + // bootstrap mount namespace. Otherwise, /apex in the bootstrap mount + // namespace would become empty due to the unmount. + // + // Hence, before unmounting, we make /apex (the second one) "private" first. + // so that the unmouting below doesn't affect to the bootstrap mount namespace. + if (mount(nullptr, "/apex", nullptr, MS_PRIVATE | MS_REC, nullptr) == -1) { + return ErrnoError() << "Failed to remount /apex as private"; + } + + // Now we can unmount /apex (bind-mount to /bootstrap-apex). This only affects + // in the default mount namespace and /apex is now seen as tmpfs mount. + // Note that /apex in the bootstrap mount namespace is still a bind-mount to + // /bootstrap-apex and holds the APEX mounts. + if (umount2("/apex", MNT_DETACH) == -1) { + return ErrnoError() << "Failed to umount /apex"; + } + + return {}; +} + static Result do_update_linker_config(const BuiltinArguments&) { return GenerateLinkerConfiguration(); } @@ -1314,6 +1359,11 @@ static Result do_enter_default_mount_ns(const BuiltinArguments& args) { if (auto result = SwitchToMountNamespaceIfNeeded(NS_DEFAULT); !result.ok()) { return result.error(); } + + if (auto result = MountApexRootForDefaultNamespace(); !result.ok()) { + return result.error(); + } + if (auto result = MountLinkerConfigForDefaultNamespace(); !result.ok()) { return result.error(); } diff --git a/init/init.cpp b/init/init.cpp index da63fdc3b..4bb8eecb9 100644 --- a/init/init.cpp +++ b/init/init.cpp @@ -832,6 +832,12 @@ static void MountExtraFilesystems() { CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV, "mode=0755,uid=0,gid=0")); + if (NeedsTwoMountNamespaces()) { + // /bootstrap-apex is used to mount "bootstrap" APEXes. + CHECKCALL(mount("tmpfs", "/bootstrap-apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV, + "mode=0755,uid=0,gid=0")); + } + // /linkerconfig is used to keep generated linker configuration CHECKCALL(mount("tmpfs", "/linkerconfig", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV, "mode=0755,uid=0,gid=0")); diff --git a/init/mount_namespace.cpp b/init/mount_namespace.cpp index 5b53d5092..e069a5d3e 100644 --- a/init/mount_namespace.cpp +++ b/init/mount_namespace.cpp @@ -66,15 +66,6 @@ static std::string GetMountNamespaceId() { return ret; } -// In case we have two sets of APEXes (non-updatable, updatable), we need two separate mount -// namespaces. -static bool NeedsTwoMountNamespaces() { - if (IsRecoveryMode()) return false; - // In microdroid, there's only one set of APEXes in built-in directories include block devices. - if (IsMicrodroid()) return false; - return true; -} - static android::base::unique_fd bootstrap_ns_fd; static android::base::unique_fd default_ns_fd; @@ -83,6 +74,15 @@ static std::string default_ns_id; } // namespace +// In case we have two sets of APEXes (non-updatable, updatable), we need two separate mount +// namespaces. +bool NeedsTwoMountNamespaces() { + if (IsRecoveryMode()) return false; + // In microdroid, there's only one set of APEXes in built-in directories include block devices. + if (IsMicrodroid()) return false; + return true; +} + bool SetupMountNamespaces() { // Set the propagation type of / as shared so that any mounting event (e.g. // /data) is by default visible to all processes. When private mounting is @@ -96,6 +96,27 @@ bool SetupMountNamespaces() { // the bootstrap namespace get APEXes from the read-only partition. if (!(ChangeMount("/apex", MS_PRIVATE))) return false; + // However, some components (e.g. servicemanager) need to access bootstrap + // APEXes from the default mount namespace. To achieve that, we bind-mount + // /apex with /bootstrap-apex (not private) in the bootstrap mount namespace. + // Bootstrap APEXes are mounted in /apex and also visible in /bootstrap-apex. + // In the default mount namespace, we detach /bootstrap-apex from /apex and + // bootstrap APEXes are still be visible in /bootstrap-apex. + // + // The end result will look like: + // in the bootstrap mount namespace: + // /apex (== /bootstrap-apex) + // {bootstrap APEXes from the read-only partition} + // + // in the default mount namespace: + // /bootstrap-apex + // {bootstrap APEXes from the read-only partition} + // /apex + // {APEXes, can be from /data partition} + if (NeedsTwoMountNamespaces()) { + if (!(BindMount("/bootstrap-apex", "/apex"))) return false; + } + // /linkerconfig is a private mountpoint to give a different linker configuration // based on the mount namespace. Subdirectory will be bind-mounted based on current mount // namespace diff --git a/init/mount_namespace.h b/init/mount_namespace.h index 5e3dab241..43c5476a6 100644 --- a/init/mount_namespace.h +++ b/init/mount_namespace.h @@ -24,9 +24,12 @@ namespace init { enum MountNamespace { NS_BOOTSTRAP, NS_DEFAULT }; bool SetupMountNamespaces(); + base::Result SwitchToMountNamespaceIfNeeded(MountNamespace target_mount_namespace); base::Result GetCurrentMountNamespace(); +bool NeedsTwoMountNamespaces(); + } // namespace init } // namespace android diff --git a/init/selinux.cpp b/init/selinux.cpp index 51093d898..8532c44c1 100644 --- a/init/selinux.cpp +++ b/init/selinux.cpp @@ -766,7 +766,7 @@ void SelinuxRestoreContext() { selinux_android_restorecon("/dev/device-mapper", 0); selinux_android_restorecon("/apex", 0); - + selinux_android_restorecon("/bootstrap-apex", 0); selinux_android_restorecon("/linkerconfig", 0); // adb remount, snapshot-based updates, and DSUs all create files during diff --git a/rootdir/Android.mk b/rootdir/Android.mk index 3362872c0..52187536f 100644 --- a/rootdir/Android.mk +++ b/rootdir/Android.mk @@ -91,7 +91,7 @@ endif # # create some directories (some are mount points) and symlinks LOCAL_POST_INSTALL_CMD := mkdir -p $(addprefix $(TARGET_ROOT_OUT)/, \ - dev proc sys system data data_mirror odm oem acct config storage mnt apex debug_ramdisk \ + dev proc sys system data data_mirror odm oem acct config storage mnt apex bootstrap-apex debug_ramdisk \ linkerconfig second_stage_resources postinstall $(BOARD_ROOT_EXTRA_FOLDERS)); \ ln -sf /system/bin $(TARGET_ROOT_OUT)/bin; \ ln -sf /system/etc $(TARGET_ROOT_OUT)/etc; \