Files
libfuse/lib/mount_fsmount.c
T
Jingbo Xu 24d40b2a7c lib: strip enclosing quotes from fsconfig option values
With the old mount(2) syscall, the kernel's SELinux code
(selinux_sb_eat_lsm_opts) internally strips double quotes from context
option values like context="system_u:object_r:root_t:s0". However, with
the new fsconfig() API the value is passed through verbatim, causing
SELinux to reject the mount with ENODEV.

Strip enclosing double quotes from key=value option values in
apply_opt_key_value() before passing them to fsconfig().

Signed-off-by: Jingbo Xu <jingbo.xu@linux.alibaba.com>
2026-06-18 08:48:59 +02:00

459 lines
11 KiB
C

/*
* FUSE: Filesystem in Userspace
* Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
* 2026 Bernd Schubert <bernd@bsbernd.com>
*
* New Linux mount API (fsopen/fsconfig/fsmount/move_mount) support.
*
* This program can be distributed under the terms of the GNU LGPLv2.
* See the file LGPL2.txt.
*/
#define _GNU_SOURCE
#include "fuse_config.h"
#include "fuse_misc.h"
#include "mount_util.h"
#include "mount_i_linux.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/mount.h>
#include <sys/syscall.h>
/*
* Mount attribute flags for fsmount() - from linux/mount.h
* This file is only compiled conditionally when support for the new
* mount API is detected - only flags that were not in the initial linux
* commit introducing that API are defined here.
*/
#ifndef MOUNT_ATTR_NOSYMFOLLOW
#define MOUNT_ATTR_NOSYMFOLLOW 0x00200000
#endif
/* Must be called after set_fsconfig_ms_flags */
unsigned long ms_flags_to_mount_attrs(unsigned long ms_flags,
unsigned int *mount_attrs)
{
int i;
*mount_attrs = 0;
for (i = 0; mount_flags[i].opt != NULL && ms_flags != 0; i++) {
/* Only process mount attributes (mount_attr != 0) with on==1 */
if (!mount_flags[i].mount_attr || !mount_flags[i].on)
continue;
if (ms_flags & mount_flags[i].flag) {
*mount_attrs |= mount_flags[i].mount_attr;
ms_flags &= ~mount_flags[i].flag;
}
}
return ms_flags;
}
void log_fsconfig_kmsg(int fd)
{
char buf[4096];
int err, sz = 0;
err = errno;
while ((sz = read(fd, buf, sizeof(buf) - 1)) != -1) {
if (sz <= 0)
continue;
if (buf[sz - 1] == '\n')
buf[--sz] = '\0';
else
buf[sz] = '\0';
if (!*buf)
continue;
switch (buf[0]) {
case 'e':
fprintf(stderr, " Error: %s\n", buf + 2);
break;
case 'w':
fprintf(stderr, " Warning: %s\n", buf + 2);
break;
case 'i':
fprintf(stderr, " Info: %s\n", buf + 2);
break;
default:
fprintf(stderr, " %s\n", buf);
break;
}
}
errno = err;
}
/* Must be called before ms_flags_to_mount_attrs */
int set_fsconfig_ms_flags(int fsfd, unsigned long *ms_flags)
{
unsigned long flags = *ms_flags;
int ret;
int i;
for (i = 0; mount_flags[i].opt != NULL && flags != 0; i++) {
if (!mount_flags[i].is_fsconfig || !mount_flags[i].on)
continue;
if (!(flags & mount_flags[i].flag))
continue;
ret = fsconfig(fsfd, FSCONFIG_SET_FLAG, mount_flags[i].opt, NULL, 0);
if (ret) {
int save_errno = errno;
fprintf(stderr, "fuse: set fsconfig %s option failed: %s\n",
mount_flags[i].opt, strerror(save_errno));
log_fsconfig_kmsg(fsfd);
return -save_errno;
}
/*
* Only consume the bit if no fsmount mount-attr leg is
* also pending for this option. Otherwise leave it for
* ms_flags_to_mount_attrs() to apply via fsmount().
*/
if (!mount_flags[i].mount_attr)
flags &= ~mount_flags[i].flag;
}
*ms_flags = flags;
return 0;
}
int apply_fsconfig_opt_fd(int fsfd, const char *value)
{
int res;
/* The fd parameter is a u32 value, not a file descriptor to pass */
res = fsconfig(fsfd, FSCONFIG_SET_STRING, "fd", value, 0);
if (res == -1) {
int save_errno = errno;
fprintf(stderr, "fuse: fsconfig SET_STRING fd=%s failed:",
value);
log_fsconfig_kmsg(fsfd);
return -save_errno;
}
return 0;
}
int apply_fsconfig_opt_string(int fsfd, const char *key, const char *value)
{
int res, save_errno;
res = fsconfig(fsfd, FSCONFIG_SET_STRING, key, value, 0);
save_errno = errno;
if (res == -1) {
fprintf(stderr, "fuse: fsconfig SET_STRING %s=%s failed: ",
key, value);
log_fsconfig_kmsg(fsfd);
return -save_errno;
}
return 0;
}
/*
* Apply a boolean flag option via fsconfig
*
* Applies a mount option that is a simple flag without a value (e.g., "rw").
* Uses FSCONFIG_SET_FLAG to set the flag in the filesystem configuration.
*
* @fsfd fsopen fd
* @opt name of filesystem mount flag option
* Returns 0 on success, negative error code on failure.
*/
static int apply_opt_flag(int fsfd, const char *opt)
{
int res;
res = fsconfig(fsfd, FSCONFIG_SET_FLAG, opt, NULL, 0);
if (res == -1) {
int save_errno = errno;
fprintf(stderr, "fuse: fsconfig SET_FLAG %s failed:", opt);
log_fsconfig_kmsg(fsfd);
return -save_errno;
}
return 0;
}
/*
* Parse and apply a single mount option via fsconfig
*
* Parses a mount option which can be either:
* - A key=value pair (e.g., "rootmode=40000")
* - A flag without a value (e.g., "rw")
*
* Special handling for the "fd" parameter which is treated as a string
* value rather than a file descriptor to pass.
*
* @fsfd fsopen fd
* @opt filesystem mount option, may be modified
* Returns 0 on success, negative error code on failure.
*/
static int apply_opt_key_value(int fsfd, char *opt)
{
char *eq;
const char *key;
char *value;
size_t len;
eq = strchr(opt, '=');
if (!eq)
return apply_opt_flag(fsfd, opt);
*eq = '\0';
key = opt;
value = eq + 1;
/*
* Strip enclosing double quotes from the value, e.g.
* context="system_u:object_r:root_t:s0". The old mount(2) path relied
* on the kernel (selinux_sb_eat_lsm_opts) to strip these, but
* fsconfig() passes the value through as-is.
*/
len = strlen(value);
if (len >= 2 && value[0] == '"' && value[len - 1] == '"') {
value[len - 1] = '\0';
value++;
}
if (strcmp(key, "fd") == 0)
return apply_fsconfig_opt_fd(fsfd, value);
return apply_fsconfig_opt_string(fsfd, key, value);
}
/*
* Check if an option is a mount attribute (handled by fsmount, not fsconfig)
* Uses the mount_flags table to determine if an option is a mount attribute.
*/
static int is_mount_attr_opt(const char *opt)
{
int i;
for (i = 0; mount_flags[i].opt != NULL; i++) {
if (strcmp(mount_flags[i].opt, opt) == 0)
return mount_flags[i].mount_attr != 0;
}
/* Unknown option, assume it's not a mount attribute */
return 0;
}
/**
* Check if an option is a mount table option (not passed to fsconfig)
*/
static int is_mtab_only_opt(const char *opt)
{
/* These options are for /run/mount/utab only, not for the kernel */
return strncmp(opt, "user=", 5) == 0 ||
strcmp(opt, "rw") == 0;
}
int apply_fsconfig_mount_opts(int fsfd, const char *opts)
{
char *opts_copy;
char *opt;
char *saveptr;
int res;
if (!opts || !*opts)
return 0;
opts_copy = strdup(opts);
if (!opts_copy) {
fprintf(stderr, "fuse: failed to allocate memory\n");
return -ENOMEM;
}
opt = strtok_r(opts_copy, ",", &saveptr);
while (opt) {
/*
* Skip mount attributes, they're handled by fsmount()
* not fsconfig().
*
* These string options (nosuid, nodev, etc.) are reconstructed
* from MS_* flags by get_mtab_flag_opts() in lib/mount.c and
* get_mtab_opts() in util/fusermount.c. Both the library path
* (via fuse_kern_mount_get_base_mtab_opts) and fusermount3 path
* rebuild these strings from the flags bitmask and pass them in
* mtab_opts. They must be filtered here because they are mount
* attributes (passed to fsmount via MOUNT_ATTR_*), not
* filesystem parameters (which would be passed to fsconfig).
*
* Also skip mtab-only options - they're for /run/mount/utab, not kernel
*/
if (!is_mount_attr_opt(opt) && !is_mtab_only_opt(opt)) {
res = apply_opt_key_value(fsfd, opt);
if (res < 0) {
free(opts_copy);
return res;
}
}
opt = strtok_r(NULL, ",", &saveptr);
}
free(opts_copy);
return 0;
}
int fuse_kern_fsmount(const char *mnt, int dest_mnt_fd, unsigned long flags,
int blkdev, const char *fsname, const char *subtype,
const char *source_dev, const char *kernel_opts,
const char *mtab_opts)
{
char *type = NULL;
char *source = NULL;
int fsfd = -1;
int mountfd = -1;
int err, res;
unsigned int mount_attrs;
/* Build type and source strings */
type = fuse_mnt_build_type(blkdev, subtype);
source = fuse_mnt_build_source(fsname, subtype, source_dev, 0);
err = -ENOMEM;
if (!type || !source) {
fprintf(stderr, "fuse: failed to allocate memory\n");
goto out_free;
}
/* Try to open filesystem context */
fsfd = fsopen(type, FSOPEN_CLOEXEC);
if (fsfd == -1) {
if (errno != EPERM)
fprintf(stderr, "fuse: fsopen(%s) failed: %s\n", type,
strerror(errno));
goto out_free;
}
/* Configure subtype */
if (subtype) {
res = fsconfig(fsfd, FSCONFIG_SET_STRING, "subtype",
subtype, 0);
if (res) {
err = -errno;
log_fsconfig_kmsg(fsfd);
fprintf(stderr, "fuse: fsconfig subtype failed: %s\n",
strerror(-err));
goto out_free;
}
}
/* Configure source */
res = fsconfig(fsfd, FSCONFIG_SET_STRING, "source", source, 0);
if (res == -1) {
err = -errno;
log_fsconfig_kmsg(fsfd);
fprintf(stderr, "fuse: fsconfig source failed: %s\n",
strerror(errno));
goto out_free;
}
/* Apply VFS superblock (fsconfig) flags (ro, sync, dirsync) */
res = set_fsconfig_ms_flags(fsfd, &flags);
if (res != 0)
goto out_free;
/* Apply kernel options */
err = apply_fsconfig_mount_opts(fsfd, kernel_opts);
if (err < 0) {
log_fsconfig_kmsg(fsfd);
fprintf(stderr,
"fuse: failed to apply kernel options '%s'\n",
kernel_opts);
goto out_free;
}
/* Create the filesystem instance */
res = fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
if (res == -1) {
err = -errno;
log_fsconfig_kmsg(fsfd);
fprintf(stderr, "fuse: fsconfig CREATE failed: %s\n",
strerror(errno));
goto out_free;
}
/* Convert MS_* flags to MOUNT_ATTR_* for fsmount() */
flags = ms_flags_to_mount_attrs(flags, &mount_attrs);
if (flags != 0) {
/* unsupported flags found, either fsconfig or mount attr flags */
errno = ENOTSUP;
goto out_free;
}
/* Create mount object with mount attributes */
mountfd = fsmount(fsfd, FSMOUNT_CLOEXEC, mount_attrs);
if (mountfd == -1) {
err = -errno;
log_fsconfig_kmsg(fsfd);
fprintf(stderr, "fuse: fsmount failed: %s\n",
strerror(errno));
goto out_free;
}
close(fsfd);
fsfd = -1;
if (dest_mnt_fd >= 0)
res = move_mount(mountfd, "", dest_mnt_fd, "",
MOVE_MOUNT_F_EMPTY_PATH |
MOVE_MOUNT_T_EMPTY_PATH);
else
res = move_mount(mountfd, "", AT_FDCWD, mnt,
MOVE_MOUNT_F_EMPTY_PATH);
if (res == -1) {
err = -errno;
fprintf(stderr, "fuse: move_mount failed: %s\n",
strerror(errno));
goto out_close_mntfd;
}
err = fuse_mnt_add_mount_helper(mnt, source, type, mtab_opts);
if (err == -1)
goto out_umount;
close(mountfd);
free(source);
free(type);
return 0;
out_umount:
{
/* race free umount */
char fd_path[64];
snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", mountfd);
if (umount2(fd_path, MNT_DETACH) == -1 && errno != EINVAL) {
fprintf(stderr,
"fuse: cleanup umount failed: %s\n",
strerror(errno));
}
}
out_close_mntfd:
if (mountfd != -1)
close(mountfd);
out_free:
free(source);
free(type);
if (fsfd != -1)
close(fsfd);
errno = -err;
return -1;
}