CVE posture
September 19, 2026 · View on GitHub
What the published container-runtime escapes do to kern, measured rather than argued.
Every row below was reproduced on a development host (x86_64, kernel 6.x, rootless, no delegated privileges) against the binary in this tree. Where the answer is "not exposed", the evidence is the command and its output, not a description of the design. Where kern does not protect, the last section says so and does not soften it.
This page is not a claim that kern is safe against hostile code. It is a claim about a specific, enumerated set of published vulnerabilities, and about why most of them cannot reach a runtime shaped like this one. The boundary this runtime does not draw is stated in THREAT_MODEL.md and repeated at the end here.
The three facts that decide most of the table
$ kern box p --image alpine:3.19 -- cat /proc/self/uid_map
0 1000 1
1 100000 65536
Root inside the box is the unprivileged host user. There is no root daemon to compromise and no privilege to escalate to: an escape lands on uid 1000, which is where the attacker already was.
$ kern box p --image alpine:3.19 -- grep -E 'CapEff|CapInh|NoNewPrivs|Seccomp:' /proc/self/status
CapEff: 00000110bd84efff NoNewPrivs: 1
CapInh: 0000000000000000 Seccomp: 2
Absent from CapEff: SYS_ADMIN, SYS_MODULE, SYS_RAWIO, SYS_PTRACE, SYS_BOOT, NET_ADMIN,
BPF, PERFMON, SYSLOG, MAC_OVERRIDE, MAC_ADMIN, AUDIT_CONTROL, AUDIT_READ. CapInh is
empty and NoNewPrivs is set.
Seccomp is a deny-by-default allowlist, not a denylist, and it is always on. mount(2),
unshare(2) and clone(2) with new namespace flags are outside it: a call returns SIGSYS, which a
shell reports as Bad system call.
The table
| CVE | What it is | kern | Evidence |
|---|---|---|---|
| CVE-2019-5736 | runc: a container overwrites the runtime binary through /proc/self/exe | not exposed | echo x > /proc/self/exe → Text file busy. The kern binary is not in the box's filesystem; /proc/1/exe is the image's own init. A successful write would land on a binary owned by uid 1000 |
| CVE-2024-21626 (Leaky Vessels) | runc: a leaked file descriptor plus WORKDIR /proc/self/fd/N puts the process in a host directory | not exposed | --workdir /proc/self/fd/N for N in 3..9: chdir(workdir) failed: No such file or directory for every one. The box holds fds 0,1,2 and one unresolvable 3 |
| CVE-2016-9962 | runc exec: descriptors to the host, ptrace of the init process | not exposed | SYS_PTRACE absent. The box sees 4 processes, its own pid 1 among them |
| CVE-2022-0492 | escape through the cgroup v1 release_agent | not exposed, two independent barriers | mount -t cgroup -o rdma none /mnt → Bad system call. And SYS_ADMIN, its prerequisite, is absent. /sys/fs/cgroup is v2 and read-only; release_agent does not exist there |
| CVE-2019-19921, CVE-2023-27561, CVE-2019-16884 | a container mount rewrites /proc, defeating masked paths or an LSM profile | not exposed | mount --bind /tmp /proc → Bad system call. /proc/sys, /proc/sysrq-trigger, /proc/irq and /proc/bus are mounted read-only |
| CVE-2018-15664, CVE-2019-14271 | docker cp: a symlink in the container redirects the copy to the host | not exposed | With /escape -> / in the box, kern cp box:/escape/etc/passwd reads the BOX's file: md5 75ab66…, 1172 bytes, identical to the box's own and different from the host's (80c0b5…, 3416 bytes). A copy INTO the box through a symlink pointing at a host path fails: the link resolves inside the box's namespace, where that path does not exist |
| CVE-2022-24769, CVE-2022-29162 | inheritable capabilities left set, so a setuid binary regains them | not exposed | CapInh: 0000000000000000, NoNewPrivs: 1 |
| CVE-2020-15257 | the containerd-shim's abstract unix socket is reachable from a host-network container | does not apply | kern is daemonless and has no shim. No abstract socket of kern's exists to reach: there is no control channel |
| CVE-2024-41110 | Docker Engine: an authorization plugin is bypassed through the API | does not apply | No daemon, no API, no plugins |
| CVE-2020-15157 (ContainerDrip) | an image pull sends registry credentials to a host the registry names | not exposed | The credential is sent only when the WWW-Authenticate realm belongs to the registry; a foreign realm is refused by name and nothing is sent. Three tests cover it: realm_trust_pins_creds_to_the_registry, upload_location_rejects_cross_host_redirect, upload_location_loopback_blocks_ssrf |
| CVE-2022-23648 and the tar class | a crafted image layer writes outside the rootfs (absolute paths, .., hardlinks, symlink redirection) | not exposed | 14 refusal tests, among them rejects_absolute_hardlink_target_raw, rejects_write_through_an_escaping_symlink, rejects_hardlink_through_an_escaping_symlink, rejects_pax_global_path_override, rejects_gnu_sparse_typeflag, under_rejects_traversal_and_absolute |
| CVE-2024-23651, CVE-2024-23652, CVE-2024-23653 | BuildKit: a build reaches outside its context, or races a cache mount | does not apply (kern has its own builder); the class was tested anyway | COPY ../../etc/passwd → escapes the build context; COPY /etc/passwd → the same; a symlink inside the context pointing out → the same; a build context resolving onto kern's own registry → refused by name. kern build --check reports all four without building |
| CVE-2021-42574 (Trojan Source) | text rendered in an order the bytes do not have: bidirectional overrides reverse what a terminal draws | was exposed, now closed | A hostile registry on loopback answered kern pull with U+202E, U+200B, U+200E and U+200F in its message, and all four reached the terminal intact while ESC and CR were correctly dropped: the filter tested char::is_control(), the Cc category, and these are Cf. One predicate now decides it for all three filters. The same body carrying 1 MB printed 1 MB; capped at 200 characters with the overflow announced. sh pentest/pentest-hostile-registry.sh is the reproducer and fails against the previous binary |
| CVE-2019-5021 | Alpine images shipped with a blank root password | no consequence | Box root is already the unprivileged host user |
Measured alongside, with no CVE of their own:
unshare -Urm true -> Bad system call (nesting is refused)
wget http://127.0.0.1:<host port> -> can't connect (the host's loopback is not the box's)
/sys/firmware /dev/mem -> absent
/sys/kernel/security /proc/sched_debug -> absent
/proc/kcore /proc/keys /proc/timer_list -> present, 0 bytes
Where kern does not protect, and will not claim to
Kernel vulnerabilities stay kernel vulnerabilities. Dirty COW (CVE-2016-5195), Dirty Pipe (CVE-2022-0847), the netfilter heap overflow (CVE-2021-22555), the fs-context overflow (CVE-2022-0185), the nf_tables use-after-free (CVE-2024-1086): these are bugs in the host kernel reachable by unprivileged code. The seccomp allowlist removes a large part of the reachable syscall surface and the empty capability set removes more, but kern is not a boundary against a kernel that is broken. Code you do not trust with your kernel belongs in a microVM, and this project says so in its own documentation rather than selling the namespace as something it is not.
A mount option discloses the host path. A box reads its own /proc/mounts and sees the overlay's
lowerdir=/home/<user>/.cache/kern/images/…, so it learns the host username. This is a property of
rootless overlay and not of kern: podman 4.9.3 rootless, on the same host, shows
lowerdir=/home/<user>/.local/share/containers/storage/overlay/l/…. It is disclosure, not access,
and it is stated here rather than left to be discovered.
This page ages. Every row is an escape that was published and analysed. It says nothing about the next one. What it does say is that a runtime with no daemon, no shim, no socket and no root has fewer places for the next one to land.
Reproducing it
Every command in this page runs against a built binary and needs no privileges:
cargo build --release -p getkern
./target/release/kern box probe --image alpine:3.19 -- sh -c 'cat /proc/self/uid_map; grep -E "CapEff|CapInh|NoNewPrivs|Seccomp:" /proc/self/status'
The image-layer refusals are in the test suite rather than in a shell:
cargo test -p kern-oci --lib