With drop_privileges, mount.fuse3 execs the file system binary only
after dropping all capabilities (CAP_DAC_OVERRIDE included) while
keeping uid 0. The kernel then applies plain DAC checks to the exec, so
a binary under a directory the capability-less process cannot traverse
(e.g. a user-owned mode-0700 home) fails with EACCES and /bin/sh prints
a bare 'Permission denied', surfacing in the tests as 'file system
process terminated prematurely'.
Resolving and opening the binary before the drop was rejected: it would
perform a privileged path resolution and open() on invoker-influenced
input (confused-deputy / privilege-escalation risk). The drop-first,
resolve-after order is preserved.
Instead, pre-flight the upcoming PATH lookup after the drop and fail
with a diagnostic that names the binary and points at mount.fuse3(8).
The check runs with the final credentials, so it is deny-only. The test
detects the same situation up front (uid/gid-aware traversal check) and
skips with a precise reason; root-owned CI checkouts keep running.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Replace bare except clauses with 'except Exception' to avoid
catching system-exiting exceptions like KeyboardInterrupt and
SystemExit, which should not be suppressed.
Fixes: GitHub CodeQL Alerts #15, #16, #17
Rule: py/catch-base-exception
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Use 'with' statements to ensure files are properly closed even
when exceptions are raised during pytest.raises() contexts.
Fixes: GitHub CodeQL Alerts #18, #19
Rule: py/file-not-closed
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Move subprocess.check_output() and proc.wait() calls out of assert
statements to avoid side effects being optimized away when running
with Python optimization enabled.
Fixes: GitHub CodeQL Alerts #22-27
Rule: py/side-effect-in-assert
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
test: Make short_tmpdir respect pytest --basetemp
When running libfuse tests in a QEMU-based vmtest environment,
test_passthrough fails with a PermissionError because the guest
restricts operations under /tmp:
FAILED test/test_examples.py::test_passthrough[False-passthrough-False]
PermissionError: [Errno 1] Operation not permitted:
'/tmp/tmp4veumkm0/mnt/tmp/tmp4veumkm0/src/testfile_16'
The short_tmpdir fixture used the custom raii_tmpdir wrapper which
calls tempfile.mkdtemp() and always places directories under /tmp,
ignoring pytest's --basetemp option. Since --basetemp is used to
relocate temporary directories to a writable location in QEMU guests,
the fixture prevents the workaround from taking effect.
Fix it by making raii_tmpdir accept an optional dir argument and
having short_tmpdir check whether --basetemp was passed. When
--basetemp is set, create the temporary directory under its resolved
path obtained via tmp_path_factory.getbasetemp(). Without --basetemp,
fall back to the original behavior to keep paths short enough for
AF_UNIX sun_path limits (104 bytes on FreeBSD/macOS, 108 on Linux).
Signed-off-by: KaFai Wan <kafai.wan@linux.dev>
* 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
The ENODEV fallback logic in both lib/mount.c and util/fusermount.c
incorrectly combined two conditions, causing block devices with fsname
to be treated as if they had no fsname.
Original behavior (before commit 50fa7f0bc2):
if (mo->fsname) {
if (!mo->blkdev)
sprintf(source, "%s#%s", mo->subtype, mo->fsname);
// else: keep existing source (fsname for blkdev)
} else {
strcpy(source, type);
}
This handled three cases:
1. fsname + non-blkdev -> "subtype#fsname"
2. fsname + blkdev -> fsname (unchanged)
3. no fsname -> type
Previous patches incorrectly changed this to:
if (mo->fsname && !mo->blkdev) {
source = fuse_mnt_build_source()
} else {
source = strdup(type);
}
This only handled two cases, causing case 2 (fsname + blkdev) to be
treated as case 3 (no fsname).
This patch restores the three-way logic.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
This is especially for the new mount API, but does not hurt
either for the traditional API.
Assisted by Claude Opus 4.7
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
Wire the pytest suite into meson as a single test() so it can be run
via 'meson test -C build' (or 'meson test -C build pytest -v') instead
of having to invoke pytest directly. The same definition works for
both unprivileged and root runs -- invoke 'meson test' as your user,
or 'sudo -E meson test' to exercise the tests that need root (these
detect the uid at runtime via os.geteuid()).
Assisted by Claude/Opus 4.7
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
Allow installation of the mount service helper as a setuid program so
that regular users can access containerized filesystem drivers.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
Valgrind complains that it does not know the fsmount syscall,
pytest checks for warnings, find that and fails the test
--14936-- WARNING: unhandled amd64-linux syscall: 430
--14936-- You may be able to write your own handler.
--14936-- Read the file README_MISSING_SYSCALL_OR_IOCTL.
--14936-- Nevertheless we consider this a bug. Please report
--14936-- it at http://valgrind.org/support/bug_reports.html.
fuse: fsopen(fuse) failed: Function not implemented
=========================== short test summary info
Obviously we want to filter our valgrind warnings.
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
Existing example/ file systems do the fuse_daemonize() after
fuse_session_mount() - i.e. after the mount point is already
established. Though, these example/ daemons do not start
extra threads and do not need network initialization either.
fuse_daemonize() also does not allow to return notification
from the forked child to the parent.
Complex fuse file system daemons often want the order of
1) fork - parent watches, child does the work
Child:
2) start extra threads and system initialization (like network
connection and RDMA memory registration) from the fork child.
3) Start the fuse session after everything else succeeded
Parent:
Report child initialization success or failure
A new API is introduced to overcome the limitations of
fuse_daemonize()
fuse_daemonize_start() - fork, but foreground process does not
terminate yet and watches the background.
fuse_daemonize_success() / fuse_daemonize_fail() - background
daemon signals to the foreground process success or failure.
fuse_daemonize_active() - helper function for the high level
interface, which needs to handle both APIs. fuse_daemonize()
is called within fuse_main(), which now needs to know if the caller
actually already used the new API itself.
The object 'struct fuse_daemonize *' is allocated dynamically
and stored a global variable in fuse_daemonize.c, because
- high level fuse_main_real_versioned() needs to know
if already daemonized
- high level daemons do not have access to struct fuse_session
- FUSE_SYNC_INIT in later commits can only be done if the new
API (or a file system internal) daemonization is used.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
It is very hard to see what exactly fails, as everything
is run from the same script.
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
Acked-by: "Darrick J. Wong" <djwong@kernel.org>
Commit 30a126c5 ("API update for fuse_loop_config additions") introduced
fuse_loop_cfg_convert() to translate legacy fuse_loop_config_v1 structs
to the new v2 API, but only converted max_idle_threads — it did not set
max_threads.
In the v1 API, max_idle_threads was the effective pool cap: the thread
pool would grow on demand and threads exceeding max_idle_threads were
destroyed after processing each request. A filesystem that configured
max_idle_threads=100 expected up to 100 threads to be available.
After the conversion, max_threads retains its default of 10, silently
capping the pool at 10 workers regardless of max_idle_threads. A busy
filesystem with all 10 threads processing long-running requests has no
thread available to receive the next kernel request, causing it to hang.
Fix fuse_loop_cfg_convert() to call fuse_loop_cfg_set_max_threads() with
the same value as max_idle_threads before calling fuse_loop_cfg_set_idle_threads(),
preserving the original v1 semantics.
Also add warnings to both setters when max_idle_threads and max_threads
are set in an inconsistent order (max_idle > max_threads). Reorder the
two setter calls in helper.c so that max_threads is set before
max_idle_threads, preventing a spurious warning when both are supplied as
consistent command-line options. Add a unit test to document and verify
the correct behaviour.
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
- Fix printf format specifiers for unsigned int (%u instead of %i/%d)
- Add const to st parameters in st_check_type and st_check_mode
- Reduce scope of res and fd variables
- Add const to de variable pointer
- Remove redundant err check after early exit
Fixes cppcheck warnings:
- cppcheck-invalidPrintfArgType_sint (3 instances)
- cppcheck-constParameterPointer (2 instances)
- cppcheck-variableScope (2 instances)
- cppcheck-constVariablePointer
- cppcheck-identicalConditionAfterEarlyExit
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Check if mountpoint is NULL before calling rmdir() in the error
path. This fixes CodeChecker warning about use of NULL 'mountpoint'
where non-null expected.
Reported by: CodeChecker (gcc-null-argument)
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
The pytest backtrace is not very interesting, we want the actual
failure message of the test that failed.
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
Without the POLLNVAL fix:
bschubert2@imesrv6 build-ubuntu>./test/test_teardown_watchdog
Testing teardown watchdog feature
Running test: unmount triggers callback
Entering FUSE loop, unmount in 1 second
Unmounting session
fuse_session_loop_mt returned 0
session exited: 0
Waiting for timeout callback...
Timeout callback invoked
Test PASSED: timeout callback was invoked
All tests PASSED
With the additional fix:
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
With 'umount -f' or
'echo 1 > /sys/fs/fuse/connections/NNN/abort'
libfuse might not receive FUSE_DESTROY and the
daemon might not know that it needs to exit.
Example: passthrough_hp might be blocked on the underlying file system
because that has some issues. The mount point would get stuck and
the user might call "umount -f" on the fuse mount point. Kernel
would then abort the connection, but libfuse/fuse-server would
still not exit and not even know that kernel had aborted the connection,
because FUSE_DESTROY might not arrive on kernel connection abort
anymore.
The new optional teardown watchdog polls on the fuse_session fd
(/dev/fuse) and starts a timer when the connection aborted.
When the timeout expires the watchdog thread exits, which will
terminate the entire program.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
Under certain conditions, libfuse was yielding a zero d_ino from
high-level filesystems. This caused a number of bugs as other
software mis-handled these entries. To fix, ensure that direntries
stored in a fuse_dh always have either FUSE_UNKNOWN_INO or an
intentionally-set st_ino.
This bug was triggered if all the following conditions were met:
* High-level FS is readdirplus-capable, and does not set use_ino
or readdir_ino.
* FS does not use offsets in readdir.
* FS passes to the dir filler the FUSE_FILL_DIR_PLUS flag, and a
non-NULL struct stat with st_ino == 0.
* A directory is large enough to need multiple readdir calls.
* Adaptive readdirplus causes a readdirplus to be followed by a
regular readdir.
When this occurred, the fuse_dh was filled with entries with
st_ino == 0. On the initial readdirplus we were calling do_lookup()
to convert these to synthetic inode numbers, but on the subsequent
regular readdirs we were returning the zero inode numbers verbatim.
Historically, d_ino == 0 indicated that a direntry should be skipped.
Several tools have treated it this way, including Glibc before 2022
(or 2024 for readdir64_r), and current versions of Go. This has caused
a number of bugs:
* https://github.com/libfuse/libfuse/issues/1338
* https://github.com/golang/go/issues/76428
* https://github.com/restic/restic/pull/5607
* https://gitlab.gnome.org/World/deja-dup/-/issues/623
When libfuse receives st_ino == 0 in readdir, we should therefore
treat it as the FS having no opinion about the inode number. We should
only truly trust that it wants a zero inode if use_ino or readdir_ino
is true.
In addition to the fix, this commit adds a mode to passthrough to
return st_ino == 0 from readdir, and uses that to test libfuse's
behavior in test_examples.py.
Signed-off-by: Dave Vasilevsky <dave@vasilevsky.ca>
Recheck if target is mounted after the mount process exited, otherwise the
test can fail erronously.
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
This commit adds libfuse support for FUSE_STATX requests on
linux distributions.
Currently, statx is only supported on linux. To make the interface a
ergonomic as possible (eg using native 'struct statx' vs 'struct
fuse_statx'), this implementation gates the 'struct statx' changes
by #ifdef linux.
Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
This patch modifies the notify_inval_entry.c example so that it includes
an extra option to use fuse_lowlevel_notify_increment_epoch(). The test
test_notify_inval_entry() was also modified to test this extra option.
Signed-off-by: Luis Henriques <luis@igalia.com>
The sigterm test triggers debug output like
dev unique: 6, opcode: STATFS (17), nodeid: 1, insize: 40, pid: 1808
unique: 6, success, outsize: 96
dev unique: 8, opcode: GETATTR (3), nodeid: 1, insize: 56, pid: 1808
exit_handler called with sig 15
fuse: session exited, terminating workers
unique: 8, error: -2 (No such file or directory), outsize: 16
Which then triggers test failures, because the test was acting
on the word 'error'.
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
fuse_apply_conn_info_opts() was applying to 'want_ext',
which would cause conflicts with 'want' if the application
applied its own flags to 'conn->want'.
Solution is:
- to move fuse_{set,unset,get}_feature_flag and
convert_to_conn_want_ext() to fuse_lowlevel.c and
to define them as part of the public API, although
convert_to_conn_want_ext() should not be used - it is
currently needed to be a public function due as it needs
to be defined for the tests.
Related to https://github.com/libfuse/libfuse/issues/1171 and
https://github.com/libfuse/libfuse/pull/1172.
Closes: https://github.com/libfuse/libfuse/issues/1171
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
We want to especially test with and without io-uring being
enabled. Ideally without modifying all tests - that is
what the env variable can be used for.
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
The test had multiple issues
- default passthrough_ll timeout was used - the created
file was then not listed if timeout was not passed
yet
- mnt_name was actually point to src_dir, file comparison
of stat, etc succeeded, because the same file was compared.
Switching to the right dir made stat to always fail,
because st_dev is different for source and mount.
I.e. the test must not compare all stat values.
Not sure how this test ever passed, but in a very slow
debug VM with lots of kernel debug options enabled,
the default passthrough_ll timeout it systematically
failed.
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
Issue with previous code was that fuse_session_exit()
didn't wake up the semaphore in fuse_loop_mt.c.
Lock, semaphore and all uses of checking for "exited"
are now moved to struct fuse_session to have it
available for the signal handler.
This also removes internal fuse_session_reset() calls,
as that makes testing hard. From git history I also
don't see why it was added.
Closes: https://github.com/libfuse/libfuse/issues/997
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
The high-level interface triggers flag conversion twice: once in the
high-level init and once in the low-level init. This caused false
"both 'want' and 'want_ext' are set" errors when using
fuse_set_feature_flag() or fuse_unset_feature_flag().
The existing check for duplicate conversion only worked when 32-bit
flags were set directly. When using the preferred flag manipulation
functions, conn->want and the lower 32 bits of conn->want_ext
would differ, triggering the error.
Fix this by synchronizing conn->want with the lower 32 bits of
conn->want_ext after conversion, ensuring consistent state for
subsequent calls.
Closes: https://github.com/libfuse/libfuse/issues/1171
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
32-bit conn->want flags been left to be ABI compatible to 3.10,
even though the so version was changed.
The more recent way is to use fuse_set_feature_flag(),
which will use conn->want_ext.
Given that we now have two flags (want and want_ext), we need
to convert and that brought several issues
- If the application sets conn->want, that needs to be set into
the lower 32 bit of conn->want_ext. As the application might
actually unset values, it really has to be a copy and not
just 'or' - fixed now.
- convert_to_conn_want_ext() actually needs to check for
_modified_ conn->want and conn->want_ext
- convert_to_conn_want_ext() must consider being called from
high and lowlevel interfact, with different want_ext_default
and want_default values. It is only a failure, if the application
changed both, conn->want and conn->want_ext. This function
was failing in issue #1171, because high level fuse_fs_init()
was changing values and then lowlevel do_init() was incorrectly
failing on that.
This also adds a new test (test_want_conversion) and sets
values into example/{hello.c,hello_ll.c}
Also some more internal users of conn->want are converted to
fuse_{set,unset}_feature_flag().
Closes: https://github.com/libfuse/libfuse/issues/1171
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>