Skip to content

libct: switch to new mount api - #5378

Open
lifubang wants to merge 7 commits into
opencontainers:mainfrom
lifubang:feat-new-mount-api
Open

libct: switch to new mount api #5378
lifubang wants to merge 7 commits into
opencontainers:mainfrom
lifubang:feat-new-mount-api

Conversation

@lifubang

@lifubang lifubang commented Jul 21, 2026

Copy link
Copy Markdown
Member

Since the new mount API, such as open_tree, fsopen, fsmount, and move_mount, has matured over the years, it's time to embrace it fully.

This comment was marked as outdated.

This comment was marked as outdated.

This comment was marked as outdated.

This comment was marked as outdated.

This comment was marked as outdated.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

libcontainer/rootfs_linux.go:1366

  • sharedMaskSrc is always created as mountSourcePlain from sharedMaskFile, but when maskDir() returns a non-nil fd it is a detached mount fd (open_tree/fsmount). Detached mount fds cannot be used as a bind-mount source via mount(2) (/proc/.../fd/N), so later mountViaFds(..., sharedMaskSrc, ..., MS_BIND, ...) can fail or bind the wrong thing. To keep shared masking working, always reopen the mounted directory (reopenAfterMount) to get an O_PATH handle suitable for mountSourcePlain, and close any detached mount fd returned by maskDir().
					sharedMaskSrc = &mountSource{Type: mountSourcePlain, file: sharedMaskFile}

Comment on lines +367 to +371
fd, err := unix.OpenTree(-int(unix.EBADF), "/", unix.OPEN_TREE_CLOEXEC)
if err != nil {
return false
}
_ = unix.Close(fd)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is copied from github.com/cyphar/filepath-securejoin:
https://github.com/cyphar/filepath-securejoin/blob/main/pathrs-lite/internal/linux/mount_linux.go#L23-L47

@kolyshkin kolyshkin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the first commit, I'd like to add the following into the commit message (assuming it's true).

No functional change, just moving the code around.

Comment thread libcontainer/rootfs_linux.go Outdated
Comment on lines +721 to +725
func checkProcMount(rootfs, dest string, m mountEntry) error {
func checkProcMount(rootfs, dest string, m *mountEntry) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for this change? Just unification?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's not necessary to make this change, so reverted it now.

Comment thread libcontainer/rootfs_linux.go Outdated
Comment thread libcontainer/rootfs_linux.go
@lifubang
lifubang force-pushed the feat-new-mount-api branch 7 times, most recently from 0024b4b to 69bb33b Compare July 26, 2026 05:12
@lifubang

Copy link
Copy Markdown
Member Author

Add a commit libct: always ignore mount label for bind mounts to align the legacy mount(2) behavior with the new mount API for bind mounts.

// file descriptor fsfd using fsconfig(2). The options are expected to be a
// comma-separated list of key=value pairs or flags.
func fsconfigApplyOptions(fsfd int, opts string) error {
fields := strings.SplitSeq(opts, ",")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On another note, since unix.FsconfigSet(String/Flag) requires individual calls, we need to re-parse back the value of m.Data. It might be better to pass the original array directly instead of joining it here:
https://github.com/opencontainers/runc/blob/main/libcontainer/specconv/spec_linux.go#L1181-L1185

However, configs.Mount is widely used by other projects, so changing m.Data from string to []string might break compatibility.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, what if a value contains a comma?

E.g. context="system_u:object_r:container_file_t:s0:c344,c805" or lowerdir=dir1\,dir2

@lifubang
lifubang force-pushed the feat-new-mount-api branch from 69bb33b to 48ada7b Compare July 30, 2026 12:24
@lifubang

Copy link
Copy Markdown
Member Author

@opencontainers/runc-maintainers PTAL, Any chance we can get this into 1.6.0?

@lifubang lifubang added this to the 1.6.0-rc.1 milestone Jul 30, 2026
mount_entry has become complex and requires further refactoring.
Extract it into a separate file to improve modularity and maintainability.

No functional change, just moving the code around.

Signed-off-by: lifubang <lifubang@acmcoder.com>
This change avoids duplicate fd closing and lays the groundwork for migrating
to the new mount API by centralizing resource cleanup logic.

Signed-off-by: lifubang <lifubang@acmcoder.com>
Adds a regression test to guarantee mount labels are respected
for all supported mount types.

Signed-off-by: lifubang <lifubang@acmcoder.com>
As documented in mount(2): if MS_BIND is included in mountflags,
the filesystem type and mount data arguments are ignored.
Therefore, keep mountLabel empty for bind mounts.
Note: mount data is also blocked in the config validator:
https://github.com/opencontainers/runc/blob/main/libcontainer/configs/validate/validator.go#L373-L375

Signed-off-by: lifubang <lifubang@acmcoder.com>
Signed-off-by: lifubang <lifubang@acmcoder.com>
Signed-off-by: lifubang <lifubang@acmcoder.com>
Signed-off-by: lifubang <lifubang@acmcoder.com>

@kolyshkin kolyshkin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A general note: maybe we need a way to

  • enforce the fsmount path (i.e. exclude fallback);
  • enforce the fallback path (i.e. exclude fsmount);

in CI so we can definitely check both paths. Perhaps an environment variable or some such. Otherwise we won't be able to consistently test both paths.

}
if bindFailed || sharedMaskSrc == nil {
err = maskDir(path, mountLabel)
fd, err := maskDir(dstFh, path, mountLabel)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a real bug. This new err declaration shadows the one which the maskPaths returns.

In other words, non-nil err here is never used / returned.

Can be fixed by e.g.:

var fd *os.File
fd, err = maskDir(...)

or perhaps some refactoring

Comment on lines -363 to +364
return mountViaFds(m.Source, nil, m.Destination, dstFd, "cgroup2", uintptr(m.Flags), m.Data)
})
// We can't set mount label on cgroup2 mounts, so we just pass an empty string here.
err := m.mountPropagate(c.root, "")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a real bug here. The old code used to pass "cgroup2" to mountViaFds explicitly.

The new code calls mountViaFds from m.mountPropagate and uses m.Device which is "cgroup" not "cgroup2", so the fall back won't work.

Most probably we can't see this in CI due to recent distros/kernels.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most probably we can't see this in CI due to recent distros/kernels.

Which leads to a question: are we at all able to actually check the fallbacks?

// file descriptor fsfd using fsconfig(2). The options are expected to be a
// comma-separated list of key=value pairs or flags.
func fsconfigApplyOptions(fsfd int, opts string) error {
fields := strings.SplitSeq(opts, ",")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, what if a value contains a comma?

E.g. context="system_u:object_r:container_file_t:s0:c344,c805" or lowerdir=dir1\,dir2

pflags |= unix.AT_RECURSIVE
}
// Only include the propagation type, not MS_REC
propagation |= uint64(pflag & ^unix.MS_REC)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When reading this code it seems that propagation is a mask of various values (since we have a for loop here). In fact, it's just a single value (one of MS_PRIVATE, MS_SLAVE, MS_SHARED, MS_UNBINDABLE) but for some reason we have m.PropagationFlags as a slice.

NOTE this is not the issue with the new code, but rather the issue with the existing code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So for example if m.PropagationFlags is shared,rslave the old code will apply shared then rslave and the new one will call mount_setattr with ropagation set to MS_SHARED|MS_SLAVE which will result in a debug-warning and a fallback. Maybe we should only take the last value from the slice? Or, even better, deny PropagationFlags that have more than one element (as it does not make sense).

Comment on lines 456 to +457
if flags&unix.MS_LAZYTIME != 0 {
logrus.Warnf("MS_LAZYTIME mount flag specified, but kernel does not support MOUNT_ATTR_LAZYTIME")
logrus.Warnf("MS_LAZYTIME mount flag specified, but kernel does not support MOUNT_ATTR_LAZYTIME; ignoring")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. So here we're ignoring MS_LAZYTIME but in mountToRootfs (case "bind") we fall back to the old implementation if MS_LAZYTIME is set. I think we should be consistent and stick to either ignoring or falling back.

  2. There are also MS_SYNCHRONOUS, MS_DIRSYNC, MS_MANDLOCK, and MS_SILENT which we ignore as there's no mapping to MOUNT_ATTR_*. From the list, MS_MANDLOCK seems to be deprecated but others are not. What should we do about those?


// createDetachedBindMount creates a detached bind mount using open_tree(2).
func createDetachedBindMount(m *configs.Mount) (_ *mountSource, retErr error) {
openTreeFlags := uint(unix.OPEN_TREE_CLONE | unix.OPEN_TREE_CLOEXEC)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we are now using OPEN_TREE_CLONE for every maskDir call. OTOH the mountFd docstring says

        // Ideally, we would use OPEN_TREE_CLONE for everything, because we can
        // be sure that the file descriptor cannot be used to escape outside of
        // the mount root. Unfortunately, OPEN_TREE_CLONE is far more expensive
        // than open(2) because it requires doing mounts inside a new anonymous
        // mount namespace. So we use open(2) for standard bind-mounts, and
        // OPEN_TREE_CLONE when we need to set mount attributes here.

Does this mean we are slower now than we used to be?

Comment on lines +351 to +354
// hasNewMountAPI reports whether the running kernel supports the new mount API
// (open_tree(2), move_mount(2), fsopen(2), fsmount(2)), which was introduced
// in Linux 5.2.
var hasNewMountAPI = sync.OnceValue(func() bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps makes sense to mention this is a copy paste from cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go

@kolyshkin

Copy link
Copy Markdown
Contributor

@lifubang added some comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/refactor refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants