Conversation
37c7d9b to
1b8e363
Compare
| defer closer() | ||
| defer procSelfFd.Close() | ||
| for i := baseFd; i < baseFd+r.preserveFDs; i++ { | ||
| err := unix.Faccessat(int(procSelfFd.Fd()), strconv.Itoa(i), unix.F_OK, 0) |
There was a problem hiding this comment.
Since Go runtime and runc itself opens all FDs with CLOEXEC, we can just replace unix.Faccessat to unix.Fctnl(F_GETFD) and check there's no CLOEXEC flag, and error out otherwise (basically the check you added to checkPreserveFDs can be done right here).
This will simplify the logic a lot (no cli parsing, no NumFiles etc) without any downsides.
There was a problem hiding this comment.
I mean, the fix itself (modulo tests) can be as simple as this:
--- a/utils_linux.go
+++ b/utils_linux.go
@@ -15,7 +15,6 @@ import (
"github.com/urfave/cli/v3"
"golang.org/x/sys/unix"
- "github.com/opencontainers/runc/internal/pathrs"
"github.com/opencontainers/runc/internal/third_party/systemd/activation"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/configs"
@@ -245,16 +244,15 @@ func (r *runner) run(config *specs.Process) (_ int, retErr error) {
process.ExtraFiles = append(process.ExtraFiles, r.listenFDs...)
}
baseFd := 3 + len(process.ExtraFiles)
- procSelfFd, closer, err := pathrs.ProcThreadSelfOpen("fd/", unix.O_DIRECTORY|unix.O_CLOEXEC)
- if err != nil {
- return -1, err
- }
- defer closer()
- defer procSelfFd.Close()
for i := baseFd; i < baseFd+r.preserveFDs; i++ {
- err := unix.Faccessat(int(procSelfFd.Fd()), strconv.Itoa(i), unix.F_OK, 0)
- if err != nil {
- return -1, fmt.Errorf("unable to stat preserved-fd %d (of %d): %w", i-baseFd, r.preserveFDs, err)
+ // Check that the fd was really inherited from runc's caller. Merely
+ // checking that the fd is open is not sufficient, as the fd number
+ // could have been reused by runc itself (or the Go runtime). Any such
+ // fd has the close-on-exec flag set, while an inherited one can not
+ // have it, as it would have been closed by execve.
+ flags, err := unix.FcntlInt(uintptr(i), unix.F_GETFD, 0)
+ if err != nil || flags&unix.FD_CLOEXEC != 0 {
+ return -1, fmt.Errorf("preserved-fd %d (of %d) was not passed to runc", i-baseFd, r.preserveFDs)
}
process.ExtraFiles = append(process.ExtraFiles, os.NewFile(uintptr(i), "PreserveFD:"+strconv.Itoa(i)))
}
(note I simplified the error message as well)
There was a problem hiding this comment.
I see no way that should fail, as long as all those FDs really always set CLOEXEC; now and forever AND this check continues to happen both before execve AND before joining the container namespaces e.g. during runc run --preserve-fds=X (otherwise we'd have a race). At present that appears to be the case.
So if that is how it's done (i.e. CLOEXEC is always set), I agree the extra safety of checking early does not outweigh the added complexity.
I'll push an updated version.
| for i := baseFd; i < baseFd+preserveFDs; i++ { | ||
| flags, err := unix.FcntlInt(uintptr(i), unix.F_GETFD, 0) | ||
| if err != nil { | ||
| return fmt.Errorf("unable to stat preserved-fd %d (of %d): %w", i-baseFd, preserveFDs, err) |
There was a problem hiding this comment.
nit: this is fcntl not stat
1b8e363 to
8bc15df
Compare
kolyshkin
left a comment
There was a problem hiding this comment.
looks like integration tests fail. Also, can you add Co-authored-by: Kir Kolyshkin <kolyshkin@gmail.com>?
The --preserve-fds option can be used to preserve file descriptors and pass them into the container. There is a check that should verify if those file-descriptors are actually open, but its results are tainted by file descriptors opened by Go code after execve and before the check runs. Currently, running runc with --preserve-fds set to 2 whilst not actually passing any descriptors reproducibly leaks runc-internal file descriptors into the container. Instead, an error message would be more helpful in this situation. This patch moves the validation logic to a point before any file descriptors related to cgroups-handling or similar are opened by runc. Closes: opencontainers#5474 Signed-off-by: Marcus Gelderie <Marcus.Gelderie@hs-aalen.de> Co-authored-by: Kir Kolyshkin <kolyshkin@gmail.com>
8bc15df to
ae6631b
Compare
|
Yes, I somehow cannot get the integration tests to run locally. They always fail, even on From what I can see in the runner results, the failures are due to the changed position of the check:
I have pushed another version in the hopes that fixes it. For now I cannot spend more time on getting those tests to run locally. If this patch does not resolve the issue, I'll revisit. |
The --preserve-fds option can be used to preserve file descriptors and pass them into the container. There is a check that should verify if those file-descriptors are actually open, but its results are tainted by file descriptors opened by Go code after execve and before the check runs. This is also mentioned in #5474.
Currently, running runc with --preserve-fds set to 2 whilst not actually passing any descriptors reproducibly leaks runc-internal file descriptors into the container. Instead, an error message would be more helpful in this situation.
This patch moves the validation logic to a point before any file descriptors related to cgroups-handling or similar are opened by runc.
Closes: #5474