From 85eaec2311c7f145900f7d9bcb0f2265cd4de5a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Caol=C3=A1n=20McNamara?= Date: Mon, 1 Jul 2024 20:06:25 +0100 Subject: [PATCH] add 'mount_namespaces' option to use linux mount namespaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit if experimental_features and mount_namespaces is enabled and it is possible to "unshare" then instead of coolforkit exec coolforkitns which doesn't have any capabilities set and inprocess mount inside a namespace instead of calling coolmount Signed-off-by: Caolán McNamara Change-Id: I48bef12b9156f41c78221e750a30aacee8a737a9 --- Makefile.am | 13 +- common/CoolMount.cpp | 341 +++++++++++++++++++++++++++++++++++++++++++ common/JailUtil.cpp | 109 ++++++++++++++ common/JailUtil.hpp | 14 ++ coolwsd.spec.in | 1 + coolwsd.xml.in | 1 + kit/ForKit.cpp | 12 +- tools/mount.cpp | 319 +--------------------------------------- wsd/COOLWSD.cpp | 22 ++- wsd/COOLWSD.hpp | 1 + 10 files changed, 509 insertions(+), 324 deletions(-) create mode 100644 common/CoolMount.cpp diff --git a/Makefile.am b/Makefile.am index 95c99e8f8..86c8ac278 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,6 +20,7 @@ export ENABLE_DEBUG bin_PROGRAMS = \ coolforkit \ + coolforkitns \ coolmount \ coolstress \ coolconvert coolconfig @@ -120,6 +121,7 @@ shared_sources = common/FileUtil.cpp \ common/Authorization.cpp \ common/CommandControl.cpp \ common/Simd.cpp \ + common/CoolMount.cpp \ kit/KitQueue.cpp \ net/DelaySocket.cpp \ net/HttpRequest.cpp \ @@ -209,6 +211,12 @@ coolforkit_SOURCES = $(coolforkit_sources) \ coolforkit_LDADD = libsimd.a +coolforkitns_SOURCES = $(coolforkit_sources) \ + $(shared_sources) \ + kit/forkit-main.cpp + +coolforkitns_LDADD = libsimd.a + if ENABLE_DEBUG coolwsd_inproc_SOURCES = $(coolwsd_sources) \ $(shared_sources) \ @@ -273,7 +281,8 @@ clientnb_SOURCES = net/clientnb.cpp \ common/Util.cpp \ common/Util-desktop.cpp -coolmount_SOURCES = tools/mount.cpp +coolmount_SOURCES = tools/mount.cpp \ + common/CoolMount.cpp coolmap_SOURCES = tools/map.cpp @@ -476,7 +485,7 @@ endif if ENABLE_LIBFUZZER CLEANUP_DEPS= else -CLEANUP_DEPS=coolwsd coolmount coolforkit +CLEANUP_DEPS=coolwsd coolmount coolforkit coolforkitns endif # Build coolwsd and coolmount first, so we can cleanup before updating diff --git a/common/CoolMount.cpp b/common/CoolMount.cpp new file mode 100644 index 000000000..625230939 --- /dev/null +++ b/common/CoolMount.cpp @@ -0,0 +1,341 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * Copyright the Collabora Online contributors. + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* + * This is a very tiny helper to allow overlay mounting. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#ifdef __FreeBSD__ +#include +#include + +#define MOUNT mount_wrapper +#define MS_MGC_VAL 0 +#define MS_SILENT 0 +#define MS_NODEV 0 +#define MS_UNBINDABLE 0 +#define MS_BIND 1 +#define MS_REC 2 +#define MS_REMOUNT 4 +#define MS_NOATIME 8 +#define MS_NOSUID 16 +#define MS_RDONLY 32 + +void +build_iovec(struct iovec **iov, int *iovlen, const char *name, const void *val, + size_t len) +{ + int i; + + if (*iovlen < 0) + return; + i = *iovlen; + *iov = reinterpret_cast(realloc(*iov, sizeof **iov * (i + 2))); + if (*iov == NULL) { + *iovlen = -1; + return; + } + (*iov)[i].iov_base = strdup(name); + (*iov)[i].iov_len = strlen(name) + 1; + i++; + (*iov)[i].iov_base = const_cast(val); + if (len == (size_t)-1) { + if (val != NULL) + len = strlen(reinterpret_cast(val)) + 1; + else + len = 0; + } + (*iov)[i].iov_len = (int)len; + *iovlen = ++i; +} + +int mount_wrapper(const char *source, const char *target, + const char *filesystemtype, unsigned long mountflags, + const void *data) +{ + struct iovec *iov = NULL; + int iovlen = 0; + int freebsd_flags = 0; + (void)data; + + if(mountflags & MS_BIND) + filesystemtype = "nullfs"; + + if(mountflags & MS_REMOUNT) + freebsd_flags |= MNT_UPDATE; + + if(mountflags & MS_NOATIME) + freebsd_flags |= MNT_NOATIME; + + if(mountflags & MS_NOSUID) + freebsd_flags |= MNT_NOSUID; + + if(mountflags & MS_RDONLY) + freebsd_flags |= MNT_RDONLY; + + // TODO: handle MS_REC? + + build_iovec(&iov, &iovlen, "fstype", reinterpret_cast(filesystemtype), (size_t)-1); + build_iovec(&iov, &iovlen, "fspath", reinterpret_cast(target), (size_t)-1); + build_iovec(&iov, &iovlen, "from", reinterpret_cast(source), (size_t)-1); + + return nmount(iov, iovlen, freebsd_flags); +} + +#define MNT_DETACH 1 + +int umount2(const char *target, int flags) +{ + struct statfs* mntbufs; + int numInfos = getmntinfo(&mntbufs, MNT_WAIT); + bool targetMounted = false; + + for (int i = 0; i < numInfos; i++) + { + if (!strcmp(target, mntbufs[i].f_mntonname)) + { + targetMounted = true; + break; + } + } + + if (!targetMounted) + { + errno = EINVAL; + return -1; + } + + if(flags == MNT_DETACH) + flags = 0; + + return unmount(target, flags); +} +#else +#define MOUNT mount +#endif + +void usage(const char* program) +{ + fprintf(stderr, "Usage: %s <-b|-r> \n", program); + fprintf(stderr, " %s -u .\n", program); +#ifdef __FreeBSD__ + fprintf(stderr, " %s -d .\n", program); +#endif + fprintf(stderr, " -b bind and mount the source to target.\n"); + fprintf(stderr, " -r bind and mount the source to target as readonly.\n"); +#ifdef __FreeBSD__ + fprintf(stderr, " -d mount minimal devfs layout (random and urandom) to target.\n"); +#endif + fprintf(stderr, " -u to unmount the target.\n"); +} + +int domount(int argc, const char* const* argv) +{ + /*WARNING: PRIVILEGED CODE CHECKING START */ + /*WARNING*/ if (!hasCorrectUID(/* appName = */ "coolmount")) + /*WARNING*/ { + /*WARNING*/ fprintf(stderr, "Aborting.\n"); + /*WARNING*/ return EX_SOFTWARE; + /*WARNING*/ } + /*WARNING: PRIVILEGED CODE CHECKING END */ + + const char* program = argv[0]; + if (argc < 3) + { + usage(program); + return EX_USAGE; + } + + const char* option = argv[1]; + if (argc == 3 && strcmp(option, "-u") == 0) // Unmount + { + const char* target = argv[2]; + + struct stat sb; + const bool target_exists = (stat(target, &sb) == 0 && S_ISDIR(sb.st_mode)); + + // Do nothing if target doesn't exist. + if (target_exists) + { + // Unmount the target, first by detaching. This should succeed. + int retval = umount2(target, MNT_DETACH); + if (retval != 0) + { + if (errno != EINVAL) + { + // Don't complain where MNT_DETACH is unsupported. Fall back to forcing instead. + fprintf(stderr, "%s: unmount failed to detach [%s]: %s.\n", program, target, + strerror(errno)); + } + + // Now try to force the unmounting, which isn't supported on all filesystems. + retval = umount2(target, MNT_FORCE); + if (retval != 0) + { + // From man umount(2), MNT_FORCE is not commonly supported: + // As at Linux 4.12, MNT_FORCE is supported only on the following filesystems: 9p (since + // Linux 2.6.16), ceph (since Linux 2.6.34), cifs (since Linux 2.6.12), + // fuse (since Linux 2.6.16), lustre (since Linux 3.11), and NFS (since Linux 2.1.116). + if (errno != EINVAL) + { + // Complain to capture the reason of failure. + fprintf(stderr, "%s: forced unmount of [%s] failed: %s.\n", program, target, + strerror(errno)); + } + + return EX_SOFTWARE; + } + } + } + } +#ifdef __FreeBSD__ + else if (argc == 3 && strcmp(option, "-d") == 0) // Mount devfs + { + const char* target = argv[2]; + + struct stat sb; + const bool target_exists = (stat(target, &sb) == 0 && S_ISDIR(sb.st_mode)); + + if (!target_exists) + { + fprintf(stderr, "%s: cannot mount on invalid target directory [%s].\n", program, + target); + return EX_USAGE; + } + + struct iovec *iov = NULL; + int iovlen = 0; + + build_iovec(&iov, &iovlen, "fstype", "devfs", (size_t)-1); + build_iovec(&iov, &iovlen, "fspath", reinterpret_cast(target), (size_t)-1); + build_iovec(&iov, &iovlen, "from", "devfs", (size_t)-1); + // See /etc/defaults/devfs.rules + // [devfsrules_jail=4] + build_iovec(&iov, &iovlen, "ruleset", "4", (size_t)-1); + + int retval = nmount(iov, iovlen, 0); + if (retval) + { + fprintf(stderr, "%s: mount failed create to devfs layout in [%s]: %s.\n", program, target, + strerror(errno)); + return EX_SOFTWARE; + } + } +#endif + else if (argc == 4) // Mount + { + const char* source = argv[2]; + struct stat sb; + if (stat(source, &sb)) + { + fprintf(stderr, "%s: cannot mount from invalid source [%s]. stat failed with %s\n", + program, source, strerror(errno)); + return EX_USAGE; + } + + const bool isDir = S_ISDIR(sb.st_mode); + const bool isCharDev = S_ISCHR(sb.st_mode); // We don't support regular files. + if (isCharDev) + { + // Even for character devices, we only support the random devices. + if (strstr("/dev/random", source) && strstr("/dev/urandom", source)) + { + fprintf(stderr, "%s: cannot mount untrusted character-device [%s]", program, + source); + return EX_USAGE; + } + } + + if (!isDir && !isCharDev) + { + fprintf(stderr, + "%s: cannot mount from invalid source [%s], it is neither a file nor a " + "directory.\n", + program, source); + return EX_USAGE; + } + + const char* target = argv[3]; + if (stat(target, &sb)) + { + fprintf(stderr, "%s: cannot mount on invalid target [%s]. stat failed with %s\n", + program, target, strerror(errno)); + return EX_USAGE; + } + + const bool target_exists = + ((isDir && S_ISDIR(sb.st_mode)) || (isCharDev && S_ISREG(sb.st_mode))); + if (!target_exists) + { + fprintf(stderr, + "%s: cannot mount on invalid target [%s], it is not a %s as the source\n", + program, target, isDir ? "directory" : "file"); + return EX_USAGE; + } + + // Mount the source path as the target path. + // First bind to mount an existing directory node into the chroot. + // MS_BIND ignores other flags. + if (strcmp(option, "-b") == 0) // Shared or Bind Mount. + { + const int retval + = MOUNT(source, target, nullptr, (MS_MGC_VAL | MS_BIND | MS_REC), nullptr); + if (retval) + { + fprintf(stderr, "%s: mount failed to bind [%s] to [%s]: %s.\n", program, source, + target, strerror(errno)); + return EX_SOFTWARE; + } + } + else if (strcmp(option, "-r") == 0) // Readonly Mount. + { + // Now we need to set read-only and other flags with a remount. + int retval = MOUNT(source, target, nullptr, + (MS_BIND | MS_REC | MS_REMOUNT | MS_NOATIME | MS_NODEV | MS_NOSUID + | MS_RDONLY | MS_SILENT), + nullptr); + if (retval) + { + fprintf(stderr, "%s: mount failed remount [%s] readonly: %s.\n", program, target, + strerror(errno)); + return EX_SOFTWARE; + } + + retval = MOUNT(source, target, nullptr, (MS_UNBINDABLE | MS_REC), nullptr); + if (retval) + { + fprintf(stderr, "%s: mount failed make [%s] private: %s.\n", program, target, + strerror(errno)); + return EX_SOFTWARE; + } + } + } + else + { + usage(program); + return EX_USAGE; + } + + fflush(stderr); + return EX_OK; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/common/JailUtil.cpp b/common/JailUtil.cpp index d5796f59d..1f4d2ddcb 100644 --- a/common/JailUtil.cpp +++ b/common/JailUtil.cpp @@ -14,8 +14,10 @@ #include "FileUtil.hpp" #include "JailUtil.hpp" +#include #include #include +#include #include #include #ifdef __linux__ @@ -25,20 +27,112 @@ #include #include #include +#include #include #include "Log.hpp" #include +extern int domount(int argc, const char* const* argv); + namespace JailUtil { static const std::string CoolTestMountpoint = "cool_test_mount"; +static void setdeny() +{ + std::ofstream of("/proc/self/setgroups"); + of << "deny"; +} + +static void mapuser(uid_t origuid, uid_t newuid, gid_t origgid, gid_t newgid) +{ + { + std::ofstream of("/proc/self/uid_map"); + of << newuid << " " << origuid << " 1"; + } + + { + std::ofstream of("/proc/self/gid_map"); + of << newgid << " " << origgid << " 1"; + } +} + +bool becomeMountingUser(uid_t uid, gid_t gid) +{ +#ifdef __linux__ + // Put this process into its own user and mount namespace. + if (unshare(CLONE_NEWNS | CLONE_NEWUSER) != 0) + { + // having multiple threads is a source of failure f.e. + fprintf(stderr, "becomeMountingUser, unshare failed %s\n", strerror(errno)); + return false; + } + + // Do not propagate any mounts from this new namespace to the system. + if (mount("none", "/", nullptr, MS_REC | MS_PRIVATE, nullptr) != 0) + { + fprintf(stderr, "becomeMountingUser, root mount failed %s\n", strerror(errno)); + return false; + } + + setdeny(); + + // Map this user as the root user of the new namespace + mapuser(uid, 0, gid, 0); + + return true; +#else + (void)uid; + (void)gid; + return false; +#endif +} + +bool restorePremountUser(uid_t uid, gid_t gid) +{ +#ifdef __linux__ + if (unshare(CLONE_NEWUSER) != 0) + { + // having multiple threads is a source of failure f.e. + fprintf(stderr, "restorePremountUser, unshare failed %s\n", strerror(errno)); + return false; + } + + // undo map of this user to root + mapuser(0, uid, 0, gid); + + assert(geteuid() == uid); + assert(getegid() == gid); + + return true; +#else + (void)uid; + (void)gid; + return false; +#endif +} + bool coolmount(const std::string& arg, std::string source, std::string target) { source = Util::trim(source, '/'); target = Util::trim(target, '/'); + + if (isMountNamespacesEnabled()) + { + const char *argv[4]; + argv[0] = "notcoolmount"; + int argc = 1; + if (!arg.empty()) + argv[argc++] = arg.c_str(); + if (!source.empty()) + argv[argc++] = source.c_str(); + if (!target.empty()) + argv[argc++] = target.c_str(); + return domount(argc, argv) == EX_OK; + } + const std::string cmd = Poco::Path(Util::getApplicationPath(), "coolmount").toString() + ' ' + arg + ' ' + source + ' ' + target; LOG_TRC("Executing coolmount command: " << cmd); @@ -343,6 +437,21 @@ bool isBindMountingEnabled() return std::getenv(BIND_MOUNTING_ENVAR_NAME) != nullptr; } +constexpr const char* NAMESPACE_MOUNTING_ENVAR_NAME = "COOL_NAMESPACE_MOUNT"; + +void enableMountNamespaces() +{ + // Set the envar to enable. + setenv(NAMESPACE_MOUNTING_ENVAR_NAME, "1", 1); +} + +bool isMountNamespacesEnabled() +{ + // Check if we have a valid envar set. + return std::getenv(NAMESPACE_MOUNTING_ENVAR_NAME) != nullptr; +} + + namespace SysTemplate { /// The network and other system files we need to keep up-to-date in jails. diff --git a/common/JailUtil.hpp b/common/JailUtil.hpp index cbaf31cb4..3e90711fe 100644 --- a/common/JailUtil.hpp +++ b/common/JailUtil.hpp @@ -28,6 +28,14 @@ constexpr const char CHILDROOT_TMP_INCOMING_PATH[] = "/tmp/incoming"; /// The LO installation directory with jail. constexpr const char LO_JAIL_SUBPATH[] = "lo"; +/// Try to put this process into its own user and mount namespace and +/// set as root within that namespace to allow mounting +/// There cannot be other threads running when calling these or it will +/// fail. +bool becomeMountingUser(uid_t uid, gid_t gid); +/// We could switch back to normal user within the namespace too +bool restorePremountUser(uid_t uid, gid_t gid); + /// Bind mount a jail directory. bool bind(const std::string& source, const std::string& target); @@ -61,6 +69,12 @@ void disableBindMounting(); /// Returns true iff bind-mounting is enabled in this process. bool isBindMountingEnabled(); +/// Enable namespace-mounting in this process. +void enableMountNamespaces(); + +/// Returns true iff namespace-mounting is enabled in this process. +bool isMountNamespacesEnabled(); + namespace SysTemplate { /// Setup links for /dev/random and /dev/urandom in systemplate. diff --git a/coolwsd.spec.in b/coolwsd.spec.in index 36ec11fac..d5f284edc 100644 --- a/coolwsd.spec.in +++ b/coolwsd.spec.in @@ -57,6 +57,7 @@ echo "account required pam_unix.so" >> %{buildroot}/etc/pam.d/coolwsd /usr/bin/coolwsd-systemplate-setup /usr/bin/loolwsd-systemplate-setup /usr/bin/coolforkit +/usr/bin/coolforkitns /usr/bin/coolconvert /usr/bin/coolconfig /usr/bin/loolconfig diff --git a/coolwsd.xml.in b/coolwsd.xml.in index 521424daa..02ef51eec 100644 --- a/coolwsd.xml.in +++ b/coolwsd.xml.in @@ -42,6 +42,7 @@ @ENABLE_EXPERIMENTAL@ + @NUM_PRESPAWN_CHILDREN@ diff --git a/kit/ForKit.cpp b/kit/ForKit.cpp index a0bad6aa9..b2a271284 100644 --- a/kit/ForKit.cpp +++ b/kit/ForKit.cpp @@ -251,12 +251,12 @@ static bool haveCapability(cap_value_t capability) return true; } -static bool haveCorrectCapabilities() +static bool haveCorrectCapabilities(bool useMountNamespaces) { bool result = true; // Do check them all, don't shortcut with && - if (!haveCapability(CAP_SYS_CHROOT)) + if (!useMountNamespaces && !haveCapability(CAP_SYS_CHROOT)) result = false; if (!haveCapability(CAP_FOWNER)) result = false; @@ -266,7 +266,7 @@ static bool haveCorrectCapabilities() return result; } #else -static bool haveCorrectCapabilities() +static bool haveCorrectCapabilities(bool /*useMountNamespaces*/) { // chroot() can only be called by root return getuid() == 0; @@ -621,6 +621,7 @@ int forkit_main(int argc, char** argv) } LogDisabledAreas = logDisabledAreas ? logDisabledAreas : ""; + bool useMountNamespaces = false; std::string childRoot; std::string sysTemplate; std::string loTemplate; @@ -709,6 +710,9 @@ int forkit_main(int argc, char** argv) NoSeccomp = true; } + else if (std::strstr(cmd, "--namespace") == cmd) + useMountNamespaces = true; + else if (std::strstr(cmd, "--ui") == cmd) { eq = std::strchr(cmd, '='); @@ -736,7 +740,7 @@ int forkit_main(int argc, char** argv) if (!std::getenv("LD_BIND_NOW")) // must be set by parent. LOG_INF("Note: LD_BIND_NOW is not set."); - if (!NoCapsForKit && !haveCorrectCapabilities()) + if (!NoCapsForKit && !haveCorrectCapabilities(useMountNamespaces)) { LOG_FTL("Capabilities are not set for the coolforkit program."); LOG_FTL("Please make sure that the current partition was *not* mounted with the 'nosuid' option."); diff --git a/tools/mount.cpp b/tools/mount.cpp index 3b2e38346..41b96ce65 100644 --- a/tools/mount.cpp +++ b/tools/mount.cpp @@ -13,329 +13,14 @@ */ #include - #include -#include -#include -#include -#include -#include - -#include - -#ifdef __FreeBSD__ #include -#include -#define MOUNT mount_wrapper -#define MS_MGC_VAL 0 -#define MS_SILENT 0 -#define MS_NODEV 0 -#define MS_UNBINDABLE 0 -#define MS_BIND 1 -#define MS_REC 2 -#define MS_REMOUNT 4 -#define MS_NOATIME 8 -#define MS_NOSUID 16 -#define MS_RDONLY 32 - -void -build_iovec(struct iovec **iov, int *iovlen, const char *name, const void *val, - size_t len) -{ - int i; - - if (*iovlen < 0) - return; - i = *iovlen; - *iov = reinterpret_cast(realloc(*iov, sizeof **iov * (i + 2))); - if (*iov == NULL) { - *iovlen = -1; - return; - } - (*iov)[i].iov_base = strdup(name); - (*iov)[i].iov_len = strlen(name) + 1; - i++; - (*iov)[i].iov_base = const_cast(val); - if (len == (size_t)-1) { - if (val != NULL) - len = strlen(reinterpret_cast(val)) + 1; - else - len = 0; - } - (*iov)[i].iov_len = (int)len; - *iovlen = ++i; -} - -int mount_wrapper(const char *source, const char *target, - const char *filesystemtype, unsigned long mountflags, - const void *data) -{ - struct iovec *iov = NULL; - int iovlen = 0; - int freebsd_flags = 0; - (void)data; - - if(mountflags & MS_BIND) - filesystemtype = "nullfs"; - - if(mountflags & MS_REMOUNT) - freebsd_flags |= MNT_UPDATE; - - if(mountflags & MS_NOATIME) - freebsd_flags |= MNT_NOATIME; - - if(mountflags & MS_NOSUID) - freebsd_flags |= MNT_NOSUID; - - if(mountflags & MS_RDONLY) - freebsd_flags |= MNT_RDONLY; - - // TODO: handle MS_REC? - - build_iovec(&iov, &iovlen, "fstype", reinterpret_cast(filesystemtype), (size_t)-1); - build_iovec(&iov, &iovlen, "fspath", reinterpret_cast(target), (size_t)-1); - build_iovec(&iov, &iovlen, "from", reinterpret_cast(source), (size_t)-1); - - return nmount(iov, iovlen, freebsd_flags); -} - -#define MNT_DETACH 1 - -int umount2(const char *target, int flags) -{ - struct statfs* mntbufs; - int numInfos = getmntinfo(&mntbufs, MNT_WAIT); - bool targetMounted = false; - - for (int i = 0; i < numInfos; i++) - { - if (!strcmp(target, mntbufs[i].f_mntonname)) - { - targetMounted = true; - break; - } - } - - if (!targetMounted) - { - errno = EINVAL; - return -1; - } - - if(flags == MNT_DETACH) - flags = 0; - - return unmount(target, flags); -} -#else -#define MOUNT mount -#endif - -void usage(const char* program) -{ - fprintf(stderr, "Usage: %s <-b|-r> \n", program); - fprintf(stderr, " %s -u .\n", program); -#ifdef __FreeBSD__ - fprintf(stderr, " %s -d .\n", program); -#endif - fprintf(stderr, " -b bind and mount the source to target.\n"); - fprintf(stderr, " -r bind and mount the source to target as readonly.\n"); -#ifdef __FreeBSD__ - fprintf(stderr, " -d mount minimal devfs layout (random and urandom) to target.\n"); -#endif - fprintf(stderr, " -u to unmount the target.\n"); -} +extern int domount(int argc, const char* const* argv); int main(int argc, char** argv) { - /*WARNING: PRIVILEGED CODE CHECKING START */ - /*WARNING*/ if (!hasCorrectUID(/* appName = */ "coolmount")) - /*WARNING*/ { - /*WARNING*/ fprintf(stderr, "Aborting.\n"); - /*WARNING*/ return EX_SOFTWARE; - /*WARNING*/ } - /*WARNING: PRIVILEGED CODE CHECKING END */ - - const char* program = argv[0]; - if (argc < 3) - { - usage(program); - return EX_USAGE; - } - - const char* option = argv[1]; - if (argc == 3 && strcmp(option, "-u") == 0) // Unmount - { - const char* target = argv[2]; - - struct stat sb; - const bool target_exists = (stat(target, &sb) == 0 && S_ISDIR(sb.st_mode)); - - // Do nothing if target doesn't exist. - if (target_exists) - { - // Unmount the target, first by detaching. This should succeed. - int retval = umount2(target, MNT_DETACH); - if (retval != 0) - { - if (errno != EINVAL) - { - // Don't complain where MNT_DETACH is unsupported. Fall back to forcing instead. - fprintf(stderr, "%s: unmount failed to detach [%s]: %s.\n", program, target, - strerror(errno)); - } - - // Now try to force the unmounting, which isn't supported on all filesystems. - retval = umount2(target, MNT_FORCE); - if (retval != 0) - { - // From man umount(2), MNT_FORCE is not commonly supported: - // As at Linux 4.12, MNT_FORCE is supported only on the following filesystems: 9p (since - // Linux 2.6.16), ceph (since Linux 2.6.34), cifs (since Linux 2.6.12), - // fuse (since Linux 2.6.16), lustre (since Linux 3.11), and NFS (since Linux 2.1.116). - if (errno != EINVAL) - { - // Complain to capture the reason of failure. - fprintf(stderr, "%s: forced unmount of [%s] failed: %s.\n", program, target, - strerror(errno)); - } - - return EX_SOFTWARE; - } - } - } - } -#ifdef __FreeBSD__ - else if (argc == 3 && strcmp(option, "-d") == 0) // Mount devfs - { - const char* target = argv[2]; - - struct stat sb; - const bool target_exists = (stat(target, &sb) == 0 && S_ISDIR(sb.st_mode)); - - if (!target_exists) - { - fprintf(stderr, "%s: cannot mount on invalid target directory [%s].\n", program, - target); - return EX_USAGE; - } - - struct iovec *iov = NULL; - int iovlen = 0; - - build_iovec(&iov, &iovlen, "fstype", "devfs", (size_t)-1); - build_iovec(&iov, &iovlen, "fspath", reinterpret_cast(target), (size_t)-1); - build_iovec(&iov, &iovlen, "from", "devfs", (size_t)-1); - // See /etc/defaults/devfs.rules - // [devfsrules_jail=4] - build_iovec(&iov, &iovlen, "ruleset", "4", (size_t)-1); - - int retval = nmount(iov, iovlen, 0); - if (retval) - { - fprintf(stderr, "%s: mount failed create to devfs layout in [%s]: %s.\n", program, target, - strerror(errno)); - return EX_SOFTWARE; - } - } -#endif - else if (argc == 4) // Mount - { - const char* source = argv[2]; - struct stat sb; - if (stat(source, &sb)) - { - fprintf(stderr, "%s: cannot mount from invalid source [%s]. stat failed with %s\n", - program, source, strerror(errno)); - return EX_USAGE; - } - - const bool isDir = S_ISDIR(sb.st_mode); - const bool isCharDev = S_ISCHR(sb.st_mode); // We don't support regular files. - if (isCharDev) - { - // Even for character devices, we only support the random devices. - if (strstr("/dev/random", source) && strstr("/dev/urandom", source)) - { - fprintf(stderr, "%s: cannot mount untrusted character-device [%s]", program, - source); - return EX_USAGE; - } - } - - if (!isDir && !isCharDev) - { - fprintf(stderr, - "%s: cannot mount from invalid source [%s], it is neither a file nor a " - "directory.\n", - program, source); - return EX_USAGE; - } - - const char* target = argv[3]; - if (stat(target, &sb)) - { - fprintf(stderr, "%s: cannot mount on invalid target [%s]. stat failed with %s\n", - program, target, strerror(errno)); - return EX_USAGE; - } - - const bool target_exists = - ((isDir && S_ISDIR(sb.st_mode)) || (isCharDev && S_ISREG(sb.st_mode))); - if (!target_exists) - { - fprintf(stderr, - "%s: cannot mount on invalid target [%s], it is not a %s as the source\n", - program, target, isDir ? "directory" : "file"); - return EX_USAGE; - } - - // Mount the source path as the target path. - // First bind to mount an existing directory node into the chroot. - // MS_BIND ignores other flags. - if (strcmp(option, "-b") == 0) // Shared or Bind Mount. - { - const int retval - = MOUNT(source, target, nullptr, (MS_MGC_VAL | MS_BIND | MS_REC), nullptr); - if (retval) - { - fprintf(stderr, "%s: mount failed to bind [%s] to [%s]: %s.\n", program, source, - target, strerror(errno)); - return EX_SOFTWARE; - } - } - else if (strcmp(option, "-r") == 0) // Readonly Mount. - { - // Now we need to set read-only and other flags with a remount. - int retval = MOUNT(source, target, nullptr, - (MS_BIND | MS_REC | MS_REMOUNT | MS_NOATIME | MS_NODEV | MS_NOSUID - | MS_RDONLY | MS_SILENT), - nullptr); - if (retval) - { - fprintf(stderr, "%s: mount failed remount [%s] readonly: %s.\n", program, target, - strerror(errno)); - return EX_SOFTWARE; - } - - retval = MOUNT(source, target, nullptr, (MS_UNBINDABLE | MS_REC), nullptr); - if (retval) - { - fprintf(stderr, "%s: mount failed make [%s] private: %s.\n", program, target, - strerror(errno)); - return EX_SOFTWARE; - } - } - } - else - { - usage(program); - return EX_USAGE; - } - - fflush(stderr); - return EX_OK; + return domount(argc, argv); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/wsd/COOLWSD.cpp b/wsd/COOLWSD.cpp index a77ee86df..a32a1a1f2 100644 --- a/wsd/COOLWSD.cpp +++ b/wsd/COOLWSD.cpp @@ -718,6 +718,7 @@ std::string COOLWSD::ConfigFile = COOLWSD_CONFIGDIR "/coolwsd.xml"; std::string COOLWSD::ConfigDir = COOLWSD_CONFIGDIR "/conf.d"; bool COOLWSD::EnableTraceEventLogging = false; bool COOLWSD::EnableAccessibility = false; +bool COOLWSD::EnableMountNamespaces= false; FILE *COOLWSD::TraceEventFile = NULL; std::string COOLWSD::LogLevel = "trace"; std::string COOLWSD::LogLevelStartup = "trace"; @@ -1997,6 +1998,7 @@ void COOLWSD::innerInitialize(Application& self) { "logging.disable_server_audit", "false" }, { "browser_logging", "false" }, { "mount_jail_tree", "true" }, + { "mount_namespaces", "false" }, { "net.connection_timeout_secs", "30" }, { "net.listen", "any" }, { "net.proto", "all" }, @@ -2167,6 +2169,12 @@ void COOLWSD::innerInitialize(Application& self) // Experimental features. EnableExperimental = getConfigValue(conf, "experimental_features", false); + const bool UseMountNamespaces = EnableExperimental && getConfigValue(conf, "mount_namespaces", false); + // attempt this early before starting logging which spawns a thread + EnableMountNamespaces = UseMountNamespaces && JailUtil::becomeMountingUser(geteuid(), getegid()); + if (EnableMountNamespaces) + JailUtil::enableMountNamespaces(); + EnableAccessibility = getConfigValue(conf, "accessibility.enable", false); // Setup user interface mode @@ -2532,6 +2540,9 @@ void COOLWSD::innerInitialize(Application& self) std::string fontsMissingHandling = config::getString("fonts_missing.handling", "log"); setenv("FONTS_MISSING_HANDLING", fontsMissingHandling.c_str(), 1); + if (UseMountNamespaces && !EnableMountNamespaces) + LOG_WRN("Using mount namespaces failed."); + IsBindMountingEnabled = getConfigValue(conf, "mount_jail_tree", true); #if CODE_COVERAGE // Code coverage is not supported with bind-mounting. @@ -3477,7 +3488,16 @@ bool COOLWSD::createForKit() #elif VALGRIND_COOLFORKIT std::string forKitPath = "/usr/bin/valgrind"; #else - std::string forKitPath = parentPath + "coolforkit"; + std::string forKitPath = parentPath; + if (EnableMountNamespaces) + { + forKitPath += "coolforkitns"; + args.push_back("--namespace"); + } + else + { + forKitPath += "coolforkit"; + } #endif // Always reap first, in case we haven't done so yet. diff --git a/wsd/COOLWSD.hpp b/wsd/COOLWSD.hpp index 6ce0eedd6..84f89e1d0 100644 --- a/wsd/COOLWSD.hpp +++ b/wsd/COOLWSD.hpp @@ -276,6 +276,7 @@ public: static std::string LOKitVersion; static bool EnableTraceEventLogging; static bool EnableAccessibility; + static bool EnableMountNamespaces; static FILE *TraceEventFile; static void writeTraceEventRecording(const char *data, std::size_t nbytes); static void writeTraceEventRecording(const std::string &recording);