blog.rust.gay

User namespaces plus file capabilities equals trouble

User namespaces are a fundamental tool for making containers in the kernel. A process in a user namespace can seem to be full root on the system, but without any extra privileges on the larger system (or at least that's the goal). But time and again it's been difficult to keep privilege limited to the user namespace. A set of patches by Josef Bacik, discussed in LWN on 5 November 2026, is one example of this: the interaction of user namespaces and file capabilities that allow a process in a user namespace to elevate the privilege levels of processes outside the user namespace.

This post is about what that interaction is, why it's hard, and how the patch series fixes it. It's a small series of patches with big implications for container security. It's a good example of a kind of bug that'll just keep coming as long as we're trying to make "root inside a container" mean something different from "root on the host".

How user namespaces are supposed to work

User namespaces provide a mapping between user/group IDs in the namespace and the host. So a process in the namespace can have UID 0 (root) and the kernel will consider it root for purposes of things it can do in the namespace (mounting filesystems, creating devices, changing ownership of files within the namespace's view). But on the host, that same process is an unprivileged user.

The idea is containment: you can run your container as root inside it without it being root on the host, so if there's a compromise in the container, that won't necessarily cascade to the host. That's the promise. And it's the basis of rootless containers. And unprivileged container runtimes. And a lot of the modern container security paradigm.

The problem is that the kernel isn't really built to think about this kind of mapping. A lot of it is still hardcoded to think that UID 0 means root. Period. So everywhere the kernel has to ask "is this process root?" we need to go back and ask "root in what namespace?" and "root for what operation?" We've been auditing this for years, but it's not done.

Where file capabilities enter

File capabilities are a different mechanism. Files can have extended attributes that give certain capabilities (CAP_NET_BIND_SERVICE or CAP_SYS_ADMIN or whatever) to the process that executes the file. So when a process executes a file with file capabilities, the process gets those capabilities (assuming it's within the capability bounding and ambient sets).

The file capabilities were built for a world where privilege is global. So setuid root binaries are root everywhere. And files that have CAP_NET_BIND_SERVICE can bind low ports everywhere. It's a powerful system because it's global and unambiguous.

But when you mix it with user namespaces, it's a problem. A process in a user namespace has a mapped UID of 0. And the kernel needs to figure out what capabilities this process should have. The rule is that a process in a user namespace that has UID 0 has the full set of capabilities. But it's only within the user namespace. So capabilities that are limited to the user namespace are OK. But not capabilities that escape or are applied to things that are outside the user namespace.

And file capabilities are exactly the kind of thing that's outside the user namespace. Files created or modified inside a container might be visible on the host. And the kernel on the host interprets the file capability xattrs using the host rules. So if a process in a user namespace is allowed to set file capabilities on a file that the host kernel will later honor, then it's effectively giving privileges to whatever process on the host executes that file. That's the kind of privilege elevation the patch series is fixing.

The specific problem

The patch series by Josef Bacik deals with the problem of privilege elevation of processes outside a user namespace by processes inside a user namespace. It's the interaction of user-namespace ID mapping and file capability handling. So the kernel's checks for whether a process can set or modify capabilities on a file don't always handle the namespace context properly. So a namespace root process can end up giving capabilities that the host kernel will honor.

The fix is in the path of setting capabilities. The patches strengthen the checks so that the authority of a process to manipulate file capabilities is assessed against the actual global authority of the process, instead of the in-namespace authority. So a process that's only root inside a user namespace shouldn't be able to bless a file with capabilities that escape the namespace.

It's a small, specific change. It doesn't redesign user namespaces or file capabilities. It's just fixing a specific hole in the boundary between them. That's the kind of fix you want in this area, because it's subtle interactions and broad changes might break legit use cases or open up new holes.

Why this class of bug keeps recurring

The basic problem is that user namespaces were retrofitted onto a kernel that assumed a single global identity space. So we've got to audit all the subsystems for namespace awareness when they touch identity or capabilities or privilege. And it never ends because the kernel keeps growing.

For example file capabilities. But also setuid binaries. Or POSIX ACLs. Or extended attributes. Or device nodes. Or various proc/sysfs interfaces. Each of those has or will have some sharp edges in terms of namespaces. It's a pattern: you have this mechanism that's built for global privilege and you're interacting with a namespace that gives local privilege. And the boundary between them is not airtight.

Container runtimes and security people have had to learn to think of this as a risk rather than a solved problem. Rootless containers help mitigate that risk by not even having to have in-namespace root at all. And if you do need in-namespace root, the mitigations are defence-in-depth. So you have seccomp filters that block the relevant syscalls. And SELinux or AppArmor profiles that limit the container's access. And you need to be careful what filesystems you expose to the container.

What this means for operators

The practical lesson for container operators is that user namespaces are a strong, but not absolute, boundary. It's good to use them. They meaningfully reduce the effect of a compromise in your container. But it shouldn't be the only thing you do.

So some specific things to think about:

  • Don't assume namespace isolation for files that are shared with the host. So if you've got any files that cross the namespace boundary (via bind mount or a shared volume or copied artifact) then you should assume they're untrusted on the host. The patch series fixes one specific path. But the general idea is that if data is leaving a container and re-entering the host's trust domain, you need to validate that data separately.

  • Keep your kernels up to date. This patch series is a fix for a real, exploitable class of problem. The fix is to update. If you're running containers on a host that lags on kernel updates, you're exposing yourself to the risk for longer than necessary.

  • Use the container runtime hardening. Modern container runtimes have seccomp profiles and capability dropping and (optionally) LSE confinement. These aren't just decoration. They're the layers that catch the things user namespaces miss. If you're running containers with --privileged or with wide capability granting, then they're all undermined.

  • Try to run rootless if you can. If you have a container that never has UID 0 in a namespace, then you don't even have these questions about privilege and namespace root. Not all workloads are doable rootless. But you should do it for the ones that are.

What this does not fix

The patch series is specific. So it's important not to overstate the significance. It's not making user namespaces completely airtight. It's not dealing with the other mechanisms (setuid or ACLs or device nodes) that also have namespace interactions. And it's not changing the basic design tension. The kernel is trying to make "root in a namespace" mean something specific and limited. But it's doing that in a codebase that was built with the assumption that root is root.

There will be more bugs as a result of that tension. The solution isn't to give up on user namespaces. That'd be a big step back for container security. But we need to keep auditing. And keep fixing. And keep adding layers so that no individual hole is catastrophic.

Conclusion

The user namespace/file capability problem is a good example of a whole category of kernel security work. Small, subtle interactions between mechanisms that were designed separately. Exploited by a process that's privileged in one context but unprivileged in another. The fix is small and correct. And it fixes a real path for privilege elevation.

The bigger lesson is that container isolation is a stack. It's not one single thing. User namespaces are a good layer in the stack. And this patch series makes it a little better. But it's the layers above and below it (runtime hardening, LSE confinement, kernel currency, application-level validation) that turn "a little better" into "good enough".

Sources- LWN, "User namespaces + file capabilities = trouble" (5 Nov 2026): https://lwn.net/Articles/1103429/

  • Josef Bacik's patch series (b4-setfcap-userns): https://lore.kernel.org/all/20261027-b4-setfcap-userns-v8-0-2674adc63f3c@toxicpanda.com/
  • Linux kernel documentation on user namespaces: https://www.kernel.org/doc/html/latest/admin-guide/namespaces.html
  • Linux capabilities manual page: https://man7.org/linux/man-pages/man7/capabilities.7.html

Sources

This post is informed by discussion on lwn.net. Primary source: https://lwn.net/Articles/1103429/.


← back to posts