Commit Graph

7709 Commits

Author SHA1 Message Date
Aleksa Sarai de87203e62 console: verify /dev/pts/ptmx before use
This is primarily done out of an abudance of caution against runc exec
being attacked by a container where /dev/pts/ptmx has been replaced with
some other bad inode (a disconnected NFS handle, a symlink that goes
through a leaked runc file descriptor to reference a host ptmx, etc).

Unfortunately, we cannot trivially verify that /dev/pts/ptmx is actually
the /dev/pts from the container without storing stuff like the fsid in
the runc state.json, which is probably not worth the extra effort. This
should at least avoid the most concerning cases.

Reported-by: Aleksa Sarai <cyphar@cyphar.com>
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
2025-11-01 21:24:03 +11:00
Aleksa Sarai 9be1dbf4ac console: avoid trivial symlink attacks for /dev/console
An attacker could make /dev/console a symlink. This presents two
possible issues:

 1. os.Create will happily truncate targets, which could have resulted
    in a worse version of CVE-2024-4531. Luckily, this all happens after
    pivot_root(2) so the scope of that particular attack is fairly
    limited (you are unlikely to be able to easily access host rootfs
    files -- though it might be possible to take advantage of leaks such
    as in CVE-2024-21626). However, O_CREAT|O_NOFOLLOW is what we should
    be doing for all file creations.

 2. Because we passed /dev/console as the only mount path (as opposed to
    using a /proc/self/fd/$n path), an attacker could swap the symlink
    to point to any other path and thus cause us to mount over some
    other path. This is not as big of a problem because all the mounts
    are in the container namespace after pivot_root(2), and users
    usually can create arbitrary mount targets inside the container.

These issues don't seem particularly exploitable, but they deserve to be
hardened regardless.

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
2025-11-01 21:24:03 +11:00
Aleksa Sarai 398955bccb console: add fallback for pre-TIOCGPTPEER kernels
The pty driver has very consistent allocation rules for the major:minor
numbers of /dev/pts/$n inodes, so it is possible to somewhat safely open
/dev/pts/* paths if we validate that the inode is the one we expect.

It is possible for an attacker to have over-mounted a pts peer from a
different devpts instance, but to fix this would require more tracking
of devpts instances than runc currently can do.

This means runc should continue to work on very old kernels.

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
2025-11-01 21:24:03 +11:00
Aleksa Sarai 531ef794e4 console: use TIOCGPTPEER when allocating peer PTY
When opening the peer end of a pty, the old kernel API required us to
open /dev/pts/$num inside the container (at least since we fixed console
handling many years ago in commit 244c9fc426 ("*: console rewrite")).

The problem is that in a hostile container it is possible for
/dev/pts/$num to be an attacker-controlled symlink that runc can be
tricked into resolving when doing bind-mounts. This allows the attacker
to (among other things) persist /proc/... entries that are later masked
by runc, allowing an attacker to escape through the kernel.core_pattern
sysctl (/proc/sys/kernel/core_pattern). This is the original issue
reported by Lei Wang and Li Fu Bang in CVE-2025-52565.

However, it should be noted that this is not entirely a newly-discovered
problem. Way back in Linux 4.13 (2017), I added the TIOCGPTPEER ioctl,
which allows us to get a pty peer without touching the /dev/pts inside
the container. The original threat model was around an attacker
replacing /dev/pts/$n or /dev/pts/ptmx with some malicious inode (a DoS
inode, or possibly a PTY they wanted a confused deputy to operate on).
Unfortunately, there was no practical way for runc to cache a safe
O_PATH handle to /dev/pts/ptmx (unlike other runtimes like LXC, which
switched to TIOCGPTPEER way back in 2017). Since it wasn't clear how we
could protect against the main attack TIOCGPTPEER was meant to protect
against, we never switched to it (even though I implemented it
specifically to harden container runtimes).

Unfortunately, It turns out that mount *sources* are a threat we didn't
fully consider. Since TIOCGPTPEER already solves this problem entirely
for us in a race free way, we should just use that. In a later patch, we
will add some hardening for /dev/pts/$num opening to maintain support
for very old kernels (Linux 4.13 is very old at this point, but RHEL 7
is still kicking and is stuck on Linux 3.10).

Fixes: GHSA-qw9x-cqr3-wc7r CVE-2025-52565
Reported-by: Lei Wang <ssst0n3@gmail.com> (CVE-2025-52565)
Reported-by: lfbzhm <lifubang@acmcoder.com> (CVE-2025-52565)
Reported-by: Aleksa Sarai <cyphar@cyphar.com> (TIOCGPTPEER)
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
2025-11-01 21:24:03 +11:00
Kir Kolyshkin 5d7b242407 libct: maskPaths: don't rely on ENOTDIR for mount
Currently, we rely on mount returning ENOTDIR when the destination is a
directory (and so mount tells us that the source is not), and fall back
to read-only tmpfs bind mount for such cases.

Theoretically, ENOTDIR can also be returned in some other cases,
resulting in the wrong type of mount being used.

Let's be more straightforward here -- call fstat on destination file
descriptor, and use the proper mount depending on whether it is a
directory.

Reported-by: Rodrigo Campos <rodrigoca@microsoft.com>
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-11-01 21:24:02 +11:00
Kir Kolyshkin 1a30a8f3d9 libct: maskPaths: only ignore ENOENT on mount dest
When mounting a path being masked, the /dev/null might disappear from
under us, and mount (even on an opened /dev/null file descriptor) will
return ENOENT, which we deliberately ignore, as there's no need to mask
non-existent paths.

Let's open the destination path and ignore ENOENT during open, then
mount via the destination file descriptor, not ignoring ENOENT.

Reported-by: lifubang <lifubang@acmcoder.com>
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-11-01 21:24:02 +11:00
Kir Kolyshkin 8476df83b5 libct: add/use isDevNull, verifyDevNull
The /dev/null in a container should not be trusted, because when /dev
is a bind mount, /dev/null is not created by runc itself.

1. Add isDevNull which checks the fd minor/major and device type,
   and verifyDevNull which does the stat and the check.

2. Rewrite maskPath to open and check /dev/null, and use its fd to
   perform mounts. Move the loop over the MaskPaths into the function,
   and rename it to maskPaths.

3. reOpenDevNull: use verifyDevNull and isDevNull.

4. fixStdioPermissions: use isDevNull instead of stat.

Fixes: GHSA-9493-h29p-rfm2 CVE-2025-31133
Co-authored-by: Rodrigo Campos <rodrigoca@microsoft.com>
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
2025-11-01 21:24:02 +11:00
Aleksa Sarai ff94f9991b *: switch to safer securejoin.Reopen
filepath-securejoin v0.3 gave us a much safer re-open primitive, we
should use it to avoid any theoretical attacks. Rather than using it
direcly, add a small pathrs wrapper to make libpathrs migrations in the
future easier...

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
2025-11-01 21:24:02 +11:00
Aleksa Sarai 6fc1914491 internal: move utils.MkdirAllInRoot to internal/pathrs
We will have more wrappers around filepath-securejoin, and so move them
to their own specific package so that we can eventually use libpathrs
fairly cleanly (by swapping out the implementation).

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
2025-11-01 21:24:02 +11:00
Aleksa Sarai db19bbed53 internal/sys: add VerifyInode helper
This will be used for a few security patches in later patches in this
patchset. The need to verify what kind of inode we are operating on in a
race-free way turns out to be quite a common pattern...

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
2025-11-01 21:24:01 +11:00
Kir Kolyshkin fb01482d12 Merge pull request #4951 from kolyshkin/better-nsenter-errors
libct/nsenter: improve error reporting
2025-10-29 09:54:49 -07:00
Kir Kolyshkin 6c18b25cdc libct/nsenter: better read/write errors
Introduce and use iobail, xread, and xwrite wrappers so that we can
properly check read/write return value and call either bail or bailx on
error, with proper diagnostics (distinguishing failed read/write from a
short read/write).

This prevents the "Success" prefix in errors like:

	failed to sync with stage-1: next state: Success

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-28 17:26:09 -07:00
Kir Kolyshkin aea52d0ab0 libct/nsenter: sprinkle missing sane_kill
Add a few missing sane_kill calls where they make sense.

Remove one useless sane_kill of stage2_pid, as during SYNC_USERMAP stage2
is not yet started. It is harmless yet it makes the code slightly harder
to read.

Set the child pid to -1 upon receiving SYNC_CHILD_FINISH
to minimize the chances of killing an unrelated process.
When a child sends SYNC_CHILD_FINISH it is about to exit
(although theoretically it could be stuck during debug logging).

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-28 17:21:38 -07:00
Kir Kolyshkin 067b8335e7 libct/nsenter: add and use bailx
We use bail to report fatal errors, and bail always append %m
(aka strerror(errno)). In case an error condition did not set
errno, the log message will end up with ": Success" or an error
from a stale errno value. Either case is confusing for users.

Introduce bailx which is the same as bail except it does not
append %m, and use it where appropriate.

The naming follows libc's err(3) and errx(3).

PS we still use bail in a few cases after read or write, even
if that read/write did not return an error, because the code
does not distinguish between short read/write and error (-1).
This will be addressed by the next commit.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-28 17:21:38 -07:00
Kir Kolyshkin 9c8f476cb6 libct/nsenter: save errno in sane_kill
Since sane_kill after a failed read or write, but before reporting the
error from that read or write, it may change the errno value in case
kill(2) fails.

Save and restore the errno around the call to kill.

While at it,
 - change the code to return early;
 - don't return kill return value as no one is using it, and the errno
   value no longer correlates.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-28 17:21:25 -07:00
lfbzhm 52a9dd563d Merge pull request #4953 from opencontainers/dependabot/github_actions/actions/upload-artifact-5
build(deps): bump actions/upload-artifact from 4 to 5
2025-10-28 08:13:33 +08:00
dependabot[bot] f5f6056219 build(deps): bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 04:09:57 +00:00
Akihiro Suda 644bb1e59d Merge pull request #4950 from ariel-anieli/loop-range-integers
test: add missed use of for range over integers
2025-10-26 04:20:57 +09:00
Ariel Otilibili 34da991298 libcontainer/seccomp: Use for range over integers
The commit mentioned below has missed these changes.

Fixes: 17570625 ("Use for range over integers")
Signed-off-by: Ariel Otilibili <otilibil@eurecom.fr>
2025-10-23 12:48:49 +02:00
lfbzhm 3a56046197 Merge pull request #4945 from kolyshkin/int-cleanup-part1
tests/int: cleanup
2025-10-22 23:40:54 +08:00
Akihiro Suda 7f32e50085 Merge pull request #4938 from zhaixiaojuan/main
Add loong64 support in seccomp and PIE
2025-10-22 12:51:28 +09:00
zhaixiaojuan d12d6010de Merge branch 'main' into main 2025-10-21 10:10:33 +08:00
zhaixiaojuan 885509afdf Add loong64 support in seccomp and PIE
Signed-off-by: zhaixiaojuan <zhaixiaojuan@loongson.cn>
2025-10-21 09:41:49 +08:00
Aleksa Sarai a7651e0ee0 merge #4944 into opencontainers/runc:main
Kir Kolyshkin (2):
  ci: show criu version in criu-dev testing
  ci: bump bats to 1.11.1

LGTMs: AkihiroSuda cyphar
2025-10-20 19:29:58 +11:00
Kir Kolyshkin 3c2683f52f tests/int/cgroups: use heredoc to break a long line
This is mostly to improve readability. While at it, make the script more
robust by adding -e option to shell. The exception is echo $pid which is
opportunistic and may fail depending on the order of pids in the file.

Also, remove the empty comment and a shellcheck annotation.

Fixes: c91fe9ae
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-18 15:35:51 -07:00
Kir Kolyshkin b82ae3afdc tests/int/delete: fix pause test for rootless case
The "runc delete --force [paused container]" test case does not check
runc pause exit code, and if added, the test fails in rootless tests,
because:
 - not all rootless tests have access to cgroups;
 - rootless containers doesn't have default cgroups path.

To fix, add:
  - setup for rootless case;
  - require cgroups_freezer;
  - runc pause exit code check.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-18 15:33:27 -07:00
Kir Kolyshkin ad72eab6c7 tests/int/checkpoint: fix using run twice
In our bats tests, runc itself is a wrapper which calls bats run helper,
so using "run runc" is wrong as it results in calling run helper twice.

Fixes: 8d180e965
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-18 15:30:16 -07:00
Kir Kolyshkin 92f3d1b225 tests/int/cgroups.bats: fix a wrong comment
This misleading comment is obviously a copy/paste from the previous
test. Fix it.

Fixes: dd696235
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-18 15:30:16 -07:00
Kir Kolyshkin b3a9f423b9 tests/int: remove bogus $status checks
Commands that are not run via "run" helper (cat, mkdir, __runc)
do not set $status, so it makes no sense to check it.

Fixes: 94505a04, ed548376
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-18 15:30:16 -07:00
Kir Kolyshkin 693a471af8 tests/int: use run with a status check
...instead of an explicit or absent status check.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-18 15:30:16 -07:00
Kir Kolyshkin 773a44cc1d tests/int/netdev: slight refactoring
Move the repetitive code and comment into setup_netns.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-18 15:30:16 -07:00
Kir Kolyshkin 0eb03ef86f tests/int: remove useless/obvious comments
This is a bit opinionated, but some comments in integration tests do not
really help to understand the nature of the tests being performed by
stating something very obvious, like

	# run busybox detached
	runc run -d busybox

To make things worse, these not-so-helpful messages are being
copy/pasted over and over, and that is the main reason to remove them.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-18 15:30:16 -07:00
Kir Kolyshkin 772e91062d tests/int/README: update
1. Remove the devicemapper driver mentions, and is it no longer
   supported by docker (or podman).

2. Remove the test example -- we have plenty of real ones.

3. Add a link to (well written and extensive) bats documentation.

4. Fix capitalization in a sentence.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-18 15:30:16 -07:00
Kir Kolyshkin 2a7ce15e68 ci: show criu version in criu-dev testing
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-16 12:09:46 -07:00
Kir Kolyshkin 6af1d637ba ci: bump bats to 1.11.1
Bump bats to the version from Fedora 42 (used in "fedora" job), so we
have the same version everywhere.

This also fixes an issue introduced by commit d31e6b87 (which forgot to
bump bats in GHA CI), and adds a note to the yaml in order to avoid the
same issue in the future.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-16 12:07:55 -07:00
lfbzhm d34ea2bc06 Merge pull request #4937 from kolyshkin/lint-extra
ci: only run lint-extra job on PRs to main
2025-10-16 08:58:04 +08:00
Kir Kolyshkin 1c4dba693f ci: only run lint-extra job on PRs to main
All the new code appears in main (not in the release branches),
and we only want extra linter rules to apply to new code.

Disable lint-extra job if the PR is not to the main branch.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-15 16:51:22 -07:00
Aleksa Sarai ef90082853 merge #4930 into opencontainers/runc:main
Kir Kolyshkin (2):
  libct: refactor setnsProcess.start
  libct: close child fds on prepareCgroupFD error

LGTMs: lifubang cyphar
2025-10-16 09:36:26 +11:00
lfbzhm f023e1c222 Merge pull request #4934 from AkihiroSuda/lima
CI: remove deprecated lima-vm/lima-actions/ssh
2025-10-15 09:22:23 +08:00
Kir Kolyshkin 871052b791 libct: refactor setnsProcess.start
Factor startWithCgroupFD out of start to reduce the start complexity.
This also implements a more future-proof way of calling p.comm.closeChild.

Co-authored-by: lifubang <lifubang@acmcoder.com>
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-14 11:48:25 -07:00
Kir Kolyshkin 4e262509b8 libct: close child fds on prepareCgroupFD error
The (*setns).start is supposed to close child fds once the child has
started, or upon an error. Commit 5af4dd4e6 added a bug -- child fds
are not closed if prepareCgroupFD fails.

Fix by adding a missing call to closeChild.

I'm not sure how to write a good test case for it. Found when working
on PR 4928 (and tested in there).

Fixes: 5af4dd4e6
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-14 11:48:25 -07:00
Akihiro Suda c0e6f42427 CI: remove deprecated lima-vm/lima-actions/ssh
`lima-vm/lima-actions/ssh` is now merged into
`lima-vm/lima-actions/setup`.

https://github.com/lima-vm/lima-actions/releases/tag/v1.1.0

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2025-10-14 14:41:27 +09:00
lfbzhm 721d066576 Merge pull request #4902 from osamakader/fix-outdated-seccomp-todo
docs: update seccomp documentation
2025-10-14 08:11:27 +08:00
Osama Abdelkader e0632ccd1c docs: update seccomp documentation
Replace outdated TODO comment with updated information about runc's seccomp support.

Signed-off-by: Osama Abdelkader <osama.abdelkader@gmail.com>
2025-10-13 22:41:36 +03:00
lfbzhm fa1b69348e Merge pull request #4914 from osamakader/fix-stringsTitle-criu-linux
criu: replace deprecated strings.Title
2025-10-13 17:33:16 +08:00
Osama Abdelkader 1adb070b58 criu: replace deprecated strings.Title
strings.Title is deprecated since Go 1.18. Replace it with a simple
manual capitalization of the first character in criuNsToKey().

Signed-off-by: Osama Abdelkader <osama.abdelkader@gmail.com>
Co-authored-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-10-12 14:02:21 +03:00
Akihiro Suda 17938cd8b9 Merge pull request #4926 from cyphar/cpuset-fill
libct: switch to (*CPUSet).Fill
2025-10-09 16:41:02 +09:00
Aleksa Sarai 93f9a392cf libct: switch to (*CPUSet).Fill
Now that we've updated to golang.org/x/sys@v0.37.0, CPUSet has a Fill
helper that does the equivalent to our underflow trick to make setting
all CPUs efficient.

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
2025-10-09 15:39:02 +11:00
Aleksa Sarai e3ab743998 Merge pull request #4924 from opencontainers/dependabot/go_modules/golang.org/x/net-0.46.0
build(deps): bump golang.org/x/net from 0.45.0 to 0.46.0
2025-10-09 15:37:03 +11:00
dependabot[bot] e59062cec9 build(deps): bump golang.org/x/net from 0.45.0 to 0.46.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.45.0 to 0.46.0.
- [Commits](https://github.com/golang/net/compare/v0.45.0...v0.46.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-09 04:02:50 +00:00