/* | | nsjail - mount namespace routines using the new mount API (fsopen/fsmount/move_mount) | ----------------------------------------- | | Copyright 2025 Google Inc. All Rights Reserved. | | Licensed under the Apache License, Version 2.0 (the "License"); | you may not use this file except in compliance with the License. | You may obtain a copy of the License at | | http://www.apache.org/licenses/LICENSE-2.0 | | Unless required by applicable law or agreed to in writing, software | distributed under the License is distributed on an "AS IS" BASIS, | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | See the License for the specific language governing permissions and | limitations under the License. | */ #include "mnt_newapi.h" #include // clang-format off /* https://github.com/google/nsjail/issues/250 */ #include // clang-format on #include #include #include "logs.h" #include "util.h" /* * Compile-time feature detection for the new mount API. * Requires kernel headers with fsopen/fsconfig/fsmount/move_mount support. */ #if defined(__NR_fsopen) && defined(__NR_fsconfig) && defined(__NR_fsmount) && \ defined(__NR_move_mount) && defined(__NR_open_tree) && defined(__NR_mount_setattr) && \ defined(FSOPEN_CLOEXEC) && defined(FSMOUNT_CLOEXEC) && defined(MOVE_MOUNT_F_EMPTY_PATH) && \ defined(MOUNT_ATTR_RDONLY) && defined(MOUNT_ATTR_NOSUID) && defined(MOUNT_ATTR_NODEV) && \ defined(MOUNT_ATTR_NOEXEC) && defined(AT_EMPTY_PATH) && defined(AT_RECURSIVE) #define MNT_NEWAPI_SUPPORTED 1 #endif #if !defined(MNT_NEWAPI_SUPPORTED) namespace mnt { namespace newapi { bool isAvailable() { LOG_W("New mount API unavailable: missing compile-time support"); return false; } bool remountPt(mnt::mount_t&) { return false; } std::unique_ptr buildMountTree(nsj_t*, std::vector*) { return nullptr; } } // namespace newapi } // namespace mnt #else /* MNT_NEWAPI_SUPPORTED */ #include #include #include #include #include #include #include #include #include #include "macros.h" #include "mnt.h" namespace mnt { namespace newapi { static bool applyMountFlags( int fd, uintptr_t flags, bool log_error = true, bool recursive = false) { struct mount_attr attr = {}; if (flags & MS_RDONLY) { attr.attr_set |= MOUNT_ATTR_RDONLY; } if (flags & MS_NOSUID) { attr.attr_set |= MOUNT_ATTR_NOSUID; } if (flags & MS_NODEV) { attr.attr_set |= MOUNT_ATTR_NODEV; } if (flags & MS_NOEXEC) { attr.attr_set |= MOUNT_ATTR_NOEXEC; } if (util::syscall(__NR_mount_setattr, (uintptr_t)fd, (uintptr_t)"", (uintptr_t)(AT_EMPTY_PATH | (recursive ? AT_RECURSIVE : 0)), (uintptr_t)&attr, sizeof(attr)) < 0) { if (log_error) { PLOG_W("mount_setattr(fd=%d, flags=0x%" PRIx64 ")", fd, (uint64_t)attr.attr_set); } return false; } return true; } static bool isGenericMountOption(const std::string& opt) { return opt == "ro" || opt == "rw" || opt == "nosuid" || opt == "suid" || opt == "nodev" || opt == "dev" || opt == "noexec" || opt == "exec"; } static void applyGenericMountOption(const std::string& opt, uintptr_t* flags) { if (opt == "ro") { *flags |= MS_RDONLY; } else if (opt == "rw") { *flags &= ~MS_RDONLY; } else if (opt == "nosuid") { *flags |= MS_NOSUID; } else if (opt == "suid") { *flags &= ~MS_NOSUID; } else if (opt == "nodev") { *flags |= MS_NODEV; } else if (opt == "dev") { *flags &= ~MS_NODEV; } else if (opt == "noexec") { *flags |= MS_NOEXEC; } else if (opt == "exec") { *flags &= ~MS_NOEXEC; } } static unsigned long computeLegacyRemountFlags(const mount_t& mpt, const struct statvfs& vfs) { struct { const unsigned long mount_flag; const unsigned long vfs_flag; } static const mountPairs[] = { {MS_NOSUID, ST_NOSUID}, {MS_NODEV, ST_NODEV}, {MS_NOEXEC, ST_NOEXEC}, {MS_SYNCHRONOUS, ST_SYNCHRONOUS}, {MS_MANDLOCK, ST_MANDLOCK}, {MS_NOATIME, ST_NOATIME}, {MS_NODIRATIME, ST_NODIRATIME}, {MS_RELATIME, ST_RELATIME}, {MS_NOSYMFOLLOW, ST_NOSYMFOLLOW}, }; const unsigned long per_mountpoint_flags = MS_LAZYTIME | MS_MANDLOCK | MS_NOATIME | MS_NODEV | MS_NODIRATIME | MS_NOEXEC | MS_NOSUID | MS_RELATIME | MS_RDONLY | MS_SYNCHRONOUS | MS_NOSYMFOLLOW; unsigned long flags = MS_REMOUNT | MS_BIND | (mpt.flags & per_mountpoint_flags); for (const auto& i : mountPairs) { if (vfs.f_flag & i.vfs_flag) { flags |= i.mount_flag; } } return flags; } static bool remountWithLegacyMount(const mount_t& mpt) { struct statvfs vfs; if (TEMP_FAILURE_RETRY(statvfs(mpt.dst.c_str(), &vfs)) == -1) { PLOG_W("statvfs('%s')", mpt.dst.c_str()); return false; } unsigned long flags = computeLegacyRemountFlags(mpt, vfs); LOG_D("Falling back to legacy remount for '%s' with flags: %s", mpt.dst.c_str(), mnt::flagsToStr(flags).c_str()); if (mount(mpt.dst.c_str(), mpt.dst.c_str(), nullptr, flags, nullptr) == -1) { PLOG_W("mount('%s', flags=%s)", mpt.dst.c_str(), mnt::flagsToStr(flags).c_str()); return false; } return true; } static bool openMountForRemount(mount_t* mpt, int parent_fd, const char* basename) { mpt->fd = util::syscall(__NR_open_tree, (uintptr_t)parent_fd, (uintptr_t)basename, (uintptr_t)OPEN_TREE_CLOEXEC); if (mpt->fd < 0) { PLOG_W("open_tree(parent_fd, '%s')", basename); return false; } mpt->mounted = true; return true; } static bool createDirAt(int dir_fd, const char* path, mode_t mode) { path = util::stripLeadingSlashes(path); if (!path[0]) { return true; } if (!util::isSafeContainmentPath(path)) { LOG_E("Mount destination escapes containment via '.'/'..'/NUL: '%s'", path); return false; } /* * Walk one path component at a time with O_NOFOLLOW so a previously * planted symlink mount (-s) cannot redirect mkdirat/openat outside the * staging root. Multi-component mkdirat() follows intermediate symlinks. */ int prev_fd = dir_fd; bool close_prev = false; for (const auto& component : util::strSplit(path, '/')) { if (component.empty()) { continue; } if (component == "." || component == "..") { LOG_E("Mount destination escapes containment via '%s' in '%s'", component.c_str(), path); if (close_prev) { close(prev_fd); } return false; } if (mkdirat(prev_fd, component.c_str(), mode) == -1 && errno != EEXIST) { if (errno != EROFS || !util::existsAsDirAt(prev_fd, component.c_str())) { PLOG_W("mkdirat(%d, '%s')", prev_fd, component.c_str()); if (close_prev) { close(prev_fd); } return false; } } int next_fd = TEMP_FAILURE_RETRY( openat(prev_fd, component.c_str(), O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW)); if (next_fd == -1) { PLOG_W("openat(%d, '%s', O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW)", prev_fd, component.c_str()); if (close_prev) { close(prev_fd); } return false; } if (close_prev) { close(prev_fd); } prev_fd = next_fd; close_prev = true; } if (close_prev) { close(prev_fd); } return true; } /* Open the parent directory of rel_dst under root_fd without following * symlinks; return the final basename for leaf operations. */ static bool openParentAtNoFollow( int root_fd, const char* rel_dst, int* parent_fd_out, std::string* basename_out) { rel_dst = util::stripLeadingSlashes(rel_dst); if (!rel_dst[0] || strcmp(rel_dst, ".") == 0) { *parent_fd_out = TEMP_FAILURE_RETRY(fcntl(root_fd, F_DUPFD_CLOEXEC, 0)); if (*parent_fd_out < 0) { PLOG_W("fcntl(root_fd, F_DUPFD_CLOEXEC)"); return false; } *basename_out = "."; return true; } const char* last_slash = strrchr(rel_dst, '/'); if (!last_slash) { *parent_fd_out = TEMP_FAILURE_RETRY(fcntl(root_fd, F_DUPFD_CLOEXEC, 0)); if (*parent_fd_out < 0) { PLOG_W("fcntl(root_fd, F_DUPFD_CLOEXEC)"); return false; } *basename_out = rel_dst; return true; } std::string parent(rel_dst, last_slash - rel_dst); *basename_out = last_slash + 1; if (basename_out->empty() || *basename_out == "." || *basename_out == "..") { LOG_E("Unsafe mount destination basename in '%s'", rel_dst); return false; } if (!createDirAt(root_fd, parent.c_str(), 0755)) { return false; } /* Re-walk parent with O_NOFOLLOW to obtain the directory fd. */ int prev_fd = root_fd; bool close_prev = false; for (const auto& component : util::strSplit(parent, '/')) { if (component.empty()) { continue; } int next_fd = TEMP_FAILURE_RETRY( openat(prev_fd, component.c_str(), O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW)); if (next_fd == -1) { PLOG_W("openat(parent walk '%s')", component.c_str()); if (close_prev) { close(prev_fd); } return false; } if (close_prev) { close(prev_fd); } prev_fd = next_fd; close_prev = true; } *parent_fd_out = prev_fd; return true; } static int createDetachedTmpfs(size_t size) { int fs_fd = util::syscall(__NR_fsopen, (uintptr_t)"tmpfs", (uintptr_t)FSOPEN_CLOEXEC); if (fs_fd < 0) { PLOG_W("fsopen('tmpfs')"); return -1; } defer { close(fs_fd); }; const std::string size_str = std::to_string(size); if (util::syscall(__NR_fsconfig, (uintptr_t)fs_fd, (uintptr_t)FSCONFIG_SET_STRING, (uintptr_t)"size", (uintptr_t)size_str.c_str(), (uintptr_t)0) < 0) { PLOG_W("fsconfig(size=%s)", size_str.c_str()); return -1; } if (util::syscall(__NR_fsconfig, (uintptr_t)fs_fd, (uintptr_t)FSCONFIG_CMD_CREATE, (uintptr_t)nullptr, (uintptr_t)nullptr, (uintptr_t)0) < 0) { PLOG_W("fsconfig(CMD_CREATE)"); return -1; } int mnt_fd = util::syscall(__NR_fsmount, (uintptr_t)fs_fd, (uintptr_t)FSMOUNT_CLOEXEC, (uintptr_t)0); if (mnt_fd < 0) { PLOG_W("fsmount('tmpfs')"); } return mnt_fd; } static int createFilesystemMount(const mount_t& mpt) { int fs_fd = util::syscall( __NR_fsopen, (uintptr_t)mpt.mpt->fstype().c_str(), (uintptr_t)FSOPEN_CLOEXEC); if (fs_fd < 0) { PLOG_W("fsopen('%s')", mpt.mpt->fstype().c_str()); return -1; } defer { close(fs_fd); }; if (!mpt.src.empty() && mpt.src != "none") { if (util::syscall(__NR_fsconfig, (uintptr_t)fs_fd, (uintptr_t)FSCONFIG_SET_STRING, (uintptr_t)"source", (uintptr_t)mpt.src.c_str(), (uintptr_t)0) < 0) { PLOG_W("fsconfig(source='%s')", mpt.src.c_str()); return -1; } } if (mpt.mpt->has_options()) { for (const auto& opt : util::strSplit(mpt.mpt->options(), ',')) { if (opt.empty() || isGenericMountOption(opt)) { continue; } auto eq = opt.find('='); if (eq != std::string::npos) { std::string key = opt.substr(0, eq); std::string val = opt.substr(eq + 1); if (util::syscall(__NR_fsconfig, (uintptr_t)fs_fd, (uintptr_t)FSCONFIG_SET_STRING, (uintptr_t)key.c_str(), (uintptr_t)val.c_str(), (uintptr_t)0) < 0) { PLOG_W("fsconfig(key='%s', value='%s')", key.c_str(), val.c_str()); return -1; } } else { if (util::syscall(__NR_fsconfig, (uintptr_t)fs_fd, (uintptr_t)FSCONFIG_SET_FLAG, (uintptr_t)opt.c_str(), (uintptr_t)nullptr, (uintptr_t)0) < 0) { PLOG_W("fsconfig(flag='%s')", opt.c_str()); return -1; } } } } if (util::syscall(__NR_fsconfig, (uintptr_t)fs_fd, (uintptr_t)FSCONFIG_CMD_CREATE, (uintptr_t)nullptr, (uintptr_t)nullptr, (uintptr_t)0) < 0) { PLOG_W("fsconfig(CMD_CREATE)"); return -1; } int mnt_fd = util::syscall(__NR_fsmount, (uintptr_t)fs_fd, (uintptr_t)FSMOUNT_CLOEXEC, (uintptr_t)0); if (mnt_fd < 0) { PLOG_W("fsmount('%s')", mpt.mpt->fstype().c_str()); } return mnt_fd; } static bool mountSymlinkAt(mount_t* mpt, int parent_fd, const char* basename) { LOG_D("Creating symlink: %s -> %s (fd-relative)", mpt->src.c_str(), basename); if (symlinkat(mpt->src.c_str(), parent_fd, basename) == -1) { if (mpt->mpt->mandatory()) { PLOG_E("symlinkat('%s' -> '%s')", mpt->src.c_str(), basename); return false; } PLOG_W( "symlinkat('%s' -> '%s') failed (non-mandatory)", mpt->src.c_str(), basename); } return true; } static bool mountDynamicContentAt(mount_t* mpt, int root_fd, int parent_fd, const char* basename) { static uint64_t counter = 0; std::string src_rel = ".dyn." + std::to_string(++counter); int src_fd = openat(root_fd, src_rel.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0644); if (src_fd < 0) { PLOG_W("openat(root_fd, '%s', O_CREAT)", src_rel.c_str()); return false; } const auto& content = mpt->mpt->src_content(); bool ok = util::writeToFd(src_fd, content.data(), content.length()); close(src_fd); if (!ok) { LOG_W("Failed to write %zu bytes for dynamic content '%s'", content.length(), basename); unlinkat(root_fd, src_rel.c_str(), 0); return false; } int mnt_fd = syscall(__NR_open_tree, root_fd, src_rel.c_str(), OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); if (mnt_fd < 0) { PLOG_W("open_tree('%s')", src_rel.c_str()); unlinkat(root_fd, src_rel.c_str(), 0); return false; } if (!applyMountFlags(mnt_fd, mpt->flags & ~MS_RDONLY)) { LOG_W("Failed to apply mount flags to '%s'", basename); } if (util::syscall(__NR_move_mount, (uintptr_t)mnt_fd, (uintptr_t)"", (uintptr_t)parent_fd, (uintptr_t)basename, (uintptr_t)MOVE_MOUNT_F_EMPTY_PATH) < 0) { PLOG_W("move_mount('%s' -> '%s')", src_rel.c_str(), basename); close(mnt_fd); unlinkat(root_fd, src_rel.c_str(), 0); return false; } close(mnt_fd); if (unlinkat(root_fd, src_rel.c_str(), 0) == -1) { PLOG_W("unlinkat(root_fd, '%s')", src_rel.c_str()); } return openMountForRemount(mpt, parent_fd, basename); } static bool doBindMountAt(mount_t* mpt, int parent_fd, const char* basename) { unsigned int flags = OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC; if (mpt->flags & MS_REC) { flags |= AT_RECURSIVE; } LOG_D("open_tree('%s', flags=0x%x)", mpt->src.c_str(), flags); int mnt_fd = syscall(__NR_open_tree, AT_FDCWD, mpt->src.c_str(), flags); if (mnt_fd < 0) { PLOG_W("open_tree('%s')", mpt->src.c_str()); return false; } /* * Apply non-RO flags now; RO applied later via remount. * When the bind is recursive (MS_REC), open_tree above cloned the whole * subtree (AT_RECURSIVE), so the nosuid/nodev/noexec attributes must be * applied recursively as well -- otherwise nested submounts of the source * keep their original suid/dev/exec-permitting attributes inside the jail. * This mirrors the recursive read-only pass done later on the root. */ if (!applyMountFlags(mnt_fd, mpt->flags & ~MS_RDONLY, true, (mpt->flags & MS_REC) != 0)) { LOG_W("Failed to apply mount flags to '%s'", basename); } if (util::syscall(__NR_move_mount, (uintptr_t)mnt_fd, (uintptr_t)"", (uintptr_t)parent_fd, (uintptr_t)basename, (uintptr_t)MOVE_MOUNT_F_EMPTY_PATH) < 0) { PLOG_W("move_mount('%s' -> '%s')", mpt->src.c_str(), basename); close(mnt_fd); return false; } close(mnt_fd); return openMountForRemount(mpt, parent_fd, basename); } static bool mountSinglePointAt(mount_t* mpt, int root_fd) { LOG_D("Mounting (new API): %s", mnt::describeMountPt(*mpt->mpt).c_str()); const char* rel_dst = util::stripLeadingSlashes(mpt->dst.c_str()); /* Empty / only-slashes dst means the jail root (represented as "."). */ if (!rel_dst[0]) { rel_dst = "."; } else if (!util::isSafeContainmentPath(rel_dst)) { LOG_E("Mount destination escapes containment via '.'/'..'/NUL: %s", QC(mpt->dst)); return false; } int parent_fd = -1; std::string basename; if (!openParentAtNoFollow(root_fd, rel_dst, &parent_fd, &basename)) { LOG_W("Failed to resolve mount destination parents for '%s'", rel_dst); return false; } defer { close(parent_fd); }; if (mpt->mpt->is_symlink()) { return mountSymlinkAt(mpt, parent_fd, basename.c_str()); } if (mpt->is_dir) { if (basename != "." && mkdirat(parent_fd, basename.c_str(), 0711) == -1 && errno != EEXIST) { if (errno != EROFS || !util::existsAsDirAt(parent_fd, basename.c_str())) { PLOG_W("mkdirat(parent_fd, '%s')", basename.c_str()); } } } else { int fd = openat( parent_fd, basename.c_str(), O_CREAT | O_RDONLY | O_CLOEXEC | O_NOFOLLOW, 0644); if (fd >= 0) { close(fd); } else if (errno != EROFS || !util::existsAsRegAt(parent_fd, basename.c_str())) { PLOG_W("openat(parent_fd, '%s', O_CREAT|O_NOFOLLOW)", basename.c_str()); } } if (!mpt->mpt->src_content().empty()) { return mountDynamicContentAt(mpt, root_fd, parent_fd, basename.c_str()); } if (mpt->flags & MS_BIND) { return doBindMountAt(mpt, parent_fd, basename.c_str()); } int mnt_fd = createFilesystemMount(*mpt); if (mnt_fd < 0) { return false; } if (!applyMountFlags(mnt_fd, mpt->flags & ~MS_RDONLY)) { LOG_W("Failed to apply mount flags to '%s'", basename.c_str()); } if (util::syscall(__NR_move_mount, (uintptr_t)mnt_fd, (uintptr_t)"", (uintptr_t)parent_fd, (uintptr_t)basename.c_str(), (uintptr_t)MOVE_MOUNT_F_EMPTY_PATH) < 0) { PLOG_W("move_mount() for '%s'", basename.c_str()); close(mnt_fd); return false; } close(mnt_fd); return openMountForRemount(mpt, parent_fd, basename.c_str()); } static mount_t prepareMountPoint(const nsjail::MountPt& proto) { mount_t mpt = { .mpt = &proto, .src = "", .dst = "", .flags = 0, .is_dir = true, .mounted = false, .fd = -1, }; if (!proto.prefix_src_env().empty()) { if (const char* env = getenv(proto.prefix_src_env().c_str())) { mpt.src = env; } else { LOG_W("Environment variable not set: %s", QC(proto.prefix_src_env())); return mpt; } } mpt.src += proto.src(); if (!proto.prefix_dst_env().empty()) { if (const char* env = getenv(proto.prefix_dst_env().c_str())) { mpt.dst = env; } else { LOG_W("Environment variable not set: %s", QC(proto.prefix_dst_env())); return mpt; } } mpt.dst += proto.dst(); if (!util::isSafeContainmentPath(mpt.dst)) { LOG_E("Mount destination escapes containment via '.'/'..'/NUL: %s", QC(mpt.dst)); /* Keep the unsafe dst so later mount steps fail closed rather than * treating a cleared path as the jail root. */ return mpt; } mpt.flags = proto.rw() ? 0 : (uintptr_t)MS_RDONLY; if (proto.is_bind()) { mpt.flags |= MS_BIND | MS_REC | MS_PRIVATE; } if (proto.nosuid()) { mpt.flags |= MS_NOSUID; } if (proto.nodev()) { mpt.flags |= MS_NODEV; } if (proto.noexec()) { mpt.flags |= MS_NOEXEC; } if (proto.has_options()) { for (const auto& opt : util::strSplit(proto.options(), ',')) { applyGenericMountOption(opt, &mpt.flags); } } if (proto.has_is_dir()) { mpt.is_dir = proto.is_dir(); } else if (!proto.src_content().empty()) { mpt.is_dir = false; } else if (mpt.src.empty()) { mpt.is_dir = true; } else if (mpt.flags & MS_BIND) { struct stat st; mpt.is_dir = (stat(mpt.src.c_str(), &st) == 0 && S_ISDIR(st.st_mode)); } else { mpt.is_dir = true; } return mpt; } bool isAvailable() { if (util::kernelVersionAtLeast(6, 3, 0)) { LOG_D("New mount API available (kernel >= 6.3)"); return true; } LOG_W("New mount API unavailable (kernel < 6.3)"); return false; } bool remountPt(mnt::mount_t& mpt) { if (!mpt.mounted || mpt.mpt->is_symlink() || mpt.fd < 0) { return true; } close(mpt.fd); mpt.fd = -1; if (!remountWithLegacyMount(mpt)) { LOG_W("Failed to apply final flags to '%s'", mpt.dst.c_str()); return false; } return true; } std::unique_ptr buildMountTree(nsj_t* nsj, std::vector* mounted_mpts) { if (chdir("/") == -1) { PLOG_E("chdir('/')"); return nullptr; } /* Make root mount private recursively */ if (mount("/", "/", nullptr, MS_REC | MS_PRIVATE, nullptr) == -1) { PLOG_E("mount('/', MS_REC|MS_PRIVATE)"); return nullptr; } int root_mfd = createDetachedTmpfs(16 * 1024 * 1024); if (root_mfd < 0) { LOG_E("Failed to create root tmpfs"); return nullptr; } LOG_D("Created detached root tmpfs (fd=%d)", root_mfd); if (!applyMountFlags(root_mfd, 0)) { LOG_W("mount_setattr(root_mfd, 0) failed"); } auto destdir = mnt::findWorkDir(nsj, "root"); if (!destdir) { close(root_mfd); return nullptr; } /* * Attach the new root to the filesystem *before* populating it. * Attempting to populate a detached mount tree via move_mount() can fail with EINVAL * on some kernels (e.g. 6.12) if the target is not yet attached. */ if (util::syscall(__NR_move_mount, (uintptr_t)root_mfd, (uintptr_t)"", (uintptr_t)AT_FDCWD, (uintptr_t)destdir->c_str(), (uintptr_t)MOVE_MOUNT_F_EMPTY_PATH) < 0) { PLOG_E("move_mount(root_mfd -> '%s')", destdir->c_str()); close(root_mfd); return nullptr; } close(root_mfd); int root_fd = openat(AT_FDCWD, destdir->c_str(), O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY); if (root_fd < 0) { PLOG_E("openat('%s')", destdir->c_str()); return nullptr; } defer { close(root_fd); }; /* Build entire mount tree using fd-relative operations */ for (const auto& proto : nsj->njc.mount()) { mount_t mpt = prepareMountPoint(proto); if (!mountSinglePointAt(&mpt, root_fd)) { if (mpt.mpt->mandatory()) { LOG_E("Failed to mount mandatory point: %s", QC(mpt.dst)); return nullptr; } } mounted_mpts->push_back(mpt); } if (!nsj->is_root_rw) { struct mount_attr ro_attr = {}; ro_attr.attr_set = MOUNT_ATTR_RDONLY; if (util::syscall(__NR_mount_setattr, (uintptr_t)root_fd, (uintptr_t)"", (uintptr_t)(AT_EMPTY_PATH | AT_RECURSIVE), (uintptr_t)&ro_attr, sizeof(ro_attr)) < 0) { PLOG_E("mount_setattr(root_fd, MOUNT_ATTR_RDONLY)"); return nullptr; } } return destdir; } } // namespace newapi } // namespace mnt #endif /* MNT_NEWAPI_SUPPORTED */