| // Copyright 2023 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <dirent.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/files/file_path.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/stringprintf.h> |
| #include <brillo/flag_helper.h> |
| |
| #include "arc/mount-passthrough/mount-passthrough-util.h" |
| |
| namespace arc { |
| |
| void ParseCommandLine(int argc, |
| const char* const* argv, |
| CommandLineFlags* flags) { |
| DEFINE_string(source, "", "Source path of FUSE mount (required)"); |
| DEFINE_string(dest, "", "Target path of FUSE mount (required)"); |
| DEFINE_string(fuse_umask, "", |
| "Umask to set filesystem permissions in FUSE (required)"); |
| DEFINE_int32(fuse_uid, -1, "UID set as file owner in FUSE (required)"); |
| DEFINE_int32(fuse_gid, -1, "GID set as file group in FUSE (required)"); |
| DEFINE_string(android_app_access_type, "full", "Access type of Android apps"); |
| DEFINE_bool(use_default_selinux_context, false, |
| "Use default \"fuse\" SELinux context"); |
| DEFINE_int32( |
| media_provider_uid, -1, |
| "UID of Android's MediaProvider " |
| "(required in Android R+ for setting non-default SELinux context)"); |
| DEFINE_bool(enable_casefold_lookup, false, "Enable casefold lookup"); |
| DEFINE_bool(enter_concierge_namespace, false, "Enter concierge namespace"); |
| // This is larger than the default value 1024 because this process handles |
| // many open files. See b/30236190 for more context. |
| DEFINE_int32(max_number_of_open_fds, 8192, "Max number of open fds"); |
| |
| brillo::FlagHelper::Init(argc, argv, "mount-passthrough-jailed"); |
| flags->source = FLAGS_source; |
| flags->dest = FLAGS_dest; |
| flags->fuse_umask = FLAGS_fuse_umask; |
| flags->fuse_uid = FLAGS_fuse_uid; |
| flags->fuse_gid = FLAGS_fuse_gid; |
| flags->android_app_access_type = FLAGS_android_app_access_type; |
| flags->use_default_selinux_context = FLAGS_use_default_selinux_context; |
| flags->media_provider_uid = FLAGS_media_provider_uid; |
| flags->enable_casefold_lookup = FLAGS_enable_casefold_lookup; |
| flags->enter_concierge_namespace = FLAGS_enter_concierge_namespace; |
| flags->max_number_of_open_fds = FLAGS_max_number_of_open_fds; |
| } |
| |
| std::vector<std::string> CreateMinijailCommandLineArgs( |
| const CommandLineFlags& flags) { |
| std::vector<std::string> args; |
| args.push_back("/sbin/minijail0"); |
| |
| if (flags.enter_concierge_namespace) { |
| // Enter the concierge namespace. |
| args.push_back("-V"); |
| args.push_back("/run/namespaces/mnt_concierge"); |
| } else { |
| // Use minimalistic-mountns profile. |
| args.push_back("--profile=minimalistic-mountns"); |
| args.push_back("--no-fs-restrictions"); |
| } |
| |
| // Enter a new cgroup namespace. |
| args.push_back("-N"); |
| |
| // Enter a new UTS namespace. |
| args.push_back("--uts"); |
| |
| // Enter a new VFS namespace and remount /proc read-only. |
| args.push_back("-v"); |
| args.push_back("-r"); |
| |
| // Enter a new network namespace. |
| args.push_back("-e"); |
| |
| // Enter a new IPC namespace. |
| args.push_back("-l"); |
| |
| // Grant CAP_SYS_ADMIN needed to mount FUSE filesystem. |
| args.push_back("-c"); |
| args.push_back("cap_sys_admin+eip"); |
| |
| // Set uid and gid of the daemon as chronos. |
| args.push_back("-u"); |
| args.push_back("chronos"); |
| args.push_back("-g"); |
| args.push_back("chronos"); |
| |
| // Inherit supplementary groups. |
| args.push_back("-G"); |
| |
| // Allow sharing mounts between CrOS and Android. WARNING: BE CAREFUL |
| // not to unexpectedly expose shared mounts in following bind mounts! |
| // Always remount them with MS_REC|MS_PRIVATE unless you want to share |
| // those mounts explicitly. |
| args.push_back("-K"); |
| |
| // Specify the maximum number of file descriptors the process can open. |
| args.push_back("-R"); |
| args.push_back(base::StringPrintf("RLIMIT_NOFILE,%d,%d", |
| flags.max_number_of_open_fds, |
| flags.max_number_of_open_fds)); |
| |
| std::string source_in_minijail = flags.source; |
| std::string dest_in_minijail = flags.dest; |
| |
| if (!flags.enter_concierge_namespace) { |
| // Set up the source and destination under /mnt inside the new |
| // namespace. |
| source_in_minijail = "/mnt/source"; |
| dest_in_minijail = "/mnt/dest"; |
| |
| // Mount tmpfs on /mnt. |
| args.push_back("-k"); |
| args.push_back("tmpfs,/mnt,tmpfs,MS_NOSUID|MS_NODEV|MS_NOEXEC"); |
| |
| // Bind /dev/fuse to mount FUSE file systems. |
| args.push_back("-b"); |
| args.push_back("/dev/fuse"); |
| |
| // Mark PRIVATE recursively under (pivot) root, in order not to |
| // expose shared mount points accidentally. |
| // 0x44000 = private,rec |
| args.push_back("-k"); |
| args.push_back("none,/,none,0x44000"); |
| |
| // Mount source/dest directories. Note that those directories might |
| // be shared mountpoints and we allow them. |
| // 0x5000 = bind,rec |
| args.push_back("-k"); |
| args.push_back(base::StringPrintf("%s,%s,none,0x5000", flags.source.c_str(), |
| source_in_minijail.c_str())); |
| |
| // 0x84000 = slave,rec |
| args.push_back("-k"); |
| args.push_back(base::StringPrintf("%s,%s,none,0x84000", |
| flags.source.c_str(), |
| source_in_minijail.c_str())); |
| |
| // 0x102e = bind,remount,noexec,nodev,nosuid |
| args.push_back("-k"); |
| args.push_back(base::StringPrintf("%s,%s,none,0x102e", flags.source.c_str(), |
| source_in_minijail.c_str())); |
| |
| // 0x1000 = bind |
| args.push_back("-k"); |
| args.push_back(base::StringPrintf("%s,%s,none,0x1000", flags.dest.c_str(), |
| dest_in_minijail.c_str())); |
| // 0x102e = bind,remount,noexec,nodev,nosuid |
| args.push_back("-k"); |
| args.push_back(base::StringPrintf("%s,%s,none,0x102e", flags.dest.c_str(), |
| dest_in_minijail.c_str())); |
| } |
| |
| // Finally, specify command line arguments. |
| args.push_back("--"); |
| args.push_back("/usr/bin/mount-passthrough"); |
| |
| args.push_back("--source=" + source_in_minijail); |
| args.push_back("--dest=" + dest_in_minijail); |
| args.push_back("--fuse_umask=" + flags.fuse_umask); |
| args.push_back("--fuse_uid=" + base::NumberToString(flags.fuse_uid)); |
| args.push_back("--fuse_gid=" + base::NumberToString(flags.fuse_gid)); |
| args.push_back("--android_app_access_type=" + flags.android_app_access_type); |
| |
| if (flags.use_default_selinux_context) { |
| args.push_back("--use_default_selinux_context"); |
| } |
| |
| if (flags.media_provider_uid >= 0) { |
| args.push_back(base::StringPrintf("--media_provider_uid=%d", |
| flags.media_provider_uid)); |
| } |
| |
| if (flags.enable_casefold_lookup) { |
| args.push_back("--enable_casefold_lookup"); |
| } |
| |
| return args; |
| } |
| |
| base::FilePath CasefoldLookup(const base::FilePath& root, |
| const base::FilePath& path) { |
| // For simplicity, do not deal with paths referencing their parents. |
| if (path.ReferencesParent()) { |
| return path; |
| } |
| |
| // Just return the original path as-is if it is not a descendant of the root. |
| // Note that `IsParent()` returns true if and only if the path is an ancestor |
| // (not necessarily the direct parent) of the specified child. |
| if (!root.IsParent(path)) { |
| return path; |
| } |
| |
| // Look for the nearest existing ancestor under the root. |
| base::FilePath lookup_path = path; |
| std::vector<std::string> components; |
| while (lookup_path != root) { |
| if (access(lookup_path.value().c_str(), F_OK) == 0) { |
| break; |
| } |
| |
| base::FilePath parent = lookup_path.DirName(); |
| // A cheap check to ensure that the loop is terminated. This should not be |
| // needed as long as everything is functioning. |
| if (parent.value().length() >= lookup_path.value().length()) { |
| LOG(ERROR) << "Unexpectedly long path length " << parent.value().length() |
| << " for the parent of a path of length " |
| << lookup_path.value().length(); |
| break; |
| } |
| |
| components.push_back(lookup_path.BaseName().value()); |
| lookup_path = std::move(parent); |
| } |
| |
| // Repeat the following: |
| // Open `lookup_path` assuming that it is a directory, look for an entry that |
| // matches the original path component in the case insensitive way, and append |
| // it to `lookup_path` if there is such an entry. |
| for (auto it = components.rbegin(); it != components.rend(); ++it) { |
| DIR* dirp = opendir(lookup_path.value().c_str()); |
| if (dirp == nullptr) { |
| // `lookup_path` cannot be opened. Append the remaining path components |
| // and return, since there is no point in continuing the lookup. |
| do { |
| lookup_path = lookup_path.Append(*it); |
| ++it; |
| } while (it != components.rend()); |
| return lookup_path; |
| } |
| |
| // Iterate through the entries. Note that `readdir(3)` does not guarantee |
| // the order of ieteration. |
| while (true) { |
| struct dirent* entry = readdir(dirp); |
| if (entry == nullptr) { |
| // There is no matching entry. Append the remaining path components and |
| // return, since there is no point in continuing the lookup. |
| closedir(dirp); |
| do { |
| lookup_path = lookup_path.Append(*it); |
| ++it; |
| } while (it != components.rend()); |
| return lookup_path; |
| } |
| |
| const std::string entry_name = entry->d_name; |
| if (base::FilePath::CompareEqualIgnoreCase(entry_name, *it)) { |
| // A matching entry is found. Append it to `lookup_path` and continue |
| // the lookup. |
| lookup_path = lookup_path.Append(entry_name); |
| closedir(dirp); |
| break; |
| } |
| } |
| } |
| |
| return lookup_path; |
| } |
| |
| } // namespace arc |