Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 686bff6e authored by Yi-Yo Chiang's avatar Yi-Yo Chiang
Browse files

fs_mgr_overlayfs: Fix submount propagation type after remount

When juggling submounts, the temp dir holding the references to
submounts should be MS_PRIVATE. Otherwise the submount propagation type
would be changed according to section 5d,
https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt

For example, a root mount tree of
  <mountpoint> <shared/private>
  /dev         shared
  /product     shared
  /product/app private

When executing "remount /product", "/product/app" would be moved to a
temp dir under "/dev", which is MS_SHARED. This would also change the
state of the mount to MS_SHARED.
  /dev         shared
  /product     shared
  /dev/temp    shared  (moved from /product/app)

Thus after "/product" overlay is mounted, and "/dev/temp" is moved back,
"/product/app" would be unintentionally changed to MS_SHARED.
  /dev         shared
  /product     private
  /product     shared  (overlayfs override)
  /product/app shared  (moved from /dev/temp)


This change fixes this issue by setting up a standalone mount tree under
/dev to hold the temporary moved submounts.
This way we don't need to modify the state of "/dev" whatsoever, and can
guarantee the propagation type of mounts moved in and out of the temp
dir are not changed.

Bug: 306124139
Test: adb-remount-test
Test: remount a parent mount and then verify the mount flag of submount
Change-Id: I8174257cff6b93dd95303c6ab49b42f90b02df0c
parent dfe80e48
Loading
Loading
Loading
Loading
+47 −28
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@
#include <android-base/file.h>
#include <android-base/macros.h>
#include <android-base/properties.h>
#include <android-base/scopeguard.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <ext4_utils/ext4_utils.h>
@@ -58,6 +59,9 @@ constexpr char kPreferCacheBackingStorageProp[] = "fs_mgr.overlayfs.prefer_cache
constexpr char kCacheMountPoint[] = "/cache";
constexpr char kPhysicalDevice[] = "/dev/block/by-name/";

// Mount tree to temporarily hold references to submounts.
constexpr char kMoveMountTempDir[] = "/dev/remount";

constexpr char kLowerdirOption[] = "lowerdir=";
constexpr char kUpperdirOption[] = "upperdir=";
constexpr char kWorkdirOption[] = "workdir=";
@@ -374,24 +378,21 @@ static std::vector<mount_info> ReadMountinfoFromFile(const std::string& path) {
    return info;
}

static bool fs_mgr_overlayfs_mount(const FstabEntry& entry) {
    const auto mount_point = fs_mgr_mount_point(entry.mount_point);
    const auto options = fs_mgr_get_overlayfs_options(entry);
static bool fs_mgr_overlayfs_mount_one(const FstabEntry& fstab_entry) {
    const auto mount_point = fs_mgr_mount_point(fstab_entry.mount_point);
    const auto options = fs_mgr_get_overlayfs_options(fstab_entry);
    if (options.empty()) return false;

    auto retval = true;

    struct MoveEntry {
        std::string mount_point;
        std::string dir;
        bool shared_flag;
    };

    std::vector<MoveEntry> moved_mounts;
    auto parent_private = false;
    auto parent_made_private = false;
    auto dev_private = false;
    auto dev_made_private = false;

    bool retval = true;
    bool move_dir_shared = true;
    bool parent_shared = true;

    // There could be multiple mount entries with the same mountpoint.
    // Group these entries together with stable_sort, and keep only the last entry of a group.
@@ -411,19 +412,24 @@ static bool fs_mgr_overlayfs_mount(const FstabEntry& entry) {
    // mountinfo is reversed twice, so still is in lexical sorted order.

    for (const auto& entry : mountinfo) {
        if ((entry.mount_point == mount_point) && !entry.shared_flag) {
            parent_private = true;
        if (entry.mount_point == kMoveMountTempDir) {
            move_dir_shared = entry.shared_flag;
        }
        if ((entry.mount_point == "/dev") && !entry.shared_flag) {
            dev_private = true;
        if (entry.mount_point == mount_point ||
            (mount_point == "/system" && entry.mount_point == "/")) {
            parent_shared = entry.shared_flag;
        }
    }

    // Precondition is that kMoveMountTempDir is MS_PRIVATE, otherwise don't try to move any
    // submount in to or out of it.
    if (move_dir_shared) {
        mountinfo.clear();
    }

    // Need to make the original mountpoint MS_PRIVATE, so that the overlayfs can be MS_MOVE.
    // This could happen if its parent mount is remounted later.
    if (!parent_private) {
        parent_made_private = fs_mgr_overlayfs_set_shared_mount(mount_point, false);
    }
    fs_mgr_overlayfs_set_shared_mount(mount_point, false);

    for (const auto& entry : mountinfo) {
        // Find all immediate submounts.
@@ -440,8 +446,8 @@ static bool fs_mgr_overlayfs_mount(const FstabEntry& entry) {
        // mountinfo is in lexical order, so no need to worry about |entry| being a parent mount of
        // entries of |moved_mounts|.

        // use as the bound directory in /dev.
        MoveEntry new_entry{entry.mount_point, "/dev/TemporaryDir-XXXXXX", entry.shared_flag};
        MoveEntry new_entry{entry.mount_point, kMoveMountTempDir + "/TemporaryDir-XXXXXX"s,
                            entry.shared_flag};
        {
            AutoSetFsCreateCon createcon;
            auto new_context = fs_mgr_get_context(entry.mount_point);
@@ -497,10 +503,6 @@ static bool fs_mgr_overlayfs_mount(const FstabEntry& entry) {

    // Move submounts back.
    for (const auto& entry : moved_mounts) {
        if (!dev_private && !dev_made_private) {
            dev_made_private = fs_mgr_overlayfs_set_shared_mount("/dev", false);
        }

        if (!fs_mgr_overlayfs_move_mount(entry.dir, entry.mount_point)) {
            retval = false;
        } else if (entry.shared_flag &&
@@ -509,10 +511,8 @@ static bool fs_mgr_overlayfs_mount(const FstabEntry& entry) {
        }
        rmdir(entry.dir.c_str());
    }
    if (dev_made_private) {
        fs_mgr_overlayfs_set_shared_mount("/dev", true);
    }
    if (parent_made_private) {
    // If the original (overridden) mount was MS_SHARED, then set the overlayfs mount to MS_SHARED.
    if (parent_shared) {
        fs_mgr_overlayfs_set_shared_mount(mount_point, true);
    }

@@ -730,6 +730,25 @@ bool fs_mgr_overlayfs_mount_all(Fstab* fstab) {
    if (!OverlayfsSetupAllowed()) {
        return false;
    }

    // Ensure kMoveMountTempDir is standalone mount tree with 'private' propagation by bind mounting
    // to itself and set to MS_PRIVATE.
    // Otherwise mounts moved in to it would have their propagation type changed unintentionally.
    // Section 5d, https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
    if (!fs_mgr_overlayfs_already_mounted(kMoveMountTempDir, false)) {
        if (mkdir(kMoveMountTempDir, 0755) && errno != EEXIST) {
            PERROR << "mkdir " << kMoveMountTempDir;
        }
        if (mount(kMoveMountTempDir, kMoveMountTempDir, nullptr, MS_BIND, nullptr)) {
            PERROR << "bind mount " << kMoveMountTempDir;
        }
    }
    fs_mgr_overlayfs_set_shared_mount(kMoveMountTempDir, false);
    android::base::ScopeGuard umountDir([]() {
        umount(kMoveMountTempDir);
        rmdir(kMoveMountTempDir);
    });

    auto ret = true;
    auto scratch_can_be_mounted = !fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false);
    for (const auto& entry : fs_mgr_overlayfs_candidate_list(*fstab)) {
@@ -742,7 +761,7 @@ bool fs_mgr_overlayfs_mount_all(Fstab* fstab) {
            scratch_can_be_mounted = false;
            TryMountScratch();
        }
        ret &= fs_mgr_overlayfs_mount(entry);
        ret &= fs_mgr_overlayfs_mount_one(entry);
    }
    return ret;
}