A writable cgroup v1 mount comes with a release helper that runs as root
A writable cgroup v1 mount comes with a release helper that runs as root Container escapes get discussed as kernel bugs. A large share of the practical ones need no bug at all: they need one bind mount that was granted
A writable cgroup v1 mount comes with a release helper that runs as root
Container escapes get discussed as kernel bugs. A large share of the practical ones need no bug at all: they need one bind mount that was granted for monitoring or resource limits and never re-examined afterwards.
On a cgroup v1 host, each hierarchy carries a file called release_agent in its top-level directory. The kernel documentation describes it plainly: the file holds "the path to use for release notifications (this file exists in the top cgroup only)". When a cgroup becomes empty, the kernel checks the notify_on_release flag for that cgroup and, if it is set, runs the program named in release_agent, passing the pathname of the abandoned cgroup relative to the hierarchy mount point as an argument.
Why this matters more than it looks
The helper is executed by the kernel on behalf of the cgroup subsystem, in the initial namespaces, as root. Nothing in that path consults the container's user namespace, its seccomp filter, or its AppArmor profile, because the execution does not originate from a syscall made inside the container's context.
The decisive question is narrower: can the container write to the release_agent file of a cgroup v1 hierarchy, for example through a writable /sys/fs/cgroup mount, and can it write to a cgroup directory so that notify_on_release can be set and later triggered?
Those two writes are the whole primitive. Everything else is bookkeeping: create a child cgroup, turn on the notification flag, drop the last process from it, and wait for the helper to fire.
Where the writable mount comes from
Redacted configuration templates for container platforms still circulate with the cgroup filesystem mounted read-write, sometimes at the top level of the hierarchy, sometimes with a narrower path. Monitoring agents that read cpuacct and memory usage usually need only read access. The writable variant is normally added for a different reason: a legacy manifest that expected to manage its own limits, or a convenience grant copied from an older deployment.
The same applies to container runtimes that expose a host path through a volume. A volume of /sys/fs/cgroup with write permission is the equivalent of handing over the escape directly.
Detection that survives a rebuild
Reading the kernel's configuration is the durable check. On any host still running cgroup v1, confirm which hierarchies exist and whether release_agent exists at their root, then confirm which workloads can write to it. release_agent exists at the top of the hierarchy only, which is useful: a container that can write the top-level file of a hierarchy is not a borderline case.
For each running container, inspect the cgroup mounts and their access mode rather than the declared configuration, since the declared configuration is what drifted. A mount listing that shows rw against a cgroup hierarchy for a workload that only reports metrics is the finding, and it does not depend on guessing which process will be abandoned.
Process auditing helps on the second half. An unexpected execution of a helper binary whose parent is the kernel's cgroup release path is the event to look for, but by then the code has run, so this belongs in incident response rather than prevention.
Kernel-level telemetry can also cover the trigger: the creation of a cgroup directory, the write to notify_on_release, and the removal of its contents form a distinctive sequence. Legitimate orchestration produces it too, so the useful discriminator is the origin of those writes, not their shape.
What the migration to cgroup v2 changes
cgroup v2 removed the release helper rather than repairing it. The notification model was rebuilt around cgroup.events and polling, and there is no per-hierarchy path that the kernel executes. Distributions have been moving to the unified hierarchy for years, and the kernel documentation for cgroup v1 notes that release_agent itself should be replaced with the conventional fsnotify interface, which is a candid summary of the design.
That makes the fix partly a migration decision and partly a permissions decision. On a mixed estate, some hosts will not have the primitive at all and others will still have it for as long as the old hierarchies are mounted. Neither state is discoverable from an inventory that only records the distribution version.
The practical checklist
Mount cgroup hierarchies read-only unless a workload genuinely needs to write them, and if it does, give it a subtree rather than the hierarchy root. Treat a container that can write the top-level release_agent file as already able to run code on the host, and rotate anything the container could reach.
Then test the assumption rather than trusting the template. Attempt the write from inside a representative container, with the same runtime and the same manifest, and record whether it succeeds. That result is one line in an asset inventory that says something true about the host, which is more than the mount flags in a Git repository usually manage.
References
- Linux kernel documentation, Control Groups (cgroup v1),
release_agentandnotify_on_release: https://docs.kernel.org/admin-guide/cgroup-v1/cgroups.html - Linux kernel documentation, Control Group v2: https://docs.kernel.org/admin-guide/cgroup-v2.html
- Linux
cgroups(7)manual page: https://man7.org/linux/man-pages/man7/cgroups.7.html
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.