Merge "Use /bootstrap-apex for bootstrap APEXes" into main am: 58ba0b44c2

Original change: https://android-review.googlesource.com/c/platform/system/core/+/2666915

Change-Id: Ifccb5b15c8fc1b7e9a2c9140127fc884090d35c4
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/init/builtins.cpp b/init/builtins.cpp
index fa5e36d..cf784ac 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -1262,6 +1262,51 @@
 
     return {};
 }
+
+static Result<void> 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<void> do_update_linker_config(const BuiltinArguments&) {
     return GenerateLinkerConfiguration();
 }
@@ -1315,6 +1360,11 @@
     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 da63fdc..4bb8eec 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -832,6 +832,12 @@
     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 5b53d50..e069a5d 100644
--- a/init/mount_namespace.cpp
+++ b/init/mount_namespace.cpp
@@ -66,15 +66,6 @@
     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 @@
 
 }  // 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 @@
     // 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 5e3dab2..43c5476 100644
--- a/init/mount_namespace.h
+++ b/init/mount_namespace.h
@@ -24,9 +24,12 @@
 enum MountNamespace { NS_BOOTSTRAP, NS_DEFAULT };
 
 bool SetupMountNamespaces();
+
 base::Result<void> SwitchToMountNamespaceIfNeeded(MountNamespace target_mount_namespace);
 
 base::Result<MountNamespace> GetCurrentMountNamespace();
 
+bool NeedsTwoMountNamespaces();
+
 }  // namespace init
 }  // namespace android
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 51093d8..8532c44 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -766,7 +766,7 @@
     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 3362872..5218753 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -91,7 +91,7 @@
 #
 # 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; \