GitHub Security Advisory: GHSA-qw9x-cqr3-wc7r
Package: github.com/opencontainers/runc (GO)
Severity: HIGH (CVSS 0.0)
CWE: CWE-61: UNIX Symbolic Link (Symlink) Following, CWE-363: Race Condition Enabling Link Following
Vulnerable versions: >= 1.4.0-rc.1, <= 1.4.0-rc.2
Patched in: 1.4.0-rc.3

Summary: runc container escape with malicious config due to /dev/console mount and related races

Details:
### Impact ###
This attack is very similar in concept and application to CVE-2025-31133, except that it attacks a similar vulnerability in a different target (namely, the bind-mount of `/dev/pts/$n` to `/dev/console` as configured for all containers that allocate a console). 

In runc version 1.0.0-rc3 and later, due to insufficient checks when bind-mounting `/dev/pts/$n` to `/dev/console` inside the container, an attacker can trick runc into bind-mounting paths which would normally be made read-only or be masked onto a path that the attacker can write to. This happens after `pivot_root(2)`, so this cannot be used to write to host files directly -- however, as with CVE-2025-31133, this can load to denial of service of the host or a container breakout by providing the attacker with a writable copy of `/proc/sysrq-trigger` or `/proc/sys/kernel/core_pattern` (respectively). 

The reason that the attacker can gain write access to these files is because the `/dev/console` bind-mount happens before `maskedPaths` and `readonlyPaths` are applied.

#### Additional Findings ####
While investigating this issue, runc discovered some other theoretical issues that may or may not be exploitable, as well as taking the opportunity to fix some fairly well-known issues related to consoles.

##### Issue 1: Problematic Usage of `os.Create` #####
Go provides an `os.Create` function for creating files, which older code in runc (dating back to the original `libcontainer` from the early 2010s) had a tendency to use fairly liberally. `os.Create` implies `O_CREAT|O_TRUNC` but by design it does not apply `O_NOFOLLOW` nor `O_EXCL`, meaning if the target is swapped with a malicious symlink runc can be tricked into truncating host files (which can lead to denial of service attacks, among other concerns). 

Runc conducted an audit of all `os.Create` usages in runc and found some suspicious usages related to device inodes, but based on runc's testing these were not exploitable in practice. Runc now has custom code lints to block any `os.Create` usage in runc, and plan to do a further audit of any other plain `os.*` operation usage throughout runc after this advisory becomes public. 

CVE-2024-45310 was a similar attack but without the `O_TRUNC` component (which resulted in a "Low" severity) -- a similar attack being exploitable would've been much more severe.

##### Issue 2: Malicious `/dev/pts/$n` Inode Attacks (`TIOCGPTPEER`) #####
The (very) classic API for constructing consoles involves first opening `/dev/ptmx` for reading and writing. This allocates a new pseudo-terminal and the returned file descriptor is the "master" end (which is used by higher-level runtimes to do I/O with the container). 

Traditionally, in order to get the "slave" end, you do `ioctl(ptm, TIOCGPTN)` to get the pseudo-terminal number and then open the file in `/dev/pts/` with the corresponding base-10 decimal number of the number returned by `TIOCGPTN`. The naive way of doing this is vulnerable to very