A claimed transform was recorded but never actually left unapplied:
every parsed transformer in a mount's chain was applied in full
regardless of what the caller had claimed, so a shim advertising it
performs "mkdir" in "format/mkdir/overlay" never got the chance to,
since the manager silently applied it anyway. This predates the shim
capability protocol — it was reachable before through the "format/*"
annotation convention — but this series turns it into a documented,
supported way for a shim to claim a transform, so a shim relying on it
(at least one is: Kata's EROFS snapshotter shim, per its annotation
usage) would find its claim silently ignored.
A claimed transform can only be honored as a suffix of the chain: each
transform's input is the previous one's output, so an outer, unclaimed
transform must still be applied even when an inner one is claimed,
since the inner one cannot run without it. planActivation now tracks,
per mount, how much of its chain — starting from the beginning — the
manager must apply itself: through the last unclaimed transform, which
by construction covers every transform before it too. Applying only
that prefix and returning the true, contiguous claimed suffix to the
caller is what actually makes the claim meaningful. This is only
observable for mounts[firstSystemMount], the one mount partially
transformed by the manager and returned to the caller to finish; every
earlier mount is fully internal to the manager and was, and remains,
always fully transformed regardless of any claim.
While changing this expression, also guard it against the case where
firstSystemMount reaches len(mounts) — every mount consumed inside the
manager, with no system mount left to transform — which indexes out of
range today. This is a distinct, pre-existing latent panic (for
example, reachable via WithTemporary combined with a transformed mount
type), tracked separately from the claim-handling fix above; guarding
it here only prevents a crash newly reachable by touching this line,
it does not address the mount.ActivationInfo.System non-empty
invariant that scenario also violates.
Signed-off-by: Derek McGowan <derek@mcg.dev>
Activate's planning phase — deciding, for each mount, which transforms
the manager must apply and whether the manager or the caller performs
the mount itself — was inline in a bolt transaction spanning hundreds
of lines, and every existing test of it goes through the full Activate
path, which needs root to mount anything for real.
Extract it into planActivation, a pure function of the mount list,
activation options, and registered transforms/handlers. No behavior
change; this is what makes the next commit's fix testable without
root.
Signed-off-by: Derek McGowan <derek@mcg.dev>
A caller that applies a mount transform itself had to say so by naming the
mount type "format/*", a sentinel the mount manager constructs internally
by concatenating a transform name with "/*". Both sides had to
independently know and agree on that convention, and the resulting string
is neither a mount type nor a transform name.
Add AllowTransforms, and WithAllowTransform to set it, so a transform can
be claimed by its own name instead. The "/*" convention is removed rather
than kept as a second accepted spelling: it existed only to be produced by
the runtime-allow-mounts annotation, which is removed in an earlier commit
in this series, and there is no other producer of it in this tree.
Signed-off-by: Derek McGowan <derek@mcg.dev>
Replace the snapshots.ErrAlreadyStaged sentinel error with a
read-only-mounts signal: a snapshotter (e.g. erofs serving a layer
content cache hit) now returns Prepare mounts normally, with no
error, when the layer content is already staged into the active
snapshot. The unpacker's isStaged helper checks the last mount's
ReadOnly() to decide whether to skip fetch+apply and just commit.
Signed-off-by: Maksym Pavlenko <pavlenko.maksym@gmail.com>
Because selinux contexts can contain commas, context strings may be
quoted in the single-syscall mount API. This quoting is not permitted
when using the fsconfig API, so strip the quotes when preparing the
system call arguments.
Signed-off-by: Daniel De Graaf <dgdegra@uwe.nsa.gov>
Correctly handle cases where the mount activation still exists:
- If activation is fully activate, then just return already exists and
allow the caller to return error or call Info to continue.
- If activation is stale or incomplete due to crash during activation,
overwrite the identifier and cleanup the incomplete activation during
activate.
Signed-off-by: Derek McGowan <derek@mcg.dev>
Kernel 6.12.80+ returns 'fsync=volatile' instead of just 'volatile'
in mount options, which breaks containerd's exact string matching
checks.
Fixes this issue by adding support for 'fsync=volatile' in addition
to the existing 'volatile' check in RemoveVolatileOption and
addVolatileOptionOnImageVolumeMount.
Assisted-by: Antigravity
Signed-off-by: Chris Henzie <chrishenzie@gmail.com>
The X-containerd.* prefix is reserved for containerd-internal mount
options that are processed by containerd components (such as mount
transformers and handlers). These options must not be passed to the
kernel mount syscall.
This change adds filtering at the core mount layer
as a safety net to prevent kernel errors if components forget to clean
up their options.
Signed-off-by: Aadhar Agarwal <aadagarwal@microsoft.com>
Mounts a tmpfs with MS_NOEXEC, MS_NOATIME, and MS_NODIRATIME and
verifies that getUnprivilegedMountFlags detects all of them. These
three flags were the ones missed by the range-over-indices bug.
Also verifies that flags not present on the mount (MS_NOSUID,
MS_NODEV, MS_RDONLY) are not falsely reported.
Signed-off-by: Luke Hinds <luke@stacklok.com>
The loop `for flag := range unprivilegedFlags` iterates over slice
indices (0,1,2,3,4,5,6) rather than the actual flag values (MS_RDONLY,
MS_NODEV, etc). This was a porting error from moby/moby where the data
structure was a map (where `for k := range m` yields keys/values).
As a result, MS_NOEXEC, MS_NOATIME, MS_RELATIME, and MS_NODIRATIME are
never detected or preserved. In user namespaces, this causes bind-mount
remounts to fail with EPERM when any of these flags are locked on the
parent mount, because the kernel requires all CL_UNPRIVILEGED locked
flags to be preserved during remount.
MS_RDONLY (0x1), MS_NOSUID (0x2), and MS_NODEV (0x4) happened to work
by coincidence because their values equal low index numbers.
Fix by using `for _, flag := range` to iterate over values.
Signed-off-by: Luke Hinds <luke@stacklok.com>
Recursive is needed to catch mounts under the bind mount, for example
erofs mount points. i.e. with that in place things like:
/foo/erofs1
/foo/erofs2
bind mount + idmap /foo/ -> /foo-idmapped/
makes it so erofs1 and erofs2 are at /foo-idmapped/, allowing us to use
that when constructing the final overlay mount erofs snapshotters use.
This let's the snapshotter claim to support idmap support later without
issue (right now it goes through remapfs).
As part of this, we want to make the bind mount have private mount
propagation. If you umount /foo-idmapped/erofs1 it shouldn't affect
/foo/erofs1, which could be reused in another container's lowerdirs and
idmapped bind mount.
Signed-off-by: Andrew Halaney <ahalaney@netflix.com>
containerd will do a read only mount of the mount string to inspect
things like /etc/passwd for a username to uid mapping. It doesn't need
to do a uidmap/gidmap for this, that's entirely for the actual container
mount later.
Let's stop doing the idmap here, its extra work and not necessary.
Further, it causes complications when the mount manager is involved.
Mount manager will by default create erofs layers at
/run/containerd/io.containerd.mount-manager.v1.bolt/t/8103/1, and the
snapshotter's upperdir is at (if configured to use a different path)
/mnt/containerd/io.containerd.snapshotter.v1.erofs/snapshots/25795/fs.
During the read only mount this upperdir is shoved into the lowerdirs
list, and then the idmap code tries to find the common parent directory of
all lowerdirs. In this case its "/", which is invalid, so the process
fails.
Let's stop doing the extra work and get away from this class of
problems by doing less.
Signed-off-by: Andrew Halaney <ahalaney@netflix.com>
The bind mount created for temporary activations was missing the
Options field, causing mount to fail with "no such device" because
the MS_BIND flag wasn't being set.
Fixes#12549
Signed-off-by: Jérôme Poulin <jeromepoulin@gmail.com>
Add tests for the WithTemporary mount activation used by `ctr images mount`.
Covers bind mount and overlay scenarios to catch regressions like #12549.
Signed-off-by: Jérôme Poulin <jeromepoulin@gmail.com>
In CI we run make root-test via gotestsum, which executes multiple
package tests concurrently. TestAutoclearTrueLoop attempts to invoke
LOOP_CLR_FD using a device name, which introduces a race condition.
Example race:
Process P1 represents mount.test which runs TestAutoclearTrueLoop
Process P2 represents manager.test which runs TestLoopbackMount
T1: P1 closes fd of loop-device (loop3) (kernel unsets backing-file on close)
T2: P2 gets loop3 from /dev/loop-control
T3: P2 configures loop3 with backing file successfully
T4: P1 invokes removeLoop to clear backing file for loop3
You might see that failure like this
```
=== FAIL: core/mount/manager TestLoopbackMount (0.05s)
log_hook.go:47: time="2025-10-23T21:49:22.532811960Z" level=debug msg="activating mount" func="manager.(*mountManager).Activate" file="/home/runner/work/containerd/containerd/core/mount/manager/manager.go:134" mounts="[{loop /tmp/TestLoopbackMount989607109/001/fs-1621892597 []} {format/ext4 {{ mount 0 }} []}]" name=id1 testcase=TestLoopbackMount
helpers.go💯 unmount /tmp/TestLoopbackMount989607109/001/test-mount-3030342351
manager_linux_test.go:80:
Error Trace: /home/runner/work/containerd/containerd/core/mount/manager/manager_linux_test.go:80
/home/runner/work/containerd/containerd/core/mount/manager/manager_linux_test.go:105
Error: Received unexpected error:
failed to get loop device info: no such device or address
Test: TestLoopbackMount
```
To fix this, the test now compares backing-file's inode directly and does
not call removeLoop when autoclear is set.
Signed-off-by: Wei Fu <fuweid89@gmail.com>
Commit ee8ae9d569 ("Update erofs snapshotter to use mount manager")
temporarily removed the file-backed mount feature to adapt to the new
mount manager infrastructure as a quick start.
After the mount manager was introduced, a specific mount type can be
handled with a mount handler plugin to provide a dedicated mount
process (e.g. setup loopback devices in advance or calling external
mount helpers).
This commit adds a default EROFS mount handler for the Linux hosts
to set up loop devices for mount sources and "device=" external file
blobs if necessary (i.e. when file-backed mounts are unavailable),
allowing common runtimes such as runC to work directly, e.g.
``` sh
mount -t erofs /var/lib/containerd/io.containerd.snapshotter.v1.erofs/snapshots/1/layer.erofs \
/run/containerd/io.containerd.mount-manager.v1.bolt/t/346/1
```
will be handled as
``` sh
mount -t erofs /dev/loop1 /run/containerd/io.containerd.mount-manager.v1.bolt/t/346/1
```
and
``` sh
mount -t erofs /var/lib/containerd/io.containerd.snapshotter.v1.erofs/snapshots/7/fsmeta.erofs \
-odevice=/var/lib/containerd/io.containerd.snapshotter.v1.erofs/snapshots/1/layer.erofs,\
device=/var/lib/containerd/io.containerd.snapshotter.v1.erofs/snapshots/2/layer.erofs,\
...
device=/var/lib/containerd/io.containerd.snapshotter.v1.erofs/snapshots/7/layer.erofs
/run/containerd/io.containerd.mount-manager.v1.bolt/t/335/1
```
will be handled as
``` sh
mount -t erofs /dev/loop1 -odevice=/dev/loop2,device=/dev/loop3,... \
/run/containerd/io.containerd.mount-manager.v1.bolt/t/335/1
```
if file-backed mounts are unavailable.
For other host platforms (e.g. Darwin hosts) or specific runtimes
that require EROFS raw mounts instead of parsed mounts, this plugin
can be explicitly masked off by users.
Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
Rename `default_size_mb` to `default_size` so that it's similar to
Docker's `--storage-opt size=30G` flag. For example,
``` toml
[plugins."io.containerd.snapshotter.v1.erofs"]
default_size = "5GB"
```
Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
The mount manager GC seems to be broken on my local setup.
According to commit df87a8f71b ("Add support for backreferences
in gc"), the interface name should be "ActiveWithBackRefs" instead.
After this patch, erofs and ext4 mounts won't be GCed in advance.
Fixes: 184fae60fc ("Add backreference support to mount manager")
Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
Reduce the size of the test files. Even using sparse files, the reported
large size may cause issues in some test environments.
Signed-off-by: Derek McGowan <derek@mcg.dev>
Adding a close method allows the mount manager to close any open file
descriptors. The method will also be called automatically by containerd
on shutdown.
Ensure the tests call Close to avoid leaking file descriptors or errors
on Windows cleaning up directories that are in use.
Signed-off-by: Derek McGowan <derek@mcg.dev>
Extend the mount manager to support more transformers than format. The
transformers allow altering the mount before it is passed to the mount
handlers. These could be one-time actions which are needed to perform
the mount.
Adds mkdir and mkfs actions which can be used to prepare the arguments
for a mount. The actions can be limited to actions within the target
mount directory or plugin directories.
Signed-off-by: Derek McGowan <derek@mcg.dev>
Allow the mount manager to skip handling of custom types. Ensure that
custom types are still working with formatted mounts.
Signed-off-by: Derek McGowan <derek@mcg.dev>
The autoclear may take a bit of time to clear out the file, check
multiple times for the file to get removed before returning an error.
Signed-off-by: Derek McGowan <derek@mcg.dev>
Avoid keeping file descriptor open to directory which is getting
removed. Update error handling and wrapping to provide more clarity
around failures.
Signed-off-by: Derek McGowan <derek@mcg.dev>