/* -*- 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 #include #ifdef __FreeBSD__ #include #include #define MOUNT mount_wrapper #define MS_MGC_VAL 0 #define MS_NODEV 0 #define MS_UNBINDABLE 0 #define MS_BIND 1 #define MS_REC 2 #define MS_REMOUNT 4 #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_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) { 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. unsigned long mountflags = (MS_BIND | MS_REMOUNT | MS_NODEV | MS_NOSUID | MS_RDONLY); /* a) In the linux namespace mount case an additional MS_NOATIME, etc. will result in EPERM on remounting something hosted in a toplevel [rel]atime mount. man 2 mount has 'An attempt was made to modify (MS_REMOUNT) the MS_RDONLY, MS_NOSUID, or MS_NOEXEC flag, or one of the "atime" flags (MS_NOATIME, MS_NODIRATIME, MS_RELATIME) of an existing mount, but the mount is locked'. b) lxc has default apparmor rules of https://github.com/lxc/lxc/blob/main/config/apparmor/abstractions/container-base where the closest match is: "mount options=(ro,remount,bind,nodev,nosuid)" so additional 'MS_SILENT' or 'MS_REC' flags cause the remount to be denied */ int retval = MOUNT(source, target, nullptr, mountflags, 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: */