-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
nsexec: spring cleaning #3953
nsexec: spring cleaning #3953
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
- name: procfs mount | ||
run: | | ||
# Get the list of mounts to help with debugging. | ||
cat /proc/self/mounts | ||
# Create a procfs mount that is not masked, to ensure that container | ||
# procfs mounts will succeed. | ||
sudo mkdir -p /tmp/.procfs-stashed-mount | ||
sudo unshare -pf mount -t proc -o subset=pid proc /tmp/.procfs-stashed-mount |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know why this has become necessary with this PR (the "problem" commit is the /proc/self/exe
cloning change) but this solves the issue and this is a CI-weirdness issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cyphar Does that mean that runc won't work as-is inside "your usual" GHA, or is it only for the sake of testing?
@adrianreber @Snorch I figured out what the underlying issue is, the nested mount is being leaked to the host when using We also really need a separate integration test for the leaking behaviour. The fact that only this criu test fails for this fairly broken behaviour (and doesn't fail on newer kernels) is quite concerning. |
Hello, @cyphar
I would say, there is no. If criu lacks some new mount API (move_mount(..._SET_GROUP) and openat2(RESOLVE_NO_XDEV)) it switches to "old" mount engine which uses only old mount API. Long story short: We have two mount engines in latest CRIU, "new" and "old" and there is no big difference between them in simple cases. The "new" is though more precise so it can fail in some cases where "old" just silently did something wrong, at the same time "new" supports complex cases related to propagation so in some cases where "old" would fail or do something wrong "new" will succeed =) But I would not say that there is some general rule that "old" can't restore (or badly restore) mounts created with new mount API. Only thing we can do is investigate each case.
Not sure that I fully understand it, but from context it looks like you talk about issue in runc, just detected on CRIU test.
Just in case, "new" mount engine of CRIU does not support if root mount of container is shared (only slave is supported) to mount outside of the container (e.g. from which it is created, i.e. --root criu option), after CRIU c/r root mount of the container would become separate sharing group if it was shared outside before. |
libcontainer/container_linux.go
Outdated
if err := unix.MountSetattr(int(mountFile.Fd()), "", unix.AT_EMPTY_PATH|setattrFlags, &unix.MountAttr{ | ||
Propagation: uint64(propFlags &^ unix.MS_REC), | ||
}); err != nil { | ||
return fmt.Errorf("remap mount sources: failed to set mount propagation of %q bind-mount to 0x%x: %w", m.Source, propFlags, err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be nice to say which syscall is failing too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for it! It permits to remove a lot of code and your implementation of ID map mounts without user ns is more flexible than mine.
I will take a deeper look later, but for now here are some questions:
// NOTE: when running a container with no PID namespace and the parent process spawning the container is | ||
// PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason | ||
// even with the parent still running. | ||
// Due to a Go stdlib bug, we need to add c.safeExeFile to the set of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am curious, can you please share more information regarding this bug?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is needed to make sure that our userns tests work in GitHub Actions if the host /proc is masked. Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
Otherwise TESTFLAGS="-run FooBar" will result in TESTFLAGS=-run being executed in the container. Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
This includes quite a few cleanups and improvements to the way we do syncrhonisation. The core behaviour is unchanged, but switching to embedding json.RawMessage into the synchronisation structure will allow us to do more complicated synchronisation operations in future patches. The file descriptor passing through the syncrhonisation system feature will be used as part of the idmapped-mount and bind-mount-source features when switching that code to use the new mount API outside of nsexec.c. Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
*os.File is correctly tracked by the garbage collector, and there's no need to use raw file descriptors for this code. Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
The kernel ignores these arguments, and passing them can lead to confusing error messages (the old source is irrelevant for MS_REMOUNT), as well as causing issues for a future patch where we switch to move_mount(2). Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
The original implementation of cgroupns had additional synchronisation to "ensure" that the process is in the correct cgroup before unsharing the cgroupns. This behaviour was actually never necessary, and after commit 5110bd2 ("nsenter: remove cgroupns sync mechanism") there is no synchronisation at all, meaning that CLONE_NEWCGROUP should not get any special treatment. Fixes: 5110bd2 ("nsenter: remove cgroupns sync mechanism") Fixes: df3fa11 ("Add support for cgroup namespace") Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
This allow us to remove the amount of C code in runc quite substantially, as well as removing a whole execve(2) from the nsexec path because we no longer spawn "runc init" only to re-exec "runc init" after doing the clone. Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
In the runc state JSON we always use snake_case. This is a no-op change, but it will cause any existing container state files to be incorrectly parsed. Luckily, commit fbf183c ("Add uid and gid mappings to mounts") has never been in a runc release so we can change this before a 1.2.z release. Fixes: fbf183c ("Add uid and gid mappings to mounts") Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
With open_tree(OPEN_TREE_CLONE), it is possible to implement both the id-mapped mounts and bind-mount source file descriptor logic entirely in Go without requiring any complicated handling from nsexec. This allows us to remove the amount of C code we have in nsexec, as well as simplifying a whole host of places that were made more complicated with the addition of id-mapped mounts and the bind sourcefd logic. The one downside of this is that the bind sourcefd feature now depends on Linux 5.4. In addition, we can easily add support for id-mappings that don't match the container's user namespace. The approach taken here is to use Go's officially supported mechanism for spawning a process in a user namespace, but (ab)use PTRACE_TRACEME to avoid actually having to exec a different process. The most efficient way to implement this would be to do clone() in cgo directly to run a function that just does kill(getpid(), SIGSTOP) -- we can always switch to that if it turns out this approach is too slow. This also includes a partial fix for a bug in the handling of idmap mounts and mount propagation. In short, the issue is that because we do OPEN_TREE_CLONE on the host, the RootPropagation flag does not apply (nor any other mount propagation flags configured in config.json) and thus recursive bind-mounts can and will be leaked to the host. Because this patch switches the feature from 9c44407 ("Open bind mount sources from the host userns") to use OPEN_TREE_CLONE, this resulted in all bind-mounts having this behaviour. The partial fix here is to try to emulate the behaviour of RootPropagation for bind-mounts from the host. It turns out that bind-mounts inside containers were broken when using the fd-passing feature from 9c44407 ("Open bind mount sources from the host userns") because the file descriptor opens were done before we start doing any mounts in rootfs_linux.go. The solution for this is more involved and is fixed in a separate patch. At the very least, this patch doesn't worsen the mount propagation situation. Fixes: fda12ab ("Support idmap mounts on volumes") Fixes: 9c44407 ("Open bind mount sources from the host userns") Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
With the rework of nsexec.c to handle MOUNT_ATTR_IDMAP in our Go code we can now handle arbitrary mappings without issue, so remove the primary artificial limit of mappings (must use the same mapping as the container's userns) and add some tests. We still only support idmap mounts for bind-mounts because configuring mappings for other filesystems would require switching our entire mount machinery to the new mount API. The current design would easily allow for this but we would need to convert new mount options entirely to the fsopen/fsconfig/fsmount API. This can be done in the future. Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
I spent some time rebasing this PR and almost succeeded, except for idmap.bats changes. One thing that helped me when applying commit 18ce9de ("iidmap: allow arbitrary idmap mounts regardless of userns configuration") is using |
libcontainer/init_linux.go
Outdated
// syncParentSeccomp sends the fd associated with the seccomp file descriptor | ||
// to the parent, and wait for the parent to do pidfd_getfd() to grab a copy. | ||
func syncParentSeccomp(pipe *os.File, seccompFd int) error { | ||
if seccompFd >= 0 { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
I suggest we split this one into more digestible PRs. Ideally, each medium-sized feature should be a separate PR. I rebased the more-or-less trivial stuff from this PR to #3982, which I hope we can merge soon. |
libcontainer/init_linux.go
Outdated
// syncParentSeccomp sends the fd associated with the seccomp file descriptor | ||
// to the parent, and wait for the parent to do pidfd_getfd() to grab a copy. | ||
func syncParentSeccomp(pipe *os.File, seccompFd int) error { | ||
if seccompFd >= 0 { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
// We have a copy, the child can keep working. We don't need to | ||
// wait for the seccomp notify listener to get the fd before we | ||
// permit the child to continue because the child will happily wait | ||
// for the listener if it hits SCMP_ACT_NOTIFY. | ||
if err := writeSync(p.messageSockPair.parent, procSeccompDone); err != nil { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This includes several cleanups to
nsexec.c
:nsexec.c
with all of the complicated rewriting and opening magic to just usingopen_tree()
in the Go portion of runc and passing the file descriptors down torootfs_linux.go
to just do a simplemove_mount(2)
. This also allows us to easily implement handling of different id-mappings for mounts and is thus a replacement for Support for ID map mounts without userns #3943.CLONE_NEWCGROUP
that was arguably never necessary and definitely isn't necessary now.sane_kill
error path handling by usingprctl(PR_SET_PDEATHSIG, SIGKILL)
to auto-kill our runc C code if the parent runc process dies. We clear pdeathsig once setup is complete.execve
by directly executing the copy when we spawnrunc init
.This includes several major steps towards the (maybe possible) goal of #3951.
Closes #3943