Hello everyone! I'm Deepak, starting my third year of college soon. I've been working on enabling checkpoint and restore for rootless containers in Podman for this GSoC 2026 term. Why Podman? because it already has native CRIU support. (and was also suggested by my mentors) Mentors: Radostin Satyonov and Adrian Reber
What are rootless containers and what is CRIU?
If you've installed Docker, you've probably added yourself to the docker group. That group effectively has root privileges. Rootless containers let unprivileged users create and manage containers directly. The upside is security, it's a much bigger deal than most people realize.
CRIU is the software that lets you freeze a running container, save its state to disk, and later restore it exactly where it left off.
"CRIU started as a project of Virtuozzo, and grew with the tremendous help from the community. It is currently used by (integrated into) OpenVZ, LXC/LXD/Incus, Docker, Podman, Kubernetes and other software, and packaged for many Linux distributions." https://criu.org/Main_Page
Basically, it's a really really cool project if you're interested in Linux internals, containers, or process migration.
The Problem
CRIU needs to save enough state that a process can be restored later. Memory, open files, signal handlers, namespaces, seccomp filters, network state, and a bunch of other kernel states all need to be captured.
CRIU normally needs either sudo or CAP_SYS_ADMIN to perform alot of these operations. CAP_CHECKPOINT_RESTORE was added in 2020 as a more narrowly scoped alternative, but there are still many places where checkpoint/restore requires CAP_SYS_ADMIN straigh up. So a lot of my work was basically finding all the failure points and figuring out alternative ways to get the same information without requiring additional privileges.
One example is seccomp filters. CRIU normally asks the kernel for the filter, but the kernel gives EPERM/EACCESS for unprivileged users. Instead, we can reconstruct the filter from the container's seccomp policy and pass it to CRIU thorugh RPC.
The Prototype Phase
Podman has a guard that blocks checkpoint/restore in rootless mode. I patched it out and ran with CRIU's --unprivileged flag to see where it would break. Here's what failed, in order.
1. Seccomp suspend fails: CRIU injects a small blob of code (the "parasite") into the target to read its state, it needs to suspend seccomp so the parasite's syscalls don't get blocked. The kernel refused this. Without suspension, the parasite's vmsplice() call (used to transfer memory pages) is blocked by the default Podman seccomp profile. We can keep the parasite for everything else, but read memory pages from outside using process_vm_readv(), which CRIU already had as a pre-dump fallback.
2. Mapped files are invisible: CRIU reads symlinks under /proc/PID/map_files/ to find which file backs each memory-mapped region. These return EACCES across user namespace. Fixed it by opening the target's root directory through /proc/PID/root and resolving paths from there instead.
3. Seccomp BPF is not readable: PTRACE_SECCOMP_GET_FILTER gives EACCES across user namespace. Skipped for the prototype needed to see what else was failing.
4. Socket can't be discovered outside network namespace: Normally CRIU jumps into the container's network namespace and opens diagnostic sockets directly. Can't do that without privileges so instead CRIU can fork a child process that enters the netns, creates the sockets there, and hands them back to the parent. The child exits, the parent keeps the sockets.
5. Restore mount failures: Cgroup mounts returned EPERM. CRIU's mount tree didn't understand how rootless crun binds individual /dev/null entries. Mount options contained host-subordinate IDs that don't exist in the restore-time user namespace.
6. Various EPERMs on restore: Setting hostname, raising file descriptor limits, and opening sysctl files all returned EPERM. Made them non-fatal in unprivileged mode.
7. Polish: A protobuf compatibility fix for newer Python protobuf versions. Bind remount flags like MS_RDONLY return EPERM without root the bind itself succeeds, so we log a warning and continue.
Now What?
This gave me an overview of all the big failures. After this I started testing different applications to see what else is breaking, wrote probe tests to figure out what we can and can't do, and read a lot of lkml code to actually understand what's going on under the hood. Then I started working on a proper branch where I could fix all of these issues the right way.
A lot of patches in CRIU, and a few in Podman and crun.
Right now the whole flow c/r works with just CAP_CHECKPOINT_RESTORE and CAP_SYS_PTRACE on the binary. Tested across a bunch of workloads. There are few Caveats:
1. Multi-stack seccomp filters: Filters from the OCI spec or --security-opt seccomp= work fine because crun compiles them and passes them to CRIU. But if you install a filter at runtime (e.g. with nsenter), it's gone and there's no way to pull it back out of the kernel without privileges.
2. Cgroups: CRIU doesn't touch cgroups in rootless mode. Podman handles all of that itself through systemd scopes and manual cgroup moves.
3. Open TCP connections: Active connections through pasta's port forwarding don't survive restore. Podman restarts pasta during netns teardown which kills the host-side socket. New connections work fine after restore. This is a Podman/pasta lifecycle thing, not a CRIU limitation.
I'll go over all the design decisions I made for the current open PR in the next blog.
The open PR is at checkpoint-restore/criu#3057.