Re: [PATCH 0/8] userns: per-namespace capability bounding set and opt-in netns loopback
From: Josef Bacik
Date: Wed Nov 04 2026 - 09:16:14 EST
On Wed, Oct 28, 2026 at 10:16:57PM +0000, Hallgrimur H. Gunnarsson wrote:
> When an unprivileged process creates a new user namespace, it is granted
> a full set of capabilities (CAP_FULL_SET) scoped to that namespace.
> While these capabilities are isolated to the namespace, they allow
> unprivileged local users to reach deep kernel subsystems (most notably
> networking configuration and raw sockets via CAP_NET_ADMIN and
> CAP_NET_RAW) that historically assumed a trusted caller and have
> accounted for a large share of local privilege escalation
> vulnerabilities.
>
> Many workloads and sandboxes use unprivileged user namespaces strictly
> for basic UID/GID, mount, PID, or network isolation and never need
> high-risk capabilities like CAP_NET_ADMIN or CAP_NET_RAW. Likewise,
> system administrators often want a host-wide policy ceiling restricting
> which capabilities unprivileged user namespaces may acquire, without
> disabling unprivileged user namespaces altogether.
>
> Prior work by Jonathan Calmels [1] proposed addressing this via a sixth
> per-thread capability set (cap_userns) alongside a global sysctl mask.
> During that discussion [1, 2], feedback (e.g. from Casey Schaufler)
> suggested that rather than adding another per-thread capability set and
> prctl() interface, capability restrictions could be integrated directly
> into struct user_namespace and configured via clone3(). At the same
> time, as noted in [1], a clone3() flag alone is not sufficient for
> administrators who need to enforce a host-wide ceiling on unprivileged
> user namespaces without modifying every userspace callsite.
>
> This series combines both pieces of feedback: it embeds a capability
> bounding set (cap_bset) directly into struct user_namespace, reusing
> the existing task cap_bset, cap_permitted, and cap_effective sets on
> namespace entry, and provides both a system-wide sysctl ceiling for
> unprivileged user namespaces and a CLONE_NEWUSER_BSET flag for
> voluntary per-namespace restriction via clone3().
>
> In addition, because restricting CAP_NET_ADMIN at user namespace
> creation time prevents userspace from bringing up the loopback
> interface in new network namespaces, the series first introduces
> an opt-in flag to automatically enable loopback on network namespace
> creation:
>
> 1. Opt-in loopback activation on netns creation (Patch 1)
>
> New network namespaces start with the loopback interface (lo) DOWN by
> default, requiring CAP_NET_ADMIN over the owning user namespace to
> bring it up. To block external network access while preserving
> localhost networking, sandboxes commonly create an isolated network
> namespace with only the loopback interface enabled.
>
> However, requiring a sandbox to hold CAP_NET_ADMIN in a new user
> namespace solely to bring loopback up conflicts with reducing the
> CAP_NET_ADMIN attack surface. Today, an unprivileged sandbox must
> start with CAP_NET_ADMIN to bring loopback up, drop it, and then
> block CAP_NET_ADMIN from being regained in nested user namespaces
> (e.g. via seccomp or max_user_namespaces limits). Conversely, when
> CAP_NET_ADMIN is restricted at user namespace creation time (via the
> sysctl ceiling or CLONE_NEWUSER_BSET introduced in Patches 2-3, or
> via LSMs such as Landlock), tasks inside the new namespace never
> acquire CAP_NET_ADMIN and cannot bring loopback up from userspace,
> breaking localhost networking for unprivileged sandboxes.
>
> Introduce the CLONE_NEWNET_LOOPBACK flag for clone3() and the
> corresponding UNSHARE_NEWNET_LOOPBACK flag for unshare(), which imply
> CLONE_NEWNET and automatically bring the loopback device up in
> copy_net_ns() before returning the new network namespace. Making this
> an explicit opt-in preserves default network namespace semantics
> while allowing unprivileged sandboxes to create an isolated network
> namespace with functional localhost networking without ever holding
> CAP_NET_ADMIN.
>
> 2. Per-user-namespace capability bounding set & sysctl ceiling (Patch 2)
>
> Add cap_bset to struct user_namespace (initialized to CAP_FULL_SET in
> init_user_ns) and introduce a system-wide sysctl ceiling for
> unprivileged users at:
> /proc/sys/kernel/unprivileged_userns_caps_allowed
> (defaulting to CAP_FULL_SET).
>
> When a new user namespace is created:
> - Privileged creators (holding CAP_SYS_ADMIN in the parent user
> namespace) inherit cap_bset from the parent user namespace.
> - Unprivileged creators (lacking CAP_SYS_ADMIN in the parent user
> namespace) have the child namespace's bounding set constrained by
> the sysctl ceiling combined with any effective capabilities the
> creator already holds in the parent namespace:
> child_bset = parent_bset & (sysctl_ceiling | creator_effective)
> Allowing the creator to retain capabilities it already holds in the
> parent namespace ensures that a process granted a specific
> capability (e.g. CAP_NET_ADMIN) does not lose it when creating a
> nested user namespace.
>
> When a task enters a user namespace via clone()/unshare() or
> setns(), its cap_bset, cap_permitted, and cap_effective sets are
> clamped to user_ns->cap_bset. In addition, cap_capable_helper() is
> updated so that implicit capabilities granted to the parent-namespace
> owner of a user namespace are likewise bounded by the owned
> namespace's cap_bset.
>
> 3. Per-namespace restriction via clone3() (Patch 3)
>
> Introduce CLONE_NEWUSER_BSET and add cap_bset to struct clone_args.
> When specified alongside CLONE_NEWUSER, the new user namespace's
> bounding set is further intersected with the requested cap_bset mask,
> allowing container runtimes and sandboxes to drop unneeded capabilities
> from a user namespace at creation time even when permitted by the
> global sysctl.
>
> 4. Introspection via NS_USER_GET_INFO (Patch 4)
>
> While tasks inside a user namespace see their initial ceiling in
> /proc/<pid>/status (CapBnd), threads may subsequently drop
> individual capabilities from their per-thread bounding set. Add
> an extensible NS_USER_GET_INFO ioctl on userns file descriptors
> to allow userspace to directly query a user namespace's cap_bset.
>
> 5. Selftests (Patches 5-8)
>
> Add kselftest suites covering CLONE_NEWNET_LOOPBACK, the sysctl
> ceiling, creator capability retention, nested monotonicity,
> parent-owner bounding, CLONE_NEWUSER_BSET and NS_USER_GET_INFO.
>
FYI I'm doing something similar with cells
(https://github.com/josefbacik/linux/tree/cells) that gives the same
sort of discrete control over the different parts of the network.
I super dislike this mapping of user to capabilities, as you said it
creates this hodgepodge of areas that need to be updated and just
creates more possibility of escapes where we get it wrong.
Extending userns makes sense in the current landscape, but I'd rather
get away from using arbitrary user ids + a capability set as a control
for what a task can do. Thanks,
Josef