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; \