Merge branch 'master' into fuse-service-container

* master:
  Change use_subtype_prefix type to bool in fuse_mnt_build_source()
  lib mount: fix fsfd leakage on error
  lib mount: include linux/mount.h in mount_i_linux.h
  lib mount: no need to fsconfig mtab options
  Fix ENODEV fallback logic for block devices with fsname
  Update the condition when to use synchronous init
  fuse_daemonize_early: Rename is_active to is_used, add new is_active
  Add a missing NULL check for x_mtab_opts in mount_fuse()
  fusermount: Fix heap buffer overflow in perform_mount()
  lib/mount.c: Restore "subtype#fsname" format for legacy kernel fallback
  fuse: No need to call strlen
This commit is contained in:
Bernd Schubert
2026-05-26 14:17:45 +02:00
16 changed files with 218 additions and 75 deletions
+5 -1
View File
@@ -2438,9 +2438,13 @@ int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf);
* called after this if io-uring is enabled. Also see
* fuse_session_daemonize_start().
*
* @param se the session
* @param enable disable auto-detection based on fuse_daemonize_early_start,
* true to forcefully enable, false to forcefully disable
*
* This must be called before fuse_session_mount() to have any effect.
*/
void fuse_session_want_sync_init(struct fuse_session *se);
void fuse_session_set_sync_init(struct fuse_session *se, bool enable);
/**
* Check if the connection / session is using synchronous FUSE_INIT
+4 -3
View File
@@ -2685,11 +2685,12 @@ static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent,
struct node *dot = NULL;
if (name[0] == '.') {
int len = strlen(name);
bool isdot = (name[1] == '\0');
bool isdotdot = ((name[1] == '.') && (name[2] == '\0'));
if (len == 1 || (name[1] == '.' && len == 2)) {
if (isdot || isdotdot) {
pthread_mutex_lock(&f->lock);
if (len == 1) {
if (isdot) {
if (f->conf.debug)
fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOT\n");
dot = get_node_nocheck(f, parent);
+8 -7
View File
@@ -287,7 +287,7 @@ void fuse_daemonize_early_success(void)
* Needs to be gracefully handled as automatically called libfuse
* internal from FUSE_INIT handler
*/
if (!fuse_daemonize_early_is_active())
if (!fuse_daemonize_early_is_used())
return;
fuse_daemonize_early_signal(FUSE_DAEMONIZE_SUCCESS);
@@ -298,11 +298,16 @@ void fuse_daemonize_early_fail(int err)
fuse_daemonize_early_signal(err);
}
bool fuse_daemonize_early_is_active(void)
bool fuse_daemonize_early_is_used(void)
{
return daemonize.daemonized || daemonize.active;
}
bool fuse_daemonize_early_is_active(void)
{
return daemonize.active;
}
void fuse_daemonize_early_set_mounted(void)
{
daemonize.mounted = true;
@@ -322,7 +327,7 @@ int fuse_daemonize(int foreground);
int fuse_daemonize(int foreground)
{
/* Check if the NEW API is used */
if (fuse_daemonize_early_is_active()) {
if (fuse_daemonize_early_is_used()) {
perror("Newer API fuse_daemonize_start() already used\n");
return -1;
}
@@ -389,8 +394,4 @@ int fuse_daemonize(int foreground)
return 0;
}
bool fuse_daemonize_is_used(void)
{
return daemonize.active;
}
+3 -5
View File
@@ -20,12 +20,10 @@
*/
void fuse_daemonize_early_set_mounted(void);
/*
* Check if daemonization is used.
*
* @return true if used, false otherwise
/**
* Check if fuse_daemonize_early_start() was used
*/
bool fuse_daemonize_is_used(void);
bool fuse_daemonize_early_is_used(void);
/* Set on handling FUSE_INIT */
void fuse_daemonize_set_got_init(void);
+7 -1
View File
@@ -72,6 +72,12 @@ struct fuse_session_uring {
struct fuse_timeout_thread;
enum fuse_sync_init {
FUSE_SYNC_INIT_AUTO = 0, /* auto detection using fuse_daemonize_early_start */
FUSE_SYNC_INIT_DISABLED, /* sync init forcefully disabled */
FUSE_SYNC_INIT_ENABLED /* sync_init forcefully enabled */
};
struct fuse_session {
_Atomic(char *)mountpoint;
int fd;
@@ -111,7 +117,7 @@ struct fuse_session {
bool buf_reallocable;
/* synchronous FUSE_INIT support */
bool want_sync_init;
enum fuse_sync_init want_sync_init;
bool is_sync_init; /* sync FUSE_INIT mount succeeded*/
pthread_t init_thread;
int init_error;
+9 -3
View File
@@ -4325,6 +4325,8 @@ fuse_session_new_versioned(struct fuse_args *args,
se->mo = mo;
se->want_sync_init = FUSE_SYNC_INIT_AUTO;
/* Fuse server application should pass the version it was compiled
* against and pass it. If a libfuse version accidentally introduces an
* ABI incompatibility, it might be possible to 'fix' that at run time,
@@ -4497,7 +4499,9 @@ static int session_start_sync_init(struct fuse_session *se, int fd,
* Older fuse servers do not set want_sync_init or start the new
* daemonize code, so they get async init.
*/
if (!fuse_daemonize_is_used() || !se->want_sync_init) {
if (se->want_sync_init == FUSE_SYNC_INIT_DISABLED ||
(se->want_sync_init == FUSE_SYNC_INIT_AUTO &&
!fuse_daemonize_early_is_used())) {
if (se->debug)
fuse_log(FUSE_LOG_DEBUG,
"fuse: sync init not enabled\n");
@@ -5121,11 +5125,13 @@ void fuse_session_stop_teardown_watchdog(void *data)
fuse_tt_destruct(tt);
}
void fuse_session_want_sync_init(struct fuse_session *se)
void fuse_session_set_sync_init(struct fuse_session *se, bool enable)
{
if (se == NULL)
return;
se->want_sync_init = true;
se->want_sync_init = enable ? FUSE_SYNC_INIT_ENABLED :
FUSE_SYNC_INIT_DISABLED;
}
void fuse_session_set_debug(struct fuse_session *se)
+1 -1
View File
@@ -992,7 +992,7 @@ int fuse_service_session_mount(struct fuse_service *sf, struct fuse_session *se,
{
char *fstype = fuse_mnt_build_type(se->mo->blkdev, se->mo->subtype);
char *source = fuse_mnt_build_source(se->mo->fsname, se->mo->subtype,
fuse_mnt_get_devname());
fuse_mnt_get_devname(), false);
char *mntopts = fuse_mnt_kernel_opts(se->mo);
char *mtabopts = fuse_mnt_mtab_opts(se->mo);
char path[32];
+1
View File
@@ -232,6 +232,7 @@ FUSE_3.19 {
fuse_daemonize_early_fail;
fuse_daemonize_early_is_active;
fuse_session_set_debug;
fuse_session_set_sync_init;
fuse_service_accept;
fuse_service_append_args;
fuse_service_can_allow_other;
+2 -1
View File
@@ -16,6 +16,7 @@
#include "fuse_opt.h"
#include "fuse_lowlevel.h"
#include "fuse_daemonize.h"
#include "fuse_daemonize_i.h"
#include "fuse_service.h"
#include "mount_util.h"
@@ -447,7 +448,7 @@ int fuse_main_real_versioned(int argc, char *argv[],
}
/* The application might have already started daemonization itself */
if (!fuse_daemonize_early_is_active()) {
if (!fuse_daemonize_early_is_used()) {
int daemonize_early_flags = 0;
if (opts.foreground)
+12 -7
View File
@@ -659,7 +659,7 @@ int fuse_kern_do_mount(const char *mnt, struct mount_opts *mo,
int res;
const char *devname = fuse_mnt_get_devname();
res = -ENOMEM;
source = fuse_mnt_build_source(mo->fsname, mo->subtype, devname);
source = fuse_mnt_build_source(mo->fsname, mo->subtype, devname, 0);
type = fuse_mnt_build_type(mo->blkdev, mo->subtype);
if (!type || !source) {
fuse_log(FUSE_LOG_ERR, "%s: failed to allocate memory\n",
@@ -670,16 +670,21 @@ int fuse_kern_do_mount(const char *mnt, struct mount_opts *mo,
res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
if (res == -1 && errno == ENODEV && mo->subtype) {
/* Probably missing subtype support */
/*
* The allocated space by fuse_mnt_build_{source,type}
* might be too small.
*/
free(source);
free(type);
type = fuse_mnt_build_type(mo->blkdev, NULL);
source = fuse_mnt_build_source(mo->fsname, NULL, devname);
if (mo->fsname) {
if (!mo->blkdev) {
source = fuse_mnt_build_source(mo->fsname, mo->subtype,
devname, 1);
} else {
source = fuse_mnt_build_source(mo->fsname, NULL,
devname, 0);
}
} else {
source = strdup(type);
}
if (!type || !source) {
fuse_log(FUSE_LOG_ERR,
+3 -14
View File
@@ -309,7 +309,7 @@ int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
/* Build type and source strings */
type = fuse_mnt_build_type(blkdev, subtype);
source = fuse_mnt_build_source(fsname, subtype, source_dev);
source = fuse_mnt_build_source(fsname, subtype, source_dev, 0);
err = -ENOMEM;
if (!type || !source) {
fprintf(stderr, "fuse: failed to allocate memory\n");
@@ -363,16 +363,6 @@ int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
goto out_free;
}
/* Apply mtab options (overlap with kernel_opts is filtered and tolerated) */
err = apply_fsconfig_mount_opts(fsfd, mtab_opts);
if (err < 0) {
log_fsconfig_kmsg(fsfd);
fprintf(stderr,
"fuse: failed to apply mtab options '%s'\n",
mtab_opts);
goto out_free;
}
/* Create the filesystem instance */
res = fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
if (res == -1) {
@@ -387,9 +377,8 @@ int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
flags = ms_flags_to_mount_attrs(flags, &mount_attrs);
if (flags != 0) {
/* unsupported flags found, either fsconfig or mount attr flags */
//fuse_log(FUSE_LOG_ERR, "Unsupported mount flags found: %d", flags);
errno = -ENOTSUP;
return -1;
errno = ENOTSUP;
goto out_free;
}
/* Create mount object with mount attributes */
+1
View File
@@ -11,6 +11,7 @@
#define FUSE_MOUNT_I_LINUX_H_
#include <sys/mount.h>
#include <linux/mount.h>
struct fuse_args;
+15 -3
View File
@@ -536,13 +536,25 @@ const char *fuse_mnt_get_devname(void)
}
char *fuse_mnt_build_source(const char *fsname, const char *subtype,
const char *devname)
const char *devname, bool use_subtype_prefix)
{
char *source;
int ret;
ret = asprintf(&source, "%s",
fsname ? fsname : (subtype ? subtype : devname));
if (use_subtype_prefix && fsname && subtype) {
ret = asprintf(&source, "%s#%s", subtype, fsname);
} else {
const char *src_str;
if (fsname)
src_str = fsname;
else if (subtype)
src_str = subtype;
else
src_str = devname;
ret = asprintf(&source, "%s", src_str);
}
if (ret == -1)
return NULL;
+14 -2
View File
@@ -10,6 +10,7 @@
#define FUSE_MOUNT_UTIL_H_
#include <sys/types.h>
#include <stdbool.h>
#include "mount_common_i.h" // IWYU pragma: keep
/* Mount flags mapping structure */
@@ -37,9 +38,20 @@ const char *fuse_mnt_get_devname(void);
int fuse_mnt_add_mount_helper(const char *mnt, const char *source,
const char *type, const char *mtab_opts);
/* Build source and type strings for mounting */
/*
* Build the mount source string for FUSE filesystems
* @fsname: Filesystem name (e.g., "user@host:/path")
* @subtype: Filesystem subtype (e.g., "sshfs")
* @devname: Device name (typically "/dev/fuse")
* @use_subtype_prefix: Set to 1 when falling back from a failed mount attempt
* with fuse.subtype (ENODEV error), to retry with the
* legacy "subtype#fsname" format. Set to 0 for the initial
* mount attempt on modern kernels.
*
* Returns: Allocated source string, or NULL on error
*/
char *fuse_mnt_build_source(const char *fsname, const char *subtype,
const char *devname);
const char *devname, bool use_subtype_prefix);
char *fuse_mnt_build_type(int blkdev, const char *subtype);
#endif /* FUSE_MOUNT_UTIL_H_ */
+72
View File
@@ -168,3 +168,75 @@ def test_mountinfo_attrs_root(tmpdir, output_checker, name,
with hello_mount(tmpdir, output_checker, name, opts) as mnt:
info = parse_mountinfo(mnt)
_check_attrs(info, must_have, must_not_have)
@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
def test_mountinfo_blkdev_with_fsname(tmpdir, output_checker, name):
"""
Test block device mount with fsname and subtype.
This test validates the ENODEV fallback logic for block devices with fsname.
When mounting with blkdev=1, fsname=<device>, and subtype=mysub, the mount
source should be the device name (fsname), not the filesystem type string.
The ENODEV fallback has three cases:
1. fsname + non-blkdev -> "subtype#fsname" (legacy format for regular FUSE)
2. fsname + blkdev -> fsname only (THIS TEST - for block devices)
3. no fsname -> type string
This test catches a regression where the logic was incorrectly simplified
from nested conditionals to a single combined condition (fsname && !blkdev),
causing case 2 to fall through to case 3 and use 'fuseblk' instead of the
device name.
Requires root to create loop devices; skipped otherwise.
"""
if os.getuid() != 0:
pytest.skip('blkdev option requires root')
import tempfile
# Create a file to use as a loop device
with tempfile.NamedTemporaryFile(mode='wb', delete=False,
dir=str(tmpdir)) as f:
loop_file = f.name
# Create a 1MB file
f.write(b'\0' * (1024 * 1024))
try:
# Create loop device from file
result = subprocess.run(['losetup', '-f', '--show', loop_file],
capture_output=True, text=True, check=True)
loop_dev = result.stdout.strip()
try:
# Mount with blkdev, fsname, and subtype
# The fsname should be the loop device
with hello_mount(tmpdir.mkdir('mnt'), output_checker, name,
('blkdev', f'fsname={loop_dev}', 'subtype=mysub')) as mnt:
info = parse_mountinfo(mnt)
assert info is not None, 'mountpoint not found in /proc/self/mountinfo'
# For block devices, fstype should be 'fuseblk' or 'fuseblk.mysub'
# depending on whether the kernel supports the subtype
assert info['fstype'] in ('fuseblk', 'fuseblk.mysub'), \
f"unexpected fstype for blkdev: {info['fstype']}"
# CRITICAL: Source should be the loop device name (fsname), NOT 'fuseblk'
# This is what the fix ensures - before the fix, it would be 'fuseblk'
# because the incorrect condition treated (fsname && blkdev) as (no fsname)
assert info['source'] == loop_dev, \
f"blkdev source should be fsname ({loop_dev}), got {info['source']}"
finally:
# Detach loop device
subprocess.run(['losetup', '-d', loop_dev], check=False)
finally:
# Clean up file
try:
os.unlink(loop_file)
except OSError:
# Ignore cleanup errors - file may already be deleted or inaccessible
pass
+61 -27
View File
@@ -678,10 +678,6 @@ struct mount_params {
char *subtype; /* Subtype from options */
int blkdev; /* Block device flag */
/* Generated mount parameters */
char *source; /* Mount source string */
char *type; /* Filesystem type string */
/* mtab/utab record content: MS_* flag mirrors + kernel-bound -o opts
* + user=<n> (non-root)
*/
@@ -696,8 +692,6 @@ static void free_mount_params(struct mount_params *mp)
free(mp->optbuf);
free(mp->fsname);
free(mp->subtype);
free(mp->source);
free(mp->type);
free(mp->mtab_opts);
memset(mp, 0, sizeof(*mp));
}
@@ -821,13 +815,6 @@ static int prepare_mount(const char *opts, struct mount_params *mp)
sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
mp->fd, mp->rootmode, getuid(), getgid());
mp->source = fuse_mnt_build_source(mp->fsname, mp->subtype, mp->dev);
mp->type = fuse_mnt_build_type(mp->blkdev, mp->subtype);
if (!mp->type || !mp->source) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
goto err;
}
return 0;
err:
@@ -837,31 +824,60 @@ err:
/*
* Perform the actual mount operation using prepared parameters.
* Builds source and type strings and returns them via output parameters.
*
* Returns 0 on success, -1 on failure.
*/
static int perform_mount(const char *mnt, struct mount_params *mp)
static int perform_mount(const char *mnt, struct mount_params *mp,
char **source_out, char **type_out)
{
int res;
char *source = NULL;
char *type = NULL;
res = mount_notrunc(mp->source, mnt, mp->type, mp->flags, mp->optbuf);
source = fuse_mnt_build_source(mp->fsname, mp->subtype, mp->dev, 0);
type = fuse_mnt_build_type(mp->blkdev, mp->subtype);
if (!type || !source) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
free(source);
free(type);
return -1;
}
res = mount_notrunc(source, mnt, type, mp->flags, mp->optbuf);
if (res == -1 && errno == ENODEV && mp->subtype) {
/* Probably missing subtype support */
strcpy(mp->type, mp->blkdev ? "fuseblk" : "fuse");
free(source);
free(type);
type = fuse_mnt_build_type(mp->blkdev, NULL);
if (mp->fsname) {
if (!mp->blkdev)
sprintf(mp->source, "%s#%s", mp->subtype, mp->fsname);
if (!mp->blkdev) {
source = fuse_mnt_build_source(mp->fsname,
mp->subtype,
mp->dev, 1);
} else {
source = fuse_mnt_build_source(mp->fsname, NULL,
mp->dev, 0);
}
} else {
strcpy(mp->source, mp->type);
source = strdup(type);
}
res = mount_notrunc(mp->source, mnt, mp->type, mp->flags, mp->optbuf);
if (!type || !source) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
free(source);
free(type);
return -1;
}
res = mount_notrunc(source, mnt, type, mp->flags, mp->optbuf);
}
if (res == -1 && errno == EINVAL) {
/* It could be an old version not supporting group_id */
sprintf(mp->optbuf_end, "fd=%i,rootmode=%o,user_id=%u",
mp->fd, mp->rootmode, getuid());
res = mount_notrunc(mp->source, mnt, mp->type, mp->flags, mp->optbuf);
res = mount_notrunc(source, mnt, type, mp->flags, mp->optbuf);
}
if (res == -1) {
int errno_save = errno;
@@ -871,9 +887,13 @@ static int perform_mount(const char *mnt, struct mount_params *mp)
else
fprintf(stderr, "%s: mount failed: %s\n", progname,
strerror(errno_save));
free(source);
free(type);
return -1;
}
*source_out = source;
*type_out = type;
return 0;
}
@@ -883,6 +903,8 @@ static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
{
struct mount_params mp = { .fd = fd }; /* implicit zero of other params */
int res;
char *source = NULL;
char *type = NULL;
mp.rootmode = rootmode;
mp.dev = dev;
@@ -891,14 +913,14 @@ static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
if (res == -1)
return -1;
res = perform_mount(mnt, &mp);
res = perform_mount(mnt, &mp, &source, &type);
if (res == -1) {
free_mount_params(&mp);
return -1;
}
*sourcep = mp.source;
*typep = mp.type;
*sourcep = source;
*typep = type;
*mtab_optsp = mp.mtab_opts;
/* Free only the intermediate allocations, not the returned ones */
@@ -1133,10 +1155,16 @@ static int mount_fuse_finish_fsmount(const char *mnt,
goto fail_free_merged;
}
/* Store results in context */
ctx->source = mp.source;
/* Build source and type for mtab after successful mount */
ctx->source = fuse_mnt_build_source(mp.fsname, mp.subtype, ctx->dev, 0);
*type = fuse_mnt_build_type(mp.blkdev, mp.subtype);
if (!*type || !ctx->source) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
goto fail_free_merged;
}
/* Store mtab_opts in context */
ctx->mtab_opts = final_mtab_opts;
*type = mp.type;
res = 0;
@@ -1218,6 +1246,12 @@ static int mount_fuse(const char *mnt, const char *opts, const char **type)
size_t x_mtab_opts_len = mtab_opts_len +
strlen(x_prefixed_opts) + 2;
char *x_mtab_opts = calloc(1, x_mtab_opts_len);
if (x_mtab_opts == NULL) {
fprintf(stderr,
"%s: failed to allocate memory for mtab_opts\n",
progname);
goto fail_close_fd;
}
if (mtab_opts_len) {
strcpy(x_mtab_opts, mtab_opts);