Close the root file descriptor immediately after use in maskPaths to
reduce the window during which an attacker could potentially exploit
an open fd to access or manipulate the root filesystem. This follows
the principle of least privilege and mitigates risks in compromised
or malicious container scenarios.
Co-authored-by: Kir Kolyshkin <kolyshkin@gmail.com>
Signed-off-by: lifubang <lifubang@acmcoder.com>
(cherry picked from commit b88635e57e)
Signed-off-by: lifubang <lifubang@acmcoder.com>
This is a follow-up to #5275. That change reused a single tmpfs mount
to mask multiple directories, which is efficient when masking more than
one path. However, it introduced unnecessary overhead when only one
directory is masked. This commit restores the original behavior for the
single-path case while preserving shared tmpfs logic for multiple paths.
Signed-off-by: lifubang <lifubang@acmcoder.com>
(cherry picked from commit e7e2f00248)
Signed-off-by: lifubang <lifubang@acmcoder.com>
Kubernetes may add one sysfs thermal_throttle entry per CPU to
maskedPaths. On large Intel systems this can produce many directory
masks for a single container. runc currently handles each directory
mask with a separate read-only tmpfs mount, and therefore a separate
tmpfs superblock.
On Linux 4.18/RHEL 8 kernels, creating and tearing down many tmpfs
superblocks can contend on the global shrinker_rwsem when containers
start or stop concurrently.
Use one read-only tmpfs for directory masks and bind-mount it over the
remaining directory targets. The first non-procfs-fd directory mount is
reopened through the container root fd before it is reused. File masks
still bind /dev/null, and procfs fd targets keep the existing
one-tmpfs-per-target behaviour because they are fd aliases rather than
stable rootfs paths.
If the bind-mount of the shared source fails (e.g. due to kernel
restrictions), fall back to individual tmpfs mounts for all remaining
directories. Tmpfs mounts use nr_blocks=1,nr_inodes=1 to minimise
kernel resource usage.
The bind mounts do not create additional tmpfs superblocks. They also
retain the read-only mount flag inherited from the source vfsmount, so
the masking semantics remain unchanged.
xref: kubernetes/kubernetes#138512
xref: kubernetes/kubernetes#138388
xref: kubernetes/kubernetes#131018
Co-authored-by: Davanum Srinivas <davanum@gmail.com>
Refactored-by: lifubang <lifubang@acmcoder.com>
Signed-off-by: lifubang <lifubang@acmcoder.com>
(cherry picked from commit c046c9b973)
Signed-off-by: lifubang <lifubang@acmcoder.com>
Previously, masked directories (e.g., /proc/acpi, /proc/scsi) were
mounted as read-only tmpfs without explicit size or inode limits.
Although these mounts are meant to be empty and unwritable, the lack
of resource constraints means that—should an attacker bypass the
read-only protection (e.g., via container escape, mount namespace
manipulation, or a kernel vulnerability)—the tmpfs could consume up
to 50% of system memory by default (the kernel's default tmpfs limit).
To mitigate this risk in high-density container environments and
adhere to the principle of least privilege, we now explicitly set:
- nr_blocks=1 (sufficient for at most one block size)
- nr_inodes=1 (sufficient for at most one inode)
Ref: https://man7.org/linux/man-pages/man5/tmpfs.5.html
These limits ensure that even if compromised, kernel memory usage
remains strictly bounded and negligible.
This change aligns with best practices used by other container
runtimes and strengthens defense-in-depth for sensitive masked paths.
Co-authored-by: Davanum Srinivas <davanum@gmail.com>
Refactored-by: lifubang <lifubang@acmcoder.com>
Signed-off-by: lifubang <lifubang@acmcoder.com>
(cherry picked from commit e57a7a4c8f)
Signed-off-by: lifubang <lifubang@acmcoder.com>
Apparently, lima's experimental/fedora-rawhide image does not include
which rpm, and we don't really want to bother installing it.
Replace "which" with "command -v". Looks like this was the only place;
we already use "command -v" everywhere else.
This should fix lima (experimental/fedora-rawhide) CI.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
(cherry picked from commit 5e78f4a66d)
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
The latter is simpler and provides just enough functionality to be used
here.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
(cherry picked from commit 9970cbfdb6)
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
By default, readlink is silent about any errors. Make it verbose so we
can better interpret any test failures.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
(cherry picked from commit 54be90bf68)
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
When running a process inside a container, make sure its stderr is not
nil (except for some trivial cases like cat). Modify waitProcess to show
failed command's stderr, if possible.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
(cherry picked from commit bf4fcc3002)
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Since Wait returns an ExitError if process' exit status is not 0,
checking process status is redundant and this code is never reached.
Remove it.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
(cherry picked from commit dd9fda7d60)
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
When RUNC_USE_SYSTEMD is set, tests/rootless.sh is using
ssh -tt rootless@localhost
to run tests as rootless user. In this case, local environment is not
passed to the user's ssh session (unless explicitly specified), and so
the tests do not get ROOTLESS_FEATURES.
As a result, idmap-related tests are skipped when running as rootless
using systemd cgroup driver:
integration test (systemd driver)
...
[02] run rootless tests ... (idmap)
...
ok 286 runc run detached ({u,g}id != 0) # skip test requires rootless_idmap
...
Fix this by creating a list of environment variables needed by the
tests, and adding those to ssh command line (in case of ssh) or
exporting (in case of sudo) so both cases work similarly.
Also, modify disable_idmap to unset variables set in enable_idmap so
they are not exported at all if idmap is not in features.
Fixes: bf15cc99 ("cgroup v2: support rootless systemd")
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
(cherry picked from commit 3e0829d195)
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Those tests were added by commit 8d180e96 ("Add support for Linux
Network Devices"), apparently by copy-pasting the test cases which
call simple_cr (all four of them).
While different simple_cr tests make sense as they cover different
code paths in runc and/or check for various regression, the same
variations with netdevice do not make sense, as having a net device
is orthogonal to e.g. bind mount, --debug, or cgroupns.
Remove those.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
(cherry picked from commit 2cd4782b70)
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
These helpers all make more sense as a self-contained package and moving
them has the added benefit of removing an unneeded libpathrs dependency
(from libcontainer/utils's import of pathrs-lite) from several test
binaries.
Signed-off-by: Aleksa Sarai <aleksa@amutable.com>
(cherry picked from commit ca509e76ff)
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This allows users to automaticaly migrate to the new location
using `go fix`. It has some limitations, but can help smoothen
the transition; for example, taking this file;
```
package main
import (
"github.com/opencontainers/runc/libcontainer/devices"
)
func main() {
_, _ = devices.DeviceFromPath("a", "b")
_, _ = devices.HostDevices()
_, _ = devices.GetDevices("a")
}
```
Running `go fix -mod=readonly ./...` will migrate the code;
```
package main
import (
devices0 "github.com/moby/sys/devices"
)
func main() {
_, _ = devices0.DeviceFromPath("a", "b")
_, _ = devices0.HostDevices()
_, _ = devices0.GetDevices("a")
}
```
updates b345c78dca
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit ba83c7c7d7)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
The libcontainer/devices package has been moved to moby/sys/devices, so
we can just point users to that and keep some compatibility shims around
until runc 1.6. We don't use it at all so there are no other changes
needed.
Signed-off-by: Aleksa Sarai <aleksa@amutable.com>
(cherry picked from commit b345c78dca)
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
When signal installation was moved to a goroutine for performance,
containers that exited quickly could complete before SIGCHLD was
registered, causing runc to hang waiting for the signal.
This fix ensures SIGCHLD is registered immediately in the main thread
before other signals are handled in the goroutine, maintaining performance
while guaranteeing no missed SIGCHLD notifications for fast-exiting
containers.
Reported-by: Ayato Tokubi <atokubi@redhat.com>
Signed-off-by: lifubang <lifubang@acmcoder.com>
(cherry picked from commit 404181e4cc)
Signed-off-by: lifubang <lifubang@acmcoder.com>
Since switching to Go 1.25 in go.mod, the "detect fd leaks" test fails
like this:
> not ok 57 runc create[detect fd leak as comprehensively as possible]
> # (in test file tests/integration/create.bats, line 76)
> # `[ "$violation_found" -eq 0 ]' failed
> ...
> # Violation: FD 9 -> '/system.slice/runc-test_busybox.scope/cpu.cfs_quota_us'
> # Violation: FD 10 -> '/system.slice/runc-test_busybox.scope/cpu.cfs_period_us'
> ...
This happens because Go 1.25 adds a feature to dynamically set GOMAXPROC
based on current CPU quota values. This feature can be disabled by setting
GODEBUG=containermaxprocs=0,updatemaxprocs=0
but it is harmless to keep it (except for the above test failure).
Add an exception to the test case.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
(cherry picked from commit f9a9a36fa8)
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Kir Kolyshkin (3):
Pre-open container root directory
libct: minor refactor in mountToRootfs
libct: mountCgroupV1: address TODO
Li Fubang (1):
libct: use preopened rootfs more
LGTMs: lifubang cyphar
A lot of filesystem-related stuff happens inside the container root
directory, and we have used its name before. It makes sense to pre-open
it and use a *os.File handle instead.
Function names in internal/pathrs are kept as is for simplicity (and it
is an internal package), but they now accept root as *os.File.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
(cherry picked from commit 28cb321887)
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
No change in functionality, just a preparation for the next patch.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
(cherry picked from commit 78b80677f6)
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Indeed, it does not make sense to prepend c.root once we started using
MkdirAllInRoot in commit 63c29081.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
(cherry picked from commit 60352524d3)
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This commit factors out setupAndMountToRootfs without changing any
logic. Use "Hide whitespace changes" during review to focus on the
actual changes.
The refactor ensures the mount source file descriptor is closed via
defer in each loop iteration, reducing the total number of open FDs
in runc. This helps avoid hitting the file descriptor limit under
high concurrency or when handling many mounts.
Signed-off-by: lifubang <lifubang@acmcoder.com>
(cherry picked from commit c77e71a3e7)
Signed-off-by: lifubang <lifubang@acmcoder.com>