Repository navigation
Conversation
Container runtimes give a container that runs in a user namespace, such as a Kubernetes pod with "hostUsers: false", access to a host directory through an idmapped mount of it. Since Linux 6.12 FUSE filesystems support idmapped mounts, but only if the server sets FUSE_ALLOW_IDMAP in its FUSE_INIT reply. dfuse does not, so such a pod cannot use a dfuse mount: runc fails with "failed to set MOUNT_ATTR_IDMAP on <path>: invalid argument". Add --allow-idmap, which requests FUSE_CAP_ALLOW_IDMAP. The kernel only accepts it with default_permissions, which dfuse always uses. It is off by default because it changes what the kernel tells dfuse about the caller of a request: for requests that do not create an inode, the uid and gid in the header are FUSE_INVALID_UIDGID, on every mount of the filesystem and not only on idmapped ones. The one place where dfuse needs them for such a request is the check that it only shares its pool and container handles with interception libraries that run as the same user and group. When the kernel does not send them, read the fsuid and fsgid of the calling thread from /proc, as libfuse's fuse_req_getgroups() does for supplementary groups. The caller is blocked in ioctl() until dfuse replies, so its thread id cannot be reused in the meantime. The pid in the request is relative to the pid namespace of the process that mounted the filesystem. That is dfuse, unless dfuse was given the mounted /dev/fuse as /dev/fd/N, and then it cannot tell which namespace it is. So a caller outside of it (pid 0), any caller when dfuse sees the /proc of another pid namespace, and any caller of a /dev/fd/N mount cannot be identified and do not get the handles. As for callers running as another user, libpil4dfs then leaves their I/O to dfuse and libioil opens its own handles. Why a caller is refused is logged at DEBUG level. Without --allow-idmap nothing changes. FUSE_CAP_ALLOW_IDMAP is defined by fuse libraries that support it (libfuse commit 1f4c0608, not in a release yet). With another one, --allow-idmap has no effect and dfuse logs an error, as when the kernel does not support idmapped mounts. Add an NLT test that runs libioil and libpil4dfs against dfuse started with --allow-idmap. On Linux 6.12 and later with a fuse library that supports idmapped mounts, it fails if dfuse does not share its handles: dfuse logs that it refused the caller and libpil4dfs reports no intercepted calls. Otherwise it is a plain interception test, and the log check of NLT ignores that error of dfuse started with --allow-idmap. Document the option. Features: dfuse Signed-off-by: Dmitry Polonsky <ifnotelse@gmail.com>
|
Ticket title is 'dfuse: support idmapped mounts for containers in user namespaces' |
6 tasks
6 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Container runtimes give a container that runs in a user namespace access to host directories through
idmapped mounts (
mount_setattr(MOUNT_ATTR_IDMAP)). Kubernetes does this for every volume of a podwith
hostUsers: false(user namespaces are GA since Kubernetes 1.36).
Linux 6.12 added idmapped mount support for FUSE, but the kernel only allows it when the FUSE
server sets
FUSE_ALLOW_IDMAPin itsFUSE_INITreply(fs/fuse: allow idmapped mounts,
LWN).
dfuse does not set it, so such a pod cannot use a dfuse mount; it fails to start with
Change
--allow-idmapmakes dfuse requestFUSE_CAP_ALLOW_IDMAPindfuse_fuse_init(). The kernel only accepts it together withdefault_permissions, which dfusealways uses. When it is allowed, dfuse logs that at INFO level, like the other capabilities.
When the kernel or the fuse library does not support it, dfuse logs an error, so that it is
seen with the default log mask of clients (ERR): the option was asked for and has no effect.
dfuse keeps running, as without the option.
that do not create an inode, the uid and gid in the request header become
FUSE_INVALID_UIDGID.This applies to every mount of the filesystem, not only to idmapped ones (
fuse_get_req()infs/fuse/dev.c).inode creation. The other uses are in create, mknod and symlink, where the kernel still sends
them, mapped through the idmapping of the mount.
its own uid and gid.
caller_matches()reads the fsuid and fsgid of thecalling thread from
/proc/<pid>/task/<pid>/status. libfuse'sfuse_req_getgroups()alreadydoes this for supplementary groups, which dfuse uses in
_dfuse_mode_update()./dev/fd/Nmount, pid 0 (outside the pidnamespace), a
/procof another pid namespace, an unreadable/procentry, or another uid orgid.
FUSE_CAP_ALLOW_IDMAPexists only in fuse libraries that support it.With another library,
--allow-idmaphas no effect and dfuse logs an error, as it does when thekernel lacks support. The PR therefore does not depend on the fuse update, and its test
also passes with the current fused v1.0.0, as a plain interception test (see Dependencies).
docs/user/filesystem.mddocuments the option, when it is needed, what it changes and what itdoes not: neither who gets the handles nor who may access files. It also says that dfuse should
not run as the overflow user (
nobody) with the option.test_il_allow_idmapruns libioil and libpil4dfs against dfuse started with--allow-idmap. It is non-trivial only on Linux 6.12 or later with a fuse library that hasFUSE_CAP_ALLOW_IDMAP(DAOS-19898 build: Update fused to v1.1.0 with FUSE_CAP_ALLOW_IDMAP #19405): then the kernel does not send the ids, and the test fails ifdfuse does not share its handles: dfuse logs why it refused the caller (the test fails on
such a line in its log), and libpil4dfs, which only intercepts I/O on a dfuse mount with
dfuse's handles, reports no intercepted calls ("op_sum is zero").
plain interception. This is the case on kernels before 6.12 and in CI until DAOS-19898 build: Update fused to v1.1.0 with FUSE_CAP_ALLOW_IDMAP #19405 is merged
(fused v1.0.0). The NLT log check (
log_test(),ignore_no_idmap) then ignores dfuse'serror that the kernel or the fuse library does not support idmapped mounts, only for a
dfuse started with
--allow-idmap, so it does not become a finding.utils/cq/words.dictgets "idmap" and "idmapped", which the pylint spelling check does notknow otherwise.
Security
The ioctl handler replies with handles that carry dfuse's credentials, so the caller check must not
become weaker:
model: who gets the handles and who may access files is unchanged. The ids that the kernel
used to send are read from
/procinstead, and the kernel keeps checking file permissions(
default_permissions).ioctl()until dfuse replies, so the id cannot be reused by another task meanwhile. If the caller is killed,
the reply is discarded by the kernel.
the process that mounted the filesystem. Normally this is dfuse itself, or fusermount3 started by
it.
/procthat belongs to another pid namespace (checked withreadlink("/proc/self") == getpid()), the pid cannot be looked up and every caller is refused./dev/fuseas/dev/fd/N, the process that mounted it may be inanother pid namespace, e.g. a privileged helper that passes the descriptor to dfuse in a
container. dfuse cannot check that, so it refuses every caller. Otherwise a pid of that
namespace could be looked up in dfuse's
/procand match an unrelated process.namespaces, which run as other uids from dfuse's point of view.
EPERM, which the interceptionlibraries already handle: libpil4dfs leaves the I/O to dfuse and libioil opens its own handles
with the caller's credentials.
Limitations
--allow-idmap, they only get dfuse's handlesif they run in dfuse's pid namespace or one nested in it. For example, when dfuse runs in a
container of its own, processes in other containers don't get them. Running dfuse in the pid
namespace of the host avoids this. With a
/dev/fd/Nmount point they never get them. This isdocumented.
/procas the overflow user (65534,nobody). dfuse should therefore not run as that user with--allow-idmap. This is documented._dfuse_mode_update()compares the caller's supplementary groups, readfrom
/procbyfuse_req_getgroups(), with the parent directory's gid.mount, the check may therefore miss a group the process is a member of inside its user
namespace.
FUSE_CREATE_SUPP_GROUPinstead would be a separate change.Dependencies
A fuse library with
FUSE_CAP_ALLOW_IDMAP: libfuse commit1f4c0608,
which is not in a release yet.
The fused version DAOS uses today (v1.0.0, a libfuse snapshot before 3.17) does not have it and
cannot: the flag is bit 32 of the capabilities, which libfuse only has from 3.17 on.
the backport in
deps/patches/fused. With both PRs the option works.to libfuse 3.18.2 and 3.18.3 as a deps patch. It could also go into fused itself.
This PR does not depend on #19405. Without it the option has no effect and dfuse logs an error, so
this PR can be merged first.
Testing
DAOS master 58d0485 with this PR, built with fused v1.1.0 plus the backport as in #19405 (EL9
RPMs,
scons install), 31.10.2026. The client ran against a DAOS 2.8.0 server and agent.test_il_allow_idmappasses, and dfuse's log shows that idmapped mounts were allowed.ioctl.c, it failed with "op_sum is zero".--allow-idmapforced on every dfuse. The server log has the same two warnings both times,from code this PR does not touch: a container open refused with
DER_NO_PERM(
srv_container.c) and an object I/O context without a handle (srv_obj.c).dfuseandvm;without
daos_build_vmandioctl_pool_handles). One server and one client VM, with the clienton Linux 7.2. Each test ran twice: as it is, and with the harness starting every dfuse with
--allow-idmap. Every test gave the same result both times:mu_permstests (dfuse of a regular user in multi-user mode;owner, group and other users with and without the interception libraries) and the 18
variants of
DaosCoreTestDfuse(libioil and libpil4dfs methods, 5 cache modes with andwithout interception, writeback only without).
network:
test_bashfd_pil4dfs: bash 5.1 under libpil4dfs fails withxrealloc: cannot allocate 4294967464 bytes;test_bashcmd_pil4dfs*hits an assertion inpil4dfs/aio.c:78;test_bashcmd*tests stop at acurlof a host on the CI network, after all theirfile system steps have passed.
dfuse_test(the program behindDaosCoreTestDfuse) was also run on its own, on AlmaLinux9.7 with Linux 5.14 and 7.2. It covered 4 cache modes without an interception library, and the
3 modes other than writeback (as in ftest) with libioil, with libpil4dfs taking dfuse's handles,
and with libpil4dfs as ftest runs it. dfuse ran as a user on both kernels and as root on 7.2, with
and without the option. All tests pass.
--allow-idmap, and anidmapped mount of it is made with
mount_setattr(), mapping 0-65535 to 100000-165535.EINVAL.stored as 1000:1000, and uid 101000 gets
EACCESin a 0:0 0755 directory. chown, mkdir andrename work.
namespace (libpil4dfs from a nested pid namespace: the Kubernetes pod below). Another user
never gets them.
/proc, callers outside or inside thatnamespace are refused.
/dev/fuseas/dev/fd/Nby a helper that mounted it, idmappedmounts work, and libioil and libpil4dfs of the dfuse user are refused the handles. Without
--allow-idmapthey get them, as before. Without the/dev/fd/Ncheck they got them with--allow-idmaptoo.--allow-idmapstarts and logs "Kernel does not supportidmapped mounts". Everything behaves as without the option.
daos_agentand dfuse run as systemd services.fusermount3), with that user's interceptionlibraries;
daos filesystem queryandevict,daos container query --path;/proc/<pid>/task/<tid>.The kernel does not support idmapped FUSE mounts.
--userns=autouse the dfuse mount as an:idmapvolume. Files of container root and uid 1000 are stored as 0:0 and 1000:1000. Without
--allow-idmap, crun fails withmount_setattr ... Invalid argument. Four containers atonce, each with its own uid range, see each other's files with these owners, and permissions
hold between them.
(Linux 6.18). dfuse runs in mount pods with
hostPID: true(a CSI driver):hostUsers: false, as root and as uid 1000, use a volume ofdfuse --allow-idmap.Their files are stored with the uid and gid they have in the pod.
Permissions are enforced (uid 1000 cannot write root's 0644 file), and chown works.
intercepts I/O with dfuse's handles. In a user-namespaced pod it leaves the I/O to dfuse.
libioil there opens its own handles.
hostUsers: falseon a volume of dfuse without--allow-idmapfails tostart with the
MOUNT_ATTR_IDMAPerror.subPathandreadOnlymounts;one without a user namespace: only the latter's get dfuse's handles.
--allow-idmapand logs "Fuse library does not support idmapped mounts";idmapped mounts are refused as without the option, and the kernel keeps sending the caller
ids, so the interception libraries get dfuse's handles as before.
test_il_allow_idmapprints that idmapped mounts are not allowed..github/workflows/linting.ymlare clean on the changed lines:clang-format (repository style), flake8, pylint (
utils/cq/daos_pylint.pywith pyenchant andhunspell en_US, so its spelling check ran: no new warning), codespell and the copyright check.
The copyright check requires the HPE line added to
ioctl.c:check_update_copyright.shadds itfor authors outside HPE, Intel, Google and Enakta. Tell me if it should read differently.
Features: dfuse
Steps for the author:
After all prior steps are complete: