Linux System Programming · advanced · ~20 min
Understand the Linux features Docker is built from.
Containers are built from two Linux kernel features:
Put simply: namespaces isolate, cgroups limit. Combine the two and you get a container.
When you debug a container or audit a runtime, you need to know which feature controls which behavior.
This mapping makes diagnosis fast:
Knowing which primitive owns each symptom tells you where to look first.
There are seven namespace types: PID, network, mount, UTS (hostname), IPC, user, and cgroup.
You create them with the system calls unshare(2) or clone(2).
cgroups version 2 uses a single mounted hierarchy under /sys/fs/cgroup.
You apply limits by writing to control files, for example:
cpu.max - CPU time budgetmemory.max - memory ceilingpids.max - maximum number of processesA user namespace lets a non-root user act as root inside the namespace while staying unprivileged outside it. This is the foundation of rootless containers.
Container-escape research focuses on three layers:
Most container vulnerabilities live in one of these three places.
When you write a sandbox, document exactly which namespaces and which cgroup knobs you set.
Treat anything you did not explicitly isolate as something that can leak.
#include <sched.h>
int unshare(int flags); /* detach the caller into new namespaces */
int setns(int fd, int nstype); /* join an existing namespace by fd */
A container is simply namespaces (isolation) plus cgroups (resource caps).
Both are kernel features. Docker stitches them together and presents them as one user-facing object.
Reading what these primitives actually do helps you understand what a container can - and cannot - protect against.
unshare(CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNS);
/* child sees its own PID 1, hostname, mount table */
if (unshare(CLONE_NEWPID | CLONE_NEWNS) < 0) {
perror("unshare"); return -1;
}
/* mount a fresh /proc inside the new mount + PID namespace */
mount("proc", "/proc", "proc", 0, NULL);
Run ls -l /proc/self/ns/ to see your current namespaces. Each entry shows an inode number that identifies the namespace.
Processes that share a namespace share its inode. For example, two PIDs in the same network namespace show the same net inode.
This topic is unrelated to memory safety. Container escapes happen at the kernel level, not through memory bugs in your program.
These primitives power every major container runtime:
Each runtime is essentially a user interface built on top of namespaces and cgroups.
unshare(CLONE_NEWUTS), then sethostname(). Confirm the parent's hostname is unaffected./sys/fs/cgroup/.../cpu.max to inspect a CPU limit./proc/self/ns/pid and compare it to a child process's PID namespace inode.