Linux Security Modules development
 help / color / mirror / Atom feed
* [PATCH 0/8] userns: per-namespace capability bounding set and opt-in netns loopback
@ 2026-10-28 22:16 Hallgrimur H. Gunnarsson
  2026-10-28 22:16 ` [PATCH 1/8] net: introduce CLONE_NEWNET_LOOPBACK to auto-enable loopback Hallgrimur H. Gunnarsson
                   ` (8 more replies)
  0 siblings, 9 replies; 10+ messages in thread
From: Hallgrimur H. Gunnarsson @ 2026-10-28 22:16 UTC (permalink / raw)
  To: Christian Brauner, Alexander Viro, Paul Moore, Serge Hallyn,
	Kees Cook, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: Jan Kara, James Morris, Simon Horman, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Jann Horn, Günther Noack, KP Singh,
	Jonathan Calmels, Casey Schaufler, linux-fsdevel,
	linux-security-module, linux-api, containers, netdev, linux-doc,
	linux-kselftest, linux-kernel, Hallgrimur H. Gunnarsson

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.

[1] https://lore.kernel.org/all/20240609104355.442002-1-jcalmels@3xx0.net/
[2] https://lwn.net/Articles/978846/

Note on tree routing:

While Patch 1 (CLONE_NEWNET_LOOPBACK) touches net/core/net_namespace.c,
most of its changes add clone3()/unshare() flag plumbing in
include/uapi/linux/sched.h, kernel/fork.c, and kernel/nsproxy.c, which
Patch 3 (CLONE_NEWUSER_BSET) builds directly on top of. To avoid
merge conflicts in kernel/fork.c and include/uapi/linux/sched.h, it
may be simplest to route the entire series through a single tree, e.g.
a namespace branch in vfs.git with Acked-by from the netdev and
capabilities/credentials maintainers, or whichever tree the maintainers
prefer.

Hallgrimur H. Gunnarsson (8):
  net: introduce CLONE_NEWNET_LOOPBACK to auto-enable loopback
  userns: introduce capability bounding set for user namespaces
  userns: allow restricting capability bounding set via clone3()
  nsfs: add NS_USER_GET_INFO ioctl to inspect user namespace
    capabilities
  selftests/net: add test suite for CLONE_NEWNET_LOOPBACK
  selftests/capabilities: tests for user namespace capability bounding
    set
  selftests/capabilities: add clone3() tests for CLONE_NEWUSER_BSET
  selftests/nsfs: add tests for NS_USER_GET_INFO ioctl

 Documentation/admin-guide/sysctl/kernel.rst   |  37 ++
 fs/nsfs.c                                     |  29 +
 include/linux/cred.h                          |   3 +-
 include/linux/sched/task.h                    |   1 +
 include/linux/user_namespace.h                |  20 +-
 include/uapi/linux/nsfs.h                     |  17 +
 include/uapi/linux/sched.h                    |   9 +
 kernel/cred.c                                 |  10 +-
 kernel/fork.c                                 |  38 +-
 kernel/nsproxy.c                              |   9 +
 kernel/user.c                                 |   1 +
 kernel/user_namespace.c                       | 100 +++-
 net/core/net_namespace.c                      |  11 +
 security/commoncap.c                          |   8 +-
 .../testing/selftests/capabilities/.gitignore |   1 +
 tools/testing/selftests/capabilities/Makefile |   2 +-
 .../capabilities/userns_bounding_set_test.c   | 566 ++++++++++++++++++
 .../selftests/clone3/clone3_selftests.h       |   1 +
 .../selftests/filesystems/nsfs/.gitignore     |   1 +
 .../selftests/filesystems/nsfs/Makefile       |   4 +-
 .../selftests/filesystems/nsfs/userns.c       | 120 ++++
 tools/testing/selftests/net/.gitignore        |   1 +
 tools/testing/selftests/net/Makefile          |   1 +
 tools/testing/selftests/net/loopback_netns.c  | 103 ++++
 24 files changed, 1069 insertions(+), 24 deletions(-)
 create mode 100644 tools/testing/selftests/capabilities/userns_bounding_set_test.c
 create mode 100644 tools/testing/selftests/filesystems/nsfs/userns.c
 create mode 100644 tools/testing/selftests/net/loopback_netns.c


base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
-- 
2.56.0.313.g2479322e68-goog


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 1/8] net: introduce CLONE_NEWNET_LOOPBACK to auto-enable loopback
  2026-10-28 22:16 [PATCH 0/8] userns: per-namespace capability bounding set and opt-in netns loopback Hallgrimur H. Gunnarsson
@ 2026-10-28 22:16 ` Hallgrimur H. Gunnarsson
  2026-10-28 22:16 ` [PATCH 2/8] userns: introduce capability bounding set for user namespaces Hallgrimur H. Gunnarsson
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Hallgrimur H. Gunnarsson @ 2026-10-28 22:16 UTC (permalink / raw)
  To: Christian Brauner, Alexander Viro, Paul Moore, Serge Hallyn,
	Kees Cook, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: Jan Kara, James Morris, Simon Horman, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Jann Horn, Günther Noack, KP Singh,
	Jonathan Calmels, Casey Schaufler, linux-fsdevel,
	linux-security-module, linux-api, containers, netdev, linux-doc,
	linux-kselftest, linux-kernel, Hallgrimur H. Gunnarsson

New network namespaces start with the loopback interface DOWN by
default, requiring CAP_NET_ADMIN over the owning user namespace to
bring it up.

To block external network access while preserving the localhost
networking that many applications expect, sandboxes (such as bwrap,
nsjail and minijail) 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 or sandbox manager must start with
CAP_NET_ADMIN in its user namespace to be able to bring up loopback,
drop it, and then take precautions to block CAP_NET_ADMIN from being
regained in nested user namespaces (e.g. via seccomp or
max_user_namespaces limits). This adds considerable complexity just
to bring up loopback.

Conversely, if CAP_NET_ADMIN is restricted at user namespace creation
time, tasks inside the new namespace never acquire CAP_NET_ADMIN.
While this removes the CAP_NET_ADMIN attack surface down the
namespace hierarchy, it also prevents userspace from bringing loopback
up, breaking localhost networking for unprivileged sandboxes.

Introduce the CLONE_NEWNET_LOOPBACK flag for clone3() and the
corresponding UNSHARE_NEWNET_LOOPBACK flag for unshare(). When
specified, each flag implies CLONE_NEWNET and automatically brings 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.

Signed-off-by: Hallgrimur H. Gunnarsson <halg@google.com>
---
 include/uapi/linux/sched.h |  2 ++
 kernel/fork.c              | 22 ++++++++++++++++++----
 kernel/nsproxy.c           |  9 +++++++++
 net/core/net_namespace.c   | 11 +++++++++++
 4 files changed, 40 insertions(+), 4 deletions(-)

diff --git a/include/uapi/linux/sched.h b/include/uapi/linux/sched.h
index 33a4624285cd..efd00d9f8bd3 100644
--- a/include/uapi/linux/sched.h
+++ b/include/uapi/linux/sched.h
@@ -40,6 +40,7 @@
 #define CLONE_NNP		(1ULL << 35) /* Set no_new_privs on child. */
 #define CLONE_PIDFD_AUTOKILL	(1ULL << 36) /* Kill child when clone pidfd closes. */
 #define CLONE_EMPTY_MNTNS	(1ULL << 37) /* Create an empty mount namespace. */
+#define CLONE_NEWNET_LOOPBACK	(1ULL << 38) /* Create network namespace with loopback UP. */
 
 /*
  * cloning flags intersect with CSIGNAL so can be used with unshare and clone3
@@ -52,6 +53,7 @@
  * unshare syscall:
  */
 #define UNSHARE_EMPTY_MNTNS 0x00100000 /* Unshare an empty mount namespace. */
+#define UNSHARE_NEWNET_LOOPBACK 0x00200000 /* Unshare network namespace with loopback UP. */
 
 #ifndef __ASSEMBLY__
 /**
diff --git a/kernel/fork.c b/kernel/fork.c
index 416758c8a3d4..c0f7520e754a 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -2728,6 +2728,15 @@ pid_t kernel_clone(struct kernel_clone_args *args)
 		args->flags = clone_flags;
 	}
 
+	/*
+	 * Set implied CLONE_NEWNET before copy_process() so that
+	 * security_task_alloc() and other clone_flags checks see it.
+	 */
+	if (clone_flags & CLONE_NEWNET_LOOPBACK) {
+		clone_flags |= CLONE_NEWNET;
+		args->flags = clone_flags;
+	}
+
 	/*
 	 * For legacy clone() calls, CLONE_PIDFD uses the parent_tid argument
 	 * to return the pidfd. Hence, CLONE_PIDFD and CLONE_PARENT_SETTID are
@@ -3010,7 +3019,8 @@ static bool clone3_args_valid(struct kernel_clone_args *kargs)
 	if (kargs->flags &
 	    ~(CLONE_LEGACY_FLAGS | CLONE_CLEAR_SIGHAND |
 	      CLONE_INTO_CGROUP | CLONE_AUTOREAP | CLONE_NNP |
-	      CLONE_PIDFD_AUTOKILL | CLONE_EMPTY_MNTNS))
+	      CLONE_PIDFD_AUTOKILL | CLONE_EMPTY_MNTNS |
+	      CLONE_NEWNET_LOOPBACK))
 		return false;
 
 	/*
@@ -3160,9 +3170,10 @@ void __init proc_caches_init(void)
  */
 static int check_unshare_flags(unsigned long unshare_flags)
 {
-	if (unshare_flags & ~(CLONE_THREAD|CLONE_FS|CLONE_SIGHAND|
-				CLONE_VM|CLONE_FILES|CLONE_SYSVSEM|
-				CLONE_NS_ALL | UNSHARE_EMPTY_MNTNS))
+	if (unshare_flags & ~(CLONE_THREAD | CLONE_FS | CLONE_SIGHAND |
+				CLONE_VM | CLONE_FILES | CLONE_SYSVSEM |
+				CLONE_NS_ALL | UNSHARE_EMPTY_MNTNS |
+				UNSHARE_NEWNET_LOOPBACK))
 		return -EINVAL;
 	/*
 	 * Not implemented, but pretend it works if there is nothing
@@ -3266,6 +3277,9 @@ int ksys_unshare(unsigned long unshare_flags)
 	if (unshare_flags & CLONE_NEWNS)
 		unshare_flags |= CLONE_FS;
 
+	if (unshare_flags & UNSHARE_NEWNET_LOOPBACK)
+		unshare_flags |= CLONE_NEWNET;
+
 	/* No unsharing with overriden fs state */
 	VFS_WARN_ON_ONCE(unshare_flags & (CLONE_NEWNS | CLONE_FS) &&
 			 current->fs != current->real_fs);
diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c
index d9d3d5973bf5..4e30c6a9f8ed 100644
--- a/kernel/nsproxy.c
+++ b/kernel/nsproxy.c
@@ -231,6 +231,15 @@ int unshare_nsproxy_namespaces(unsigned long unshare_flags,
 		flags |= CLONE_EMPTY_MNTNS;
 	}
 
+	/*
+	 * Convert the 32-bit UNSHARE_NEWNET_LOOPBACK (which aliases
+	 * CLONE_CHILD_CLEARTID) to the unique 64-bit CLONE_NEWNET_LOOPBACK.
+	 */
+	if (flags & UNSHARE_NEWNET_LOOPBACK) {
+		flags &= ~(u64)UNSHARE_NEWNET_LOOPBACK;
+		flags |= CLONE_NEWNET_LOOPBACK;
+	}
+
 	*new_nsp = create_new_namespaces(flags, current, user_ns,
 					 new_fs ? new_fs : current->fs);
 	if (IS_ERR(*new_nsp)) {
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index da5f881fbd3b..70972639932a 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -596,6 +596,17 @@ struct net *copy_net_ns(u64 flags,
 		dec_net_namespaces(ucounts);
 		return ERR_PTR(rv);
 	}
+
+	if (flags & CLONE_NEWNET_LOOPBACK) {
+		rtnl_net_lock(net);
+		rv = dev_open(net->loopback_dev, NULL);
+		rtnl_net_unlock(net);
+		if (rv < 0) {
+			put_net(net);
+			return ERR_PTR(rv);
+		}
+	}
+
 	return net;
 }
 
-- 
2.56.0.313.g2479322e68-goog


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 2/8] userns: introduce capability bounding set for user namespaces
  2026-10-28 22:16 [PATCH 0/8] userns: per-namespace capability bounding set and opt-in netns loopback Hallgrimur H. Gunnarsson
  2026-10-28 22:16 ` [PATCH 1/8] net: introduce CLONE_NEWNET_LOOPBACK to auto-enable loopback Hallgrimur H. Gunnarsson
@ 2026-10-28 22:16 ` Hallgrimur H. Gunnarsson
  2026-10-28 22:17 ` [PATCH 3/8] userns: allow restricting capability bounding set via clone3() Hallgrimur H. Gunnarsson
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Hallgrimur H. Gunnarsson @ 2026-10-28 22:16 UTC (permalink / raw)
  To: Christian Brauner, Alexander Viro, Paul Moore, Serge Hallyn,
	Kees Cook, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: Jan Kara, James Morris, Simon Horman, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Jann Horn, Günther Noack, KP Singh,
	Jonathan Calmels, Casey Schaufler, linux-fsdevel,
	linux-security-module, linux-api, containers, netdev, linux-doc,
	linux-kselftest, linux-kernel, Hallgrimur H. Gunnarsson

When an unprivileged process creates a new user namespace, it receives
a full set of capabilities scoped to that new namespace.

While these capabilities are isolated to the namespace, they allow
unprivileged tasks to interact with deep kernel code (like networking
logic) that historically assumed only a fully trusted root user could
access them. Depending on the workload, administrators may not always
want or need to grant every capability to unprivileged namespaces.

To provide administrators with flexible policy control over what
privileges are permitted by default, introduce a per-user-namespace
capability bounding set (cap_bset) and a global sysctl ceiling knob at:
    /proc/sys/kernel/unprivileged_userns_caps_allowed

When a new user namespace is created, it inherits the capability
bounding set of its parent namespace. For an unprivileged creator (i.e.
a task lacking CAP_SYS_ADMIN in the parent namespace), the child
namespace's bounding set is constrained by intersecting the parent's
bounding set with the sysctl ceiling combined with any effective
capabilities the creator already holds:
    child_bset = parent_bset & (sysctl_ceiling | creator_effective)

Capability bounding sets decrease monotonically down the namespace
hierarchy, ensuring child namespaces can never exceed the capabilities
of their ancestors.

Upon entering a user namespace during creation or via setns, the task's
permitted, effective, and bounding capability sets are clamped to the
namespace's bounding set. Furthermore, cap_capable_helper() is updated
so that implicit capabilities granted to parent-namespace owners over
child namespaces are strictly bounded by the owned namespace's
cap_bset.

The sysctl defaults to CAP_FULL_SET, preserving baseline backwards
compatibility and existing behavior unless an administrator explicitly
configures a restricted ceiling.

Signed-off-by: Hallgrimur H. Gunnarsson <halg@google.com>
---
 Documentation/admin-guide/sysctl/kernel.rst | 32 +++++++
 include/linux/user_namespace.h              | 16 ++++
 kernel/user.c                               |  1 +
 kernel/user_namespace.c                     | 93 +++++++++++++++++++--
 security/commoncap.c                        |  8 +-
 5 files changed, 142 insertions(+), 8 deletions(-)

diff --git a/Documentation/admin-guide/sysctl/kernel.rst b/Documentation/admin-guide/sysctl/kernel.rst
index b6328cd0f43e..25fe46e7724b 100644
--- a/Documentation/admin-guide/sysctl/kernel.rst
+++ b/Documentation/admin-guide/sysctl/kernel.rst
@@ -1690,6 +1690,38 @@ entry will default to 2 instead of 0.
 = =============================================================
 
 
+unprivileged_userns_caps_allowed
+================================
+
+This file contains a hexadecimal bitmask representing the capability bounding
+ceiling enforced on unprivileged user namespace creation. Instead of granting
+full capabilities upon namespace creation, this ceiling can be used to
+configure which capabilities are permitted.
+
+When an unprivileged task (a task lacking ``CAP_SYS_ADMIN`` in its parent user
+namespace) creates a new user namespace, the capability bounding set of the
+new namespace is intersected with the union of this mask and the creator's
+effective capabilities in the parent namespace:
+
+  child_bset = parent_bset & (sysctl_ceiling | creator_effective)
+
+Privileged creators (holding ``CAP_SYS_ADMIN`` in the parent namespace) are
+unaffected by this ceiling and inherit the parent namespace's bounding set
+unmodified.
+
+Upon entering a user namespace during creation or via ``setns(2)``,
+the task's permitted, effective, and bounding capability sets are clamped
+to the namespace's bounding set. Tasks inside the namespace cannot exceed
+this ceiling, even when executing setuid binaries or binaries with file
+capabilities.
+
+Unknown capability bits (above ``CAP_LAST_CAP``) are ignored and masked
+off when writing to this file.
+
+* Default: ``CAP_FULL_SET`` (all capabilities permitted)
+* Privilege required to modify: ``CAP_SETPCAP`` in the initial user namespace.
+
+
 warn_limit
 ==========
 
diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
index e38d9e60569f..a0ccfb93f2a7 100644
--- a/include/linux/user_namespace.h
+++ b/include/linux/user_namespace.h
@@ -3,6 +3,7 @@
 #define _LINUX_USER_NAMESPACE_H
 
 #include <linux/kref.h>
+#include <linux/capability.h>
 #include <linux/nsproxy.h>
 #include <linux/ns_common.h>
 #include <linux/rculist_nulls.h>
@@ -89,6 +90,11 @@ struct user_namespace {
 	/* parent_could_setfcap: true if the creator if this ns had CAP_SETFCAP
 	 * in its effective capability set at the child ns creation time. */
 	bool			parent_could_setfcap;
+	/*
+	 * Capability bounding set for tasks inside the namespace and for owner
+	 * tasks in the parent namespace.
+	 */
+	kernel_cap_t		cap_bset;
 
 #ifdef CONFIG_KEYS
 	/* List of joinable keyrings in this namespace.  Modification access of
@@ -183,6 +189,11 @@ static inline struct user_namespace *get_user_ns(struct user_namespace *ns)
 	return ns;
 }
 
+static inline bool userns_has_cap(const struct user_namespace *ns, int cap)
+{
+	return cap_raised(ns->cap_bset, cap);
+}
+
 extern int create_user_ns(struct cred *new);
 extern int unshare_userns(unsigned long unshare_flags, struct cred **new_cred);
 extern void __put_user_ns(struct user_namespace *ns);
@@ -214,6 +225,11 @@ static inline struct user_namespace *get_user_ns(struct user_namespace *ns)
 	return &init_user_ns;
 }
 
+static inline bool userns_has_cap(const struct user_namespace *ns, int cap)
+{
+	return true;
+}
+
 static inline int create_user_ns(struct cred *new)
 {
 	return -EINVAL;
diff --git a/kernel/user.c b/kernel/user.c
index 21bafdc11379..78f4c5e45dcc 100644
--- a/kernel/user.c
+++ b/kernel/user.c
@@ -69,6 +69,7 @@ struct user_namespace init_user_ns = {
 	.owner = GLOBAL_ROOT_UID,
 	.group = GLOBAL_ROOT_GID,
 	.flags = USERNS_INIT_FLAGS,
+	.cap_bset = CAP_FULL_SET,
 #ifdef CONFIG_KEYS
 	.keyring_name_list = LIST_HEAD_INIT(init_user_ns.keyring_name_list),
 	.keyring_sem = __RWSEM_INITIALIZER(init_user_ns.keyring_sem),
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 0bed462e9b2a..92f489e268a8 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -3,6 +3,9 @@
 #include <linux/export.h>
 #include <linux/nsproxy.h>
 #include <linux/slab.h>
+#include <linux/capability.h>
+#include <linux/string.h>
+#include <linux/sysctl.h>
 #include <linux/sched/signal.h>
 #include <linux/user_namespace.h>
 #include <linux/proc_ns.h>
@@ -41,17 +44,77 @@ static void dec_user_namespaces(struct ucounts *ucounts)
 	return dec_ucount(ucounts, UCOUNT_USER_NAMESPACES);
 }
 
+static kernel_cap_t sysctl_unpriv_userns_bset = CAP_FULL_SET;
+
+static int unpriv_userns_bset_handler(const struct ctl_table *table,
+				      int write, void *buffer,
+				      size_t *lenp, loff_t *ppos)
+{
+	struct ctl_table tmp_table = *table;
+	char buf[32] = {};
+	unsigned long long val;
+	char *s;
+	int ret;
+
+	if (!*lenp)
+		return 0;
+	if (write && *ppos)
+		return -EINVAL;
+	if (write && !capable(CAP_SETPCAP))
+		return -EPERM;
+
+	if (!write) {
+		kernel_cap_t unpriv_bset = READ_ONCE(sysctl_unpriv_userns_bset);
+
+		snprintf(buf, sizeof(buf), "%016llx", unpriv_bset.val);
+	}
+
+	tmp_table.data = buf;
+	tmp_table.maxlen = sizeof(buf);
+
+	ret = proc_dostring(&tmp_table, write, buffer, lenp, ppos);
+	if (ret || !write)
+		return ret;
+
+	s = strim(buf);
+	ret = kstrtoull(s, 16, &val);
+	if (ret)
+		return ret;
+
+	val &= CAP_VALID_MASK;
+	WRITE_ONCE(sysctl_unpriv_userns_bset, (kernel_cap_t){ .val = val });
+	return 0;
+}
+
+static const struct ctl_table user_namespace_sysctls[] = {
+	{
+		.procname	= "unprivileged_userns_caps_allowed",
+		.mode		= 0644,
+		.proc_handler	= unpriv_userns_bset_handler,
+	},
+};
+
+static int __init init_user_ns_sysctls(void)
+{
+	register_sysctl_init("kernel", user_namespace_sysctls);
+	return 0;
+}
+subsys_initcall(init_user_ns_sysctls);
+
 static void set_cred_user_ns(struct cred *cred, struct user_namespace *user_ns)
 {
-	/* Start with the same capabilities as init but useless for doing
-	 * anything as the capabilities are bound to the new user namespace.
+	/*
+	 * Tasks inside the user namespace start with (and are permanently
+	 * bounded by) the capabilities permitted by the user namespace
+	 * capability ceiling. The capabilities are scoped to the new user
+	 * namespace.
 	 */
 	cred->securebits = SECUREBITS_DEFAULT;
+	cred->cap_bset = user_ns->cap_bset;
+	cred->cap_permitted = user_ns->cap_bset;
+	cred->cap_effective = user_ns->cap_bset;
 	cred->cap_inheritable = CAP_EMPTY_SET;
-	cred->cap_permitted = CAP_FULL_SET;
-	cred->cap_effective = CAP_FULL_SET;
 	cred->cap_ambient = CAP_EMPTY_SET;
-	cred->cap_bset = CAP_FULL_SET;
 #ifdef CONFIG_KEYS
 	key_put(cred->request_key_auth);
 	cred->request_key_auth = NULL;
@@ -86,6 +149,7 @@ int create_user_ns(struct cred *new)
 	kuid_t owner = new->euid;
 	kgid_t group = new->egid;
 	struct ucounts *ucounts;
+	kernel_cap_t cap_bset;
 	int ret, i;
 
 	ret = -ENOSPC;
@@ -119,6 +183,24 @@ int create_user_ns(struct cred *new)
 	if (ret < 0)
 		goto fail_dec;
 
+	/*
+	 * If the caller has CAP_SYS_ADMIN in the parent namespace,
+	 * they are a privileged creator and inherit the parent's
+	 * bounding set directly.
+	 *
+	 * Otherwise, enforce the unprivileged bounding ceiling, but
+	 * allow the child namespace to retain any capabilities the
+	 * creator already holds.
+	 */
+	cap_bset = parent_ns->cap_bset;
+	if (!ns_capable_noaudit(parent_ns, CAP_SYS_ADMIN)) {
+		kernel_cap_t unpriv_bset = READ_ONCE(sysctl_unpriv_userns_bset);
+		kernel_cap_t allowed = cap_combine(unpriv_bset,
+						   new->cap_effective);
+
+		cap_bset = cap_intersect(cap_bset, allowed);
+	}
+
 	ret = -ENOMEM;
 	ns = kmem_cache_zalloc(user_ns_cachep, GFP_KERNEL);
 	if (!ns)
@@ -144,6 +226,7 @@ int create_user_ns(struct cred *new)
 	set_userns_rlimit_max(ns, UCOUNT_RLIMIT_SIGPENDING, rlimit(RLIMIT_SIGPENDING));
 	set_userns_rlimit_max(ns, UCOUNT_RLIMIT_MEMLOCK, rlimit(RLIMIT_MEMLOCK));
 	ns->ucounts = ucounts;
+	ns->cap_bset = cap_bset;
 
 	/* Inherit USERNS_SETGROUPS_ALLOWED from our parent */
 	mutex_lock(&userns_state_mutex);
diff --git a/security/commoncap.c b/security/commoncap.c
index 3399535808fe..f153e7bad1ba 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -88,11 +88,13 @@ static inline int cap_capable_helper(const struct cred *cred,
 		if (ns->level <= cred_ns->level)
 			return -EPERM;
 
-		/* 
+		/*
 		 * The owner of the user namespace in the parent of the
-		 * user namespace has all caps.
+		 * user namespace has all caps, subject to the bounding
+		 * set of the owned user namespace.
 		 */
-		if ((ns->parent == cred_ns) && uid_eq(ns->owner, cred->euid))
+		if (ns->parent == cred_ns && uid_eq(ns->owner, cred->euid) &&
+		    userns_has_cap(ns, cap))
 			return 0;
 
 		/*
-- 
2.56.0.313.g2479322e68-goog


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 3/8] userns: allow restricting capability bounding set via clone3()
  2026-10-28 22:16 [PATCH 0/8] userns: per-namespace capability bounding set and opt-in netns loopback Hallgrimur H. Gunnarsson
  2026-10-28 22:16 ` [PATCH 1/8] net: introduce CLONE_NEWNET_LOOPBACK to auto-enable loopback Hallgrimur H. Gunnarsson
  2026-10-28 22:16 ` [PATCH 2/8] userns: introduce capability bounding set for user namespaces Hallgrimur H. Gunnarsson
@ 2026-10-28 22:17 ` Hallgrimur H. Gunnarsson
  2026-10-28 22:17 ` [PATCH 4/8] nsfs: add NS_USER_GET_INFO ioctl to inspect user namespace capabilities Hallgrimur H. Gunnarsson
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Hallgrimur H. Gunnarsson @ 2026-10-28 22:17 UTC (permalink / raw)
  To: Christian Brauner, Alexander Viro, Paul Moore, Serge Hallyn,
	Kees Cook, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: Jan Kara, James Morris, Simon Horman, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Jann Horn, Günther Noack, KP Singh,
	Jonathan Calmels, Casey Schaufler, linux-fsdevel,
	linux-security-module, linux-api, containers, netdev, linux-doc,
	linux-kselftest, linux-kernel, Hallgrimur H. Gunnarsson

While the unprivileged_userns_caps_allowed sysctl enforces a
system-wide capability ceiling for unprivileged users, the
clone3() support allows sandboxes and container runtimes to
voluntarily restrict the capability bounding set of a new user
namespace on a per-namespace basis.

Introduce the CLONE_NEWUSER_BSET flag and add a cap_bset field
to struct clone_args (CLONE_ARGS_SIZE_VER3). When specified alongside
CLONE_NEWUSER, the new user namespace's capability bounding set is
intersected with cap_bset in addition to the parent namespace's
bounding set and any applicable unprivileged sysctl ceiling.

Signed-off-by: Hallgrimur H. Gunnarsson <halg@google.com>
---
 Documentation/admin-guide/sysctl/kernel.rst |  5 +++++
 include/linux/cred.h                        |  3 ++-
 include/linux/sched/task.h                  |  1 +
 include/linux/user_namespace.h              |  4 ++--
 include/uapi/linux/sched.h                  |  7 +++++++
 kernel/cred.c                               | 10 ++++++++--
 kernel/fork.c                               | 18 +++++++++++++++---
 kernel/user_namespace.c                     | 11 +++++++----
 8 files changed, 47 insertions(+), 12 deletions(-)

diff --git a/Documentation/admin-guide/sysctl/kernel.rst b/Documentation/admin-guide/sysctl/kernel.rst
index 25fe46e7724b..4b6654e89c6d 100644
--- a/Documentation/admin-guide/sysctl/kernel.rst
+++ b/Documentation/admin-guide/sysctl/kernel.rst
@@ -1709,6 +1709,11 @@ Privileged creators (holding ``CAP_SYS_ADMIN`` in the parent namespace) are
 unaffected by this ceiling and inherit the parent namespace's bounding set
 unmodified.
 
+Regardless of privilege level, a creator using ``clone3(2)`` with the
+``CLONE_NEWUSER_BSET`` flag can further restrict the new namespace's
+bounding set by specifying a ``cap_bset`` mask in ``struct clone_args``,
+which is intersected with the resulting bounding set.
+
 Upon entering a user namespace during creation or via ``setns(2)``,
 the task's permitted, effective, and bounding capability sets are clamped
 to the namespace's bounding set. Tasks inside the namespace cannot exceed
diff --git a/include/linux/cred.h b/include/linux/cred.h
index 6ef1750c93e2..39eeb1584ef7 100644
--- a/include/linux/cred.h
+++ b/include/linux/cred.h
@@ -19,6 +19,7 @@
 
 struct cred;
 struct inode;
+struct kernel_clone_args;
 
 extern struct task_struct init_task;
 
@@ -150,9 +151,9 @@ struct cred {
 	};
 } __randomize_layout;
 
+int copy_creds(struct task_struct *p, const struct kernel_clone_args *kargs);
 extern void __put_cred(struct cred *);
 extern void exit_creds(struct task_struct *);
-extern int copy_creds(struct task_struct *, u64);
 extern const struct cred *get_task_cred(struct task_struct *);
 extern struct cred *cred_alloc_blank(void);
 extern struct cred *prepare_creds(void);
diff --git a/include/linux/sched/task.h b/include/linux/sched/task.h
index e0c1ca8c6a18..498db7adce5f 100644
--- a/include/linux/sched/task.h
+++ b/include/linux/sched/task.h
@@ -45,6 +45,7 @@ struct kernel_clone_args {
 	struct cgroup *cgrp;
 	struct css_set *cset;
 	unsigned int kill_seq;
+	u64 cap_bset;
 };
 
 /*
diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
index a0ccfb93f2a7..a584ee5775af 100644
--- a/include/linux/user_namespace.h
+++ b/include/linux/user_namespace.h
@@ -194,7 +194,7 @@ static inline bool userns_has_cap(const struct user_namespace *ns, int cap)
 	return cap_raised(ns->cap_bset, cap);
 }
 
-extern int create_user_ns(struct cred *new);
+int create_user_ns(struct cred *new, kernel_cap_t req_bset);
 extern int unshare_userns(unsigned long unshare_flags, struct cred **new_cred);
 extern void __put_user_ns(struct user_namespace *ns);
 
@@ -230,7 +230,7 @@ static inline bool userns_has_cap(const struct user_namespace *ns, int cap)
 	return true;
 }
 
-static inline int create_user_ns(struct cred *new)
+static inline int create_user_ns(struct cred *new, kernel_cap_t req_bset)
 {
 	return -EINVAL;
 }
diff --git a/include/uapi/linux/sched.h b/include/uapi/linux/sched.h
index efd00d9f8bd3..10e4a4733005 100644
--- a/include/uapi/linux/sched.h
+++ b/include/uapi/linux/sched.h
@@ -41,6 +41,7 @@
 #define CLONE_PIDFD_AUTOKILL	(1ULL << 36) /* Kill child when clone pidfd closes. */
 #define CLONE_EMPTY_MNTNS	(1ULL << 37) /* Create an empty mount namespace. */
 #define CLONE_NEWNET_LOOPBACK	(1ULL << 38) /* Create network namespace with loopback UP. */
+#define CLONE_NEWUSER_BSET	(1ULL << 39) /* Set userns capability bounding set. */
 
 /*
  * cloning flags intersect with CSIGNAL so can be used with unshare and clone3
@@ -96,6 +97,10 @@
  *                kernel's limit of nested PID namespaces.
  * @cgroup:       If CLONE_INTO_CGROUP is specified set this to
  *                a file descriptor for the cgroup.
+ * @cap_bset:     If CLONE_NEWUSER_BSET is specified, this
+ *                specifies the capability bounding set mask to
+ *                apply to the new user namespace. Unknown
+ *                capability bits (> CAP_LAST_CAP) are ignored.
  *
  * The structure is versioned by size and thus extensible.
  * New struct members must go at the end of the struct and
@@ -113,12 +118,14 @@ struct clone_args {
 	__aligned_u64 set_tid;
 	__aligned_u64 set_tid_size;
 	__aligned_u64 cgroup;
+	__aligned_u64 cap_bset;
 };
 #endif
 
 #define CLONE_ARGS_SIZE_VER0 64 /* sizeof first published struct */
 #define CLONE_ARGS_SIZE_VER1 80 /* sizeof second published struct */
 #define CLONE_ARGS_SIZE_VER2 88 /* sizeof third published struct */
+#define CLONE_ARGS_SIZE_VER3 96 /* sizeof fourth published struct */
 
 /*
  * Scheduling policies
diff --git a/kernel/cred.c b/kernel/cred.c
index 3df4e15bd67f..20f37a351602 100644
--- a/kernel/cred.c
+++ b/kernel/cred.c
@@ -260,8 +260,9 @@ struct cred *prepare_exec_creds(void)
  * The new process gets the current process's subjective credentials as its
  * objective and subjective credentials
  */
-int copy_creds(struct task_struct *p, u64 clone_flags)
+int copy_creds(struct task_struct *p, const struct kernel_clone_args *kargs)
 {
+	u64 clone_flags = kargs->flags;
 	struct cred *new;
 	int ret;
 
@@ -288,7 +289,12 @@ int copy_creds(struct task_struct *p, u64 clone_flags)
 		return -ENOMEM;
 
 	if (clone_flags & CLONE_NEWUSER) {
-		ret = create_user_ns(new);
+		kernel_cap_t cap_bset = CAP_FULL_SET;
+
+		if (clone_flags & CLONE_NEWUSER_BSET)
+			cap_bset = (kernel_cap_t){ .val = kargs->cap_bset };
+
+		ret = create_user_ns(new, cap_bset);
 		if (ret < 0)
 			goto error_put;
 		ret = set_cred_ucounts(new);
diff --git a/kernel/fork.c b/kernel/fork.c
index c0f7520e754a..a09f62cadeac 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -2168,7 +2168,7 @@ __latent_entropy struct task_struct *copy_process(
 #ifdef CONFIG_PROVE_LOCKING
 	DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
 #endif
-	retval = copy_creds(p, clone_flags);
+	retval = copy_creds(p, args);
 	if (retval < 0)
 		goto bad_fork_free;
 
@@ -2932,7 +2932,9 @@ static noinline int copy_clone_args_from_user(struct kernel_clone_args *kargs,
 		     CLONE_ARGS_SIZE_VER1);
 	BUILD_BUG_ON(offsetofend(struct clone_args, cgroup) !=
 		     CLONE_ARGS_SIZE_VER2);
-	BUILD_BUG_ON(sizeof(struct clone_args) != CLONE_ARGS_SIZE_VER2);
+	BUILD_BUG_ON(offsetofend(struct clone_args, cap_bset) !=
+		     CLONE_ARGS_SIZE_VER3);
+	BUILD_BUG_ON(sizeof(struct clone_args) != CLONE_ARGS_SIZE_VER3);
 
 	if (unlikely(usize > PAGE_SIZE))
 		return -E2BIG;
@@ -2962,6 +2964,11 @@ static noinline int copy_clone_args_from_user(struct kernel_clone_args *kargs,
 	    (args.cgroup > INT_MAX || usize < CLONE_ARGS_SIZE_VER2))
 		return -EINVAL;
 
+	if ((args.flags & CLONE_NEWUSER_BSET) && usize < CLONE_ARGS_SIZE_VER3)
+		return -EINVAL;
+	if (!(args.flags & CLONE_NEWUSER_BSET) && args.cap_bset)
+		return -EINVAL;
+
 	*kargs = (struct kernel_clone_args){
 		.flags		= args.flags,
 		.pidfd		= u64_to_user_ptr(args.pidfd),
@@ -2973,6 +2980,7 @@ static noinline int copy_clone_args_from_user(struct kernel_clone_args *kargs,
 		.tls		= args.tls,
 		.set_tid_size	= args.set_tid_size,
 		.cgroup		= args.cgroup,
+		.cap_bset	= args.cap_bset & CAP_VALID_MASK,
 	};
 
 	if (args.set_tid &&
@@ -3020,7 +3028,7 @@ static bool clone3_args_valid(struct kernel_clone_args *kargs)
 	    ~(CLONE_LEGACY_FLAGS | CLONE_CLEAR_SIGHAND |
 	      CLONE_INTO_CGROUP | CLONE_AUTOREAP | CLONE_NNP |
 	      CLONE_PIDFD_AUTOKILL | CLONE_EMPTY_MNTNS |
-	      CLONE_NEWNET_LOOPBACK))
+	      CLONE_NEWNET_LOOPBACK | CLONE_NEWUSER_BSET))
 		return false;
 
 	/*
@@ -3038,6 +3046,10 @@ static bool clone3_args_valid(struct kernel_clone_args *kargs)
 	    kargs->exit_signal)
 		return false;
 
+	if ((kargs->flags & CLONE_NEWUSER_BSET) &&
+	    !(kargs->flags & CLONE_NEWUSER))
+		return false;
+
 	if (!clone3_stack_valid(kargs))
 		return false;
 
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 92f489e268a8..6e95dd1a1d04 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -143,7 +143,7 @@ static unsigned long enforced_nproc_rlimit(void)
  * This is called by copy_creds(), which will finish setting the target task's
  * credentials.
  */
-int create_user_ns(struct cred *new)
+int create_user_ns(struct cred *new, kernel_cap_t req_bset)
 {
 	struct user_namespace *ns, *parent_ns = new->user_ns;
 	kuid_t owner = new->euid;
@@ -184,15 +184,18 @@ int create_user_ns(struct cred *new)
 		goto fail_dec;
 
 	/*
+	 * Intersect the parent's bounding set with any voluntary
+	 * restrictions requested via clone3().
+	 *
 	 * If the caller has CAP_SYS_ADMIN in the parent namespace,
-	 * they are a privileged creator and inherit the parent's
+	 * they are a privileged creator and inherit this resulting
 	 * bounding set directly.
 	 *
 	 * Otherwise, enforce the unprivileged bounding ceiling, but
 	 * allow the child namespace to retain any capabilities the
 	 * creator already holds.
 	 */
-	cap_bset = parent_ns->cap_bset;
+	cap_bset = cap_intersect(parent_ns->cap_bset, req_bset);
 	if (!ns_capable_noaudit(parent_ns, CAP_SYS_ADMIN)) {
 		kernel_cap_t unpriv_bset = READ_ONCE(sysctl_unpriv_userns_bset);
 		kernel_cap_t allowed = cap_combine(unpriv_bset,
@@ -267,7 +270,7 @@ int unshare_userns(unsigned long unshare_flags, struct cred **new_cred)
 
 	cred = prepare_creds();
 	if (cred) {
-		err = create_user_ns(cred);
+		err = create_user_ns(cred, CAP_FULL_SET);
 		if (err)
 			put_cred(cred);
 		else
-- 
2.56.0.313.g2479322e68-goog


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 4/8] nsfs: add NS_USER_GET_INFO ioctl to inspect user namespace capabilities
  2026-10-28 22:16 [PATCH 0/8] userns: per-namespace capability bounding set and opt-in netns loopback Hallgrimur H. Gunnarsson
                   ` (2 preceding siblings ...)
  2026-10-28 22:17 ` [PATCH 3/8] userns: allow restricting capability bounding set via clone3() Hallgrimur H. Gunnarsson
@ 2026-10-28 22:17 ` Hallgrimur H. Gunnarsson
  2026-10-28 22:17 ` [PATCH 5/8] selftests/net: add test suite for CLONE_NEWNET_LOOPBACK Hallgrimur H. Gunnarsson
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Hallgrimur H. Gunnarsson @ 2026-10-28 22:17 UTC (permalink / raw)
  To: Christian Brauner, Alexander Viro, Paul Moore, Serge Hallyn,
	Kees Cook, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: Jan Kara, James Morris, Simon Horman, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Jann Horn, Günther Noack, KP Singh,
	Jonathan Calmels, Casey Schaufler, linux-fsdevel,
	linux-security-module, linux-api, containers, netdev, linux-doc,
	linux-kselftest, linux-kernel, Hallgrimur H. Gunnarsson

With the introduction of a capability bounding set for user namespaces,
userspace needs a way to inspect the capability ceiling enforced on a
given user namespace.

Introduce the NS_USER_GET_INFO ioctl on nsfs file descriptors to query
user namespace metadata, mirroring the extensible structure pattern
used by NS_MNT_GET_INFO.

Signed-off-by: Hallgrimur H. Gunnarsson <halg@google.com>
---
 fs/nsfs.c                 | 29 +++++++++++++++++++++++++++++
 include/uapi/linux/nsfs.h | 17 +++++++++++++++++
 2 files changed, 46 insertions(+)

diff --git a/fs/nsfs.c b/fs/nsfs.c
index c3b6ae76594a..50711362d792 100644
--- a/fs/nsfs.c
+++ b/fs/nsfs.c
@@ -170,6 +170,17 @@ static int copy_ns_info_to_user(const struct mnt_namespace *mnt_ns,
 	return 0;
 }
 
+static int copy_user_ns_info_to_user(const struct user_namespace *user_ns,
+				     struct user_ns_info __user *uinfo,
+				     size_t usize, struct user_ns_info *kinfo)
+{
+	kinfo->size = min(usize, sizeof(*kinfo));
+	kinfo->spare = 0;
+	kinfo->cap_bset = user_ns->cap_bset.val;
+
+	return copy_struct_to_user(uinfo, usize, kinfo, sizeof(*kinfo), NULL);
+}
+
 static bool nsfs_ioctl_valid(unsigned int cmd)
 {
 	switch (cmd) {
@@ -194,6 +205,8 @@ static bool nsfs_ioctl_valid(unsigned int cmd)
 		return extensible_ioctl_valid(cmd, NS_MNT_GET_NEXT, MNT_NS_INFO_SIZE_VER0);
 	case _IOC_NR(NS_MNT_GET_PREV):
 		return extensible_ioctl_valid(cmd, NS_MNT_GET_PREV, MNT_NS_INFO_SIZE_VER0);
+	case _IOC_NR(NS_USER_GET_INFO):
+		return extensible_ioctl_valid(cmd, NS_USER_GET_INFO, USER_NS_INFO_SIZE_VER0);
 	}
 
 	return false;
@@ -360,6 +373,22 @@ static long ns_ioctl(struct file *filp, unsigned int ioctl,
 		ret = fd_publish(fdf);
 		break;
 	}
+	case _IOC_NR(NS_USER_GET_INFO): {
+		struct user_ns_info __user *uinfo = (struct user_ns_info __user *)arg;
+		struct user_ns_info kinfo = {};
+		size_t usize = _IOC_SIZE(ioctl);
+
+		if (ns->ns_type != CLONE_NEWUSER)
+			return -EINVAL;
+
+		if (!uinfo)
+			return -EINVAL;
+
+		if (usize < USER_NS_INFO_SIZE_VER0)
+			return -EINVAL;
+
+		return copy_user_ns_info_to_user(to_user_ns(ns), uinfo, usize, &kinfo);
+	}
 	default:
 		ret = -ENOTTY;
 	}
diff --git a/include/uapi/linux/nsfs.h b/include/uapi/linux/nsfs.h
index 007fed5971b4..d2996d47c857 100644
--- a/include/uapi/linux/nsfs.h
+++ b/include/uapi/linux/nsfs.h
@@ -44,6 +44,23 @@ struct mnt_ns_info {
 #define NS_GET_MNTNS_ID		_IOR(NSIO, 5,  __u64)
 #define NS_GET_ID		_IOR(NSIO, 13, __u64)
 
+/**
+ * struct user_ns_info - information about a user namespace
+ * @size: size of this structure
+ * @spare: reserved for future use
+ * @cap_bset: capability bounding set of the user namespace
+ */
+struct user_ns_info {
+	__u32 size;
+	__u32 spare;
+	__u64 cap_bset;
+};
+
+#define USER_NS_INFO_SIZE_VER0 16 /* sizeof first published struct */
+
+/* Get information about user namespace. */
+#define NS_USER_GET_INFO	_IOR(NSIO, 14, struct user_ns_info)
+
 enum init_ns_ino {
 	IPC_NS_INIT_INO		= 0xEFFFFFFFU,
 	UTS_NS_INIT_INO		= 0xEFFFFFFEU,
-- 
2.56.0.313.g2479322e68-goog


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 5/8] selftests/net: add test suite for CLONE_NEWNET_LOOPBACK
  2026-10-28 22:16 [PATCH 0/8] userns: per-namespace capability bounding set and opt-in netns loopback Hallgrimur H. Gunnarsson
                   ` (3 preceding siblings ...)
  2026-10-28 22:17 ` [PATCH 4/8] nsfs: add NS_USER_GET_INFO ioctl to inspect user namespace capabilities Hallgrimur H. Gunnarsson
@ 2026-10-28 22:17 ` Hallgrimur H. Gunnarsson
  2026-10-28 22:17 ` [PATCH 6/8] selftests/capabilities: tests for user namespace capability bounding set Hallgrimur H. Gunnarsson
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Hallgrimur H. Gunnarsson @ 2026-10-28 22:17 UTC (permalink / raw)
  To: Christian Brauner, Alexander Viro, Paul Moore, Serge Hallyn,
	Kees Cook, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: Jan Kara, James Morris, Simon Horman, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Jann Horn, Günther Noack, KP Singh,
	Jonathan Calmels, Casey Schaufler, linux-fsdevel,
	linux-security-module, linux-api, containers, netdev, linux-doc,
	linux-kselftest, linux-kernel, Hallgrimur H. Gunnarsson

Add tests in tools/testing/selftests/net to validate
CLONE_NEWNET_LOOPBACK and UNSHARE_NEWNET_LOOPBACK behavior.

Signed-off-by: Hallgrimur H. Gunnarsson <halg@google.com>
---
 tools/testing/selftests/net/.gitignore       |   1 +
 tools/testing/selftests/net/Makefile         |   1 +
 tools/testing/selftests/net/loopback_netns.c | 103 +++++++++++++++++++
 3 files changed, 105 insertions(+)
 create mode 100644 tools/testing/selftests/net/loopback_netns.c

diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
index c9f46031ac73..54919a8b3682 100644
--- a/tools/testing/selftests/net/.gitignore
+++ b/tools/testing/selftests/net/.gitignore
@@ -18,6 +18,7 @@ ipv6_flowlabel
 ipv6_flowlabel_mgr
 ipv6_fragmentation
 log.txt
+loopback_netns
 msg_zerocopy
 netlink-dumps
 nettest
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 0f5c178bc224..7d7e88e6676f 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -184,6 +184,7 @@ TEST_GEN_PROGS := \
 	icmp_rfc4884 \
 	ipv6_flowlabel_mgr \
 	ipv6_fragmentation \
+	loopback_netns \
 	proc_net_pktgen \
 	reuseaddr_conflict \
 	reuseport_bpf \
diff --git a/tools/testing/selftests/net/loopback_netns.c b/tools/testing/selftests/net/loopback_netns.c
new file mode 100644
index 000000000000..03cc665fb502
--- /dev/null
+++ b/tools/testing/selftests/net/loopback_netns.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <sched.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <net/if.h>
+
+#include "../clone3/clone3_selftests.h"
+#include "../kselftest_harness.h"
+
+static int get_lo_flags(void)
+{
+	struct ifreq ifr;
+	int fd, ret;
+
+	memset(&ifr, 0, sizeof(ifr));
+	strncpy(ifr.ifr_name, "lo", IFNAMSIZ - 1);
+
+	fd = socket(AF_INET, SOCK_DGRAM, 0);
+	if (fd < 0)
+		return -errno;
+
+	ret = ioctl(fd, SIOCGIFFLAGS, &ifr);
+	close(fd);
+
+	if (ret < 0)
+		return -errno;
+
+	return ifr.ifr_flags;
+}
+
+TEST(baseline_lo_down)
+{
+	int flags;
+
+	ASSERT_EQ(0, unshare(CLONE_NEWUSER | CLONE_NEWNET));
+
+	flags = get_lo_flags();
+	ASSERT_GE(flags, 0);
+	EXPECT_EQ(0, flags & IFF_UP);
+}
+
+TEST(unshare_loopback)
+{
+	struct stat st_before, st_after;
+	int flags;
+
+	ASSERT_EQ(0, stat("/proc/self/ns/net", &st_before));
+	ASSERT_EQ(0, unshare(CLONE_NEWUSER | UNSHARE_NEWNET_LOOPBACK));
+	ASSERT_EQ(0, stat("/proc/self/ns/net", &st_after));
+	EXPECT_NE(st_before.st_ino, st_after.st_ino);
+
+	flags = get_lo_flags();
+	ASSERT_GE(flags, 0);
+	EXPECT_NE(0, flags & IFF_UP);
+	EXPECT_NE(0, flags & IFF_RUNNING);
+}
+
+TEST(clone3_loopback)
+{
+	struct __clone_args args = {
+		.flags = CLONE_NEWUSER | CLONE_NEWNET_LOOPBACK,
+		.exit_signal = SIGCHLD,
+	};
+	struct stat st_parent, st_child;
+	int status;
+	pid_t pid;
+
+	ASSERT_EQ(0, stat("/proc/self/ns/net", &st_parent));
+
+	pid = sys_clone3(&args, sizeof(args));
+	ASSERT_GE(pid, 0);
+
+	if (pid == 0) {
+		int flags;
+
+		if (stat("/proc/self/ns/net", &st_child) != 0 ||
+		    st_parent.st_ino == st_child.st_ino)
+			_exit(1);
+
+		flags = get_lo_flags();
+		if (flags < 0 || !(flags & IFF_UP) || !(flags & IFF_RUNNING))
+			_exit(2);
+
+		_exit(0);
+	}
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+}
+
+TEST_HARNESS_MAIN
-- 
2.56.0.313.g2479322e68-goog


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 6/8] selftests/capabilities: tests for user namespace capability bounding set
  2026-10-28 22:16 [PATCH 0/8] userns: per-namespace capability bounding set and opt-in netns loopback Hallgrimur H. Gunnarsson
                   ` (4 preceding siblings ...)
  2026-10-28 22:17 ` [PATCH 5/8] selftests/net: add test suite for CLONE_NEWNET_LOOPBACK Hallgrimur H. Gunnarsson
@ 2026-10-28 22:17 ` Hallgrimur H. Gunnarsson
  2026-10-28 22:17 ` [PATCH 7/8] selftests/capabilities: add clone3() tests for CLONE_NEWUSER_BSET Hallgrimur H. Gunnarsson
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Hallgrimur H. Gunnarsson @ 2026-10-28 22:17 UTC (permalink / raw)
  To: Christian Brauner, Alexander Viro, Paul Moore, Serge Hallyn,
	Kees Cook, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: Jan Kara, James Morris, Simon Horman, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Jann Horn, Günther Noack, KP Singh,
	Jonathan Calmels, Casey Schaufler, linux-fsdevel,
	linux-security-module, linux-api, containers, netdev, linux-doc,
	linux-kselftest, linux-kernel, Hallgrimur H. Gunnarsson

Add tests in tools/testing/selftests/capabilities for the user
namespace capability bounding set feature:

- Verify default full capabilities and sysctl capability restriction for
  unprivileged user namespaces.
- Verify that unprivileged creators retain their existing effective
  capabilities despite the sysctl ceiling.
- Verify that privileged creators bypass the sysctl ceiling.
- Verify monotonic capability restriction across nested namespaces.
- Verify parent-ownership capability bounding.

Signed-off-by: Hallgrimur H. Gunnarsson <halg@google.com>
---
 .../testing/selftests/capabilities/.gitignore |   1 +
 tools/testing/selftests/capabilities/Makefile |   2 +-
 .../capabilities/userns_bounding_set_test.c   | 420 ++++++++++++++++++
 3 files changed, 422 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/capabilities/userns_bounding_set_test.c

diff --git a/tools/testing/selftests/capabilities/.gitignore b/tools/testing/selftests/capabilities/.gitignore
index 426d9adca67c..28767c08dea8 100644
--- a/tools/testing/selftests/capabilities/.gitignore
+++ b/tools/testing/selftests/capabilities/.gitignore
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 test_execve
+userns_bounding_set_test
 validate_cap
diff --git a/tools/testing/selftests/capabilities/Makefile b/tools/testing/selftests/capabilities/Makefile
index 411ac098308f..b29f7b117c11 100644
--- a/tools/testing/selftests/capabilities/Makefile
+++ b/tools/testing/selftests/capabilities/Makefile
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 TEST_GEN_FILES := validate_cap
-TEST_GEN_PROGS := test_execve
+TEST_GEN_PROGS := test_execve userns_bounding_set_test
 
 CFLAGS += -O2 -g -std=gnu99 -Wall $(KHDR_INCLUDES)
 LDLIBS += -lcap-ng -lrt -ldl
diff --git a/tools/testing/selftests/capabilities/userns_bounding_set_test.c b/tools/testing/selftests/capabilities/userns_bounding_set_test.c
new file mode 100644
index 000000000000..798758e0705d
--- /dev/null
+++ b/tools/testing/selftests/capabilities/userns_bounding_set_test.c
@@ -0,0 +1,420 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <linux/capability.h>
+#include <linux/sched.h>
+
+#include "../clone3/clone3_selftests.h"
+#include "../kselftest_harness.h"
+
+#define SYSCTL_PATH "/proc/sys/kernel/unprivileged_userns_caps_allowed"
+
+static const uint64_t cap_full_set = ~0ULL;
+
+struct cap_state {
+	bool bset;
+	bool permitted;
+	bool effective;
+};
+
+static int get_cap_state(int cap, struct cap_state *state)
+{
+	struct __user_cap_header_struct hdr = {
+		.version = _LINUX_CAPABILITY_VERSION_3,
+		.pid = 0,
+	};
+	struct __user_cap_data_struct data[2];
+	uint32_t mask;
+	int idx;
+
+	if (cap < 0 || cap >= 64)
+		return -EINVAL;
+
+	if (syscall(SYS_capget, &hdr, data) != 0)
+		return -errno;
+
+	idx = (cap >= 32) ? 1 : 0;
+	mask = 1U << (cap % 32);
+
+	state->bset = (prctl(PR_CAPBSET_READ, cap, 0, 0, 0) == 1);
+	state->permitted = (data[idx].permitted & mask) != 0;
+	state->effective = (data[idx].effective & mask) != 0;
+
+	return 0;
+}
+
+static bool cap_raised(int cap)
+{
+	struct cap_state t;
+
+	if (get_cap_state(cap, &t) != 0)
+		return false;
+
+	return t.bset && t.permitted && t.effective;
+}
+
+static bool cap_dropped(int cap)
+{
+	struct cap_state t;
+
+	if (get_cap_state(cap, &t) != 0)
+		return false;
+
+	return !t.bset && !t.permitted && !t.effective;
+}
+
+static int set_effective_cap(int cap)
+{
+	struct __user_cap_header_struct hdr = {
+		.version = _LINUX_CAPABILITY_VERSION_3,
+	};
+	struct __user_cap_data_struct data[2] = {};
+
+	if (cap >= 0) {
+		int idx = (cap >= 32) ? 1 : 0;
+		uint32_t mask = 1U << (cap % 32);
+
+		data[idx].effective = mask;
+		data[idx].permitted = mask;
+	}
+
+	return syscall(SYS_capset, &hdr, data);
+}
+
+static int drop_privileges(void)
+{
+	return set_effective_cap(-1);
+}
+
+static int write_file(const char *path, const char *str)
+{
+	ssize_t len = strlen(str);
+	ssize_t ret;
+	int fd;
+
+	fd = open(path, O_WRONLY | O_CLOEXEC);
+	if (fd < 0)
+		return -1;
+	ret = write(fd, str, len);
+	close(fd);
+
+	return (ret == len) ? 0 : -1;
+}
+
+static int read_sysctl_caps(uint64_t *caps_out)
+{
+	char buf[64] = {};
+	ssize_t ret;
+	int fd;
+
+	fd = open(SYSCTL_PATH, O_RDONLY | O_CLOEXEC);
+	if (fd < 0)
+		return -1;
+	ret = read(fd, buf, sizeof(buf) - 1);
+	close(fd);
+	if (ret <= 0)
+		return -1;
+
+	*caps_out = strtoull(buf, NULL, 16);
+	return 0;
+}
+
+static int write_sysctl_caps(uint64_t val)
+{
+	char buf[64];
+
+	snprintf(buf, sizeof(buf), "%016llx\n", (unsigned long long)val);
+	return write_file(SYSCTL_PATH, buf);
+}
+
+/*
+ * Attempts setns(CLONE_NEWUSER) into an unprivileged child user namespace
+ * from the owning parent process (which has no effective capabilities in
+ * its own namespace and relies on the parent-owner rule in
+ * cap_capable_helper()).
+ */
+static int run_parent_owner_setns(uint64_t sysctl_mask)
+{
+	pid_t owner_pid;
+	int status;
+
+	if (write_sysctl_caps(sysctl_mask) != 0)
+		return -1;
+
+	owner_pid = fork();
+	if (owner_pid < 0)
+		return -1;
+
+	if (owner_pid == 0) {
+		struct __clone_args args = {
+			.flags = CLONE_FILES | CLONE_NEWUSER,
+			.exit_signal = SIGCHLD,
+		};
+		pid_t child_pid;
+		int userns_fd;
+		int err;
+
+		if (drop_privileges() != 0)
+			_exit(255);
+
+		child_pid = sys_clone3(&args, sizeof(args));
+		if (child_pid < 0)
+			_exit(255);
+
+		if (child_pid == 0) {
+			int fd = open("/proc/self/ns/user", O_RDONLY);
+
+			if (fd < 0)
+				_exit(255);
+			_exit(fd);
+		}
+
+		if (waitpid(child_pid, &status, 0) != child_pid ||
+		    !WIFEXITED(status) || WEXITSTATUS(status) == 255)
+			_exit(255);
+
+		userns_fd = WEXITSTATUS(status);
+		err = (setns(userns_fd, CLONE_NEWUSER) == 0) ? 0 : errno;
+		close(userns_fd);
+		_exit(err);
+	}
+
+	if (waitpid(owner_pid, &status, 0) != owner_pid || !WIFEXITED(status))
+		return -1;
+
+	return WEXITSTATUS(status);
+}
+
+FIXTURE(userns_bset) {
+	uint64_t orig_caps;
+	bool have_orig_caps;
+};
+
+FIXTURE_SETUP(userns_bset)
+{
+	self->orig_caps = 0;
+	self->have_orig_caps = false;
+
+	if (geteuid() != 0)
+		SKIP(return, "Requires root privileges");
+	if (access(SYSCTL_PATH, F_OK) != 0)
+		SKIP(return, "Sysctl %s not present", SYSCTL_PATH);
+
+	ASSERT_EQ(0, read_sysctl_caps(&self->orig_caps));
+	self->have_orig_caps = true;
+}
+
+FIXTURE_TEARDOWN(userns_bset)
+{
+	if (self->have_orig_caps)
+		write_sysctl_caps(self->orig_caps);
+}
+
+/*
+ * An unprivileged userns creator with the full mask should receive
+ * CAP_NET_ADMIN and CAP_SYS_ADMIN.
+ */
+TEST_F(userns_bset, default_full_capabilities)
+{
+	pid_t pid;
+	int status;
+
+	ASSERT_EQ(0, write_sysctl_caps(cap_full_set));
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+
+	if (pid == 0) {
+		if (drop_privileges() != 0)
+			_exit(1);
+		if (unshare(CLONE_NEWUSER) != 0)
+			_exit(2);
+		if (!cap_raised(CAP_SYS_ADMIN))
+			_exit(3);
+		if (!cap_raised(CAP_NET_ADMIN))
+			_exit(4);
+		_exit(0);
+	}
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+}
+
+/*
+ * Dropping CAP_NET_ADMIN from the sysctl prevents unprivileged userns
+ * from obtaining CAP_NET_ADMIN while retaining other capabilities.
+ */
+TEST_F(userns_bset, unprivileged_restriction)
+{
+	uint64_t restricted_mask = cap_full_set & ~(1ULL << CAP_NET_ADMIN);
+	pid_t pid;
+	int status;
+
+	ASSERT_EQ(0, write_sysctl_caps(restricted_mask));
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+
+	if (pid == 0) {
+		if (drop_privileges() != 0)
+			_exit(1);
+		if (unshare(CLONE_NEWUSER) != 0)
+			_exit(2);
+		if (!cap_raised(CAP_SYS_ADMIN))
+			_exit(3);
+		if (!cap_dropped(CAP_NET_ADMIN))
+			_exit(4);
+		_exit(0);
+	}
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+}
+
+/*
+ * Unprivileged userns should retain effective capabilities from the creator
+ * (CAP_NET_ADMIN) while still having unheld capabilities (CAP_NET_RAW) trimmed.
+ */
+TEST_F(userns_bset, creator_retains_effective_cap)
+{
+	uint64_t restricted_mask = cap_full_set &
+		~((1ULL << CAP_NET_ADMIN) | (1ULL << CAP_NET_RAW));
+	pid_t pid;
+	int status;
+
+	ASSERT_EQ(0, write_sysctl_caps(restricted_mask));
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+
+	if (pid == 0) {
+		if (set_effective_cap(CAP_NET_ADMIN) != 0)
+			_exit(1);
+
+		if (unshare(CLONE_NEWUSER) != 0)
+			_exit(2);
+
+		if (!cap_raised(CAP_NET_ADMIN))
+			_exit(3);
+		if (!cap_dropped(CAP_NET_RAW))
+			_exit(4);
+		if (!cap_raised(CAP_SYS_ADMIN))
+			_exit(5);
+
+		_exit(0);
+	}
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+}
+
+/*
+ * Privileged processes (with CAP_SYS_ADMIN in parent ns) retain full
+ * capabilities regardless of the unprivileged sysctl mask.
+ */
+TEST_F(userns_bset, privileged_bypass)
+{
+	uint64_t restricted_mask = cap_full_set & ~(1ULL << CAP_NET_ADMIN);
+	pid_t pid;
+	int status;
+
+	ASSERT_EQ(0, write_sysctl_caps(restricted_mask));
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+
+	if (pid == 0) {
+		if (unshare(CLONE_NEWUSER) != 0)
+			_exit(1);
+		if (!cap_raised(CAP_NET_ADMIN))
+			_exit(2);
+		_exit(0);
+	}
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+}
+
+/*
+ * Verify that once a capability is dropped in a user namespace
+ * a privileged creator (holding CAP_SYS_ADMIN) in that namespace
+ * cannot regain the dropped capability by creating a nested child
+ * namespace.
+ */
+TEST_F(userns_bset, nested_monotonic_restriction)
+{
+	uint64_t restricted_mask = cap_full_set & ~(1ULL << CAP_NET_ADMIN);
+	pid_t pid;
+	int status;
+
+	ASSERT_EQ(0, write_sysctl_caps(restricted_mask));
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+
+	if (pid == 0) {
+		if (set_effective_cap(CAP_SETFCAP) != 0)
+			_exit(1);
+		if (unshare(CLONE_NEWUSER) != 0)
+			_exit(2);
+		if (!cap_dropped(CAP_NET_ADMIN))
+			_exit(3);
+
+		/* Map our UID/GID so creating a nested userns is permitted */
+		if (write_file("/proc/self/setgroups", "deny\n") != 0 ||
+		    write_file("/proc/self/uid_map", "0 0 1\n") != 0 ||
+		    write_file("/proc/self/gid_map", "0 0 1\n") != 0)
+			_exit(4);
+
+		if (unshare(CLONE_NEWUSER) != 0)
+			_exit(5);
+		if (!cap_dropped(CAP_NET_ADMIN))
+			_exit(6);
+		if (!cap_raised(CAP_SYS_ADMIN))
+			_exit(7);
+		_exit(0);
+	}
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+}
+
+/*
+ * Verify that an unprivileged parent owning a child namespace retains
+ * implicit capabilities over it when permitted by the child's cap_bset.
+ */
+TEST_F(userns_bset, parent_owner_cap_permitted)
+{
+	EXPECT_EQ(0, run_parent_owner_setns(cap_full_set));
+}
+
+/*
+ * Verify that an unprivileged parent's implicit capabilities over its
+ * owned child namespace are restricted by the child's cap_bset.
+ */
+TEST_F(userns_bset, parent_owner_cap_restricted)
+{
+	uint64_t restricted_mask = cap_full_set & ~(1ULL << CAP_SYS_ADMIN);
+
+	EXPECT_EQ(EPERM, run_parent_owner_setns(restricted_mask));
+}
+
+TEST_HARNESS_MAIN
-- 
2.56.0.313.g2479322e68-goog


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 7/8] selftests/capabilities: add clone3() tests for CLONE_NEWUSER_BSET
  2026-10-28 22:16 [PATCH 0/8] userns: per-namespace capability bounding set and opt-in netns loopback Hallgrimur H. Gunnarsson
                   ` (5 preceding siblings ...)
  2026-10-28 22:17 ` [PATCH 6/8] selftests/capabilities: tests for user namespace capability bounding set Hallgrimur H. Gunnarsson
@ 2026-10-28 22:17 ` Hallgrimur H. Gunnarsson
  2026-10-28 22:17 ` [PATCH 8/8] selftests/nsfs: add tests for NS_USER_GET_INFO ioctl Hallgrimur H. Gunnarsson
  2026-10-28 22:56 ` [PATCH 0/8] userns: per-namespace capability bounding set and opt-in netns loopback Andy Lutomirski
  8 siblings, 0 replies; 10+ messages in thread
From: Hallgrimur H. Gunnarsson @ 2026-10-28 22:17 UTC (permalink / raw)
  To: Christian Brauner, Alexander Viro, Paul Moore, Serge Hallyn,
	Kees Cook, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: Jan Kara, James Morris, Simon Horman, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Jann Horn, Günther Noack, KP Singh,
	Jonathan Calmels, Casey Schaufler, linux-fsdevel,
	linux-security-module, linux-api, containers, netdev, linux-doc,
	linux-kselftest, linux-kernel, Hallgrimur H. Gunnarsson

Add tests to userns_bounding_set_test to verify per-namespace
capability bounding via CLONE_NEWUSER_BSET.

Signed-off-by: Hallgrimur H. Gunnarsson <halg@google.com>
---
 .../capabilities/userns_bounding_set_test.c   | 146 ++++++++++++++++++
 .../selftests/clone3/clone3_selftests.h       |   1 +
 2 files changed, 147 insertions(+)

diff --git a/tools/testing/selftests/capabilities/userns_bounding_set_test.c b/tools/testing/selftests/capabilities/userns_bounding_set_test.c
index 798758e0705d..140c12e16e9e 100644
--- a/tools/testing/selftests/capabilities/userns_bounding_set_test.c
+++ b/tools/testing/selftests/capabilities/userns_bounding_set_test.c
@@ -417,4 +417,150 @@ TEST_F(userns_bset, parent_owner_cap_restricted)
 	EXPECT_EQ(EPERM, run_parent_owner_setns(restricted_mask));
 }
 
+/*
+ * Verify that CLONE_NEWUSER_BSET restricts capabilities in the child user
+ * namespace even when the global sysctl allows all capabilities.
+ */
+TEST_F(userns_bset, clone3_bset_restriction)
+{
+	struct __clone_args args = {
+		.flags = CLONE_NEWUSER | CLONE_NEWUSER_BSET,
+		.exit_signal = SIGCHLD,
+		.cap_bset = cap_full_set & ~(1ULL << CAP_NET_ADMIN),
+	};
+	pid_t pid;
+	int status;
+
+	ASSERT_EQ(0, write_sysctl_caps(cap_full_set));
+
+	pid = sys_clone3(&args, sizeof(args));
+	ASSERT_GE(pid, 0);
+
+	if (pid == 0) {
+		if (!cap_raised(CAP_SYS_ADMIN))
+			_exit(1);
+		if (!cap_dropped(CAP_NET_ADMIN))
+			_exit(2);
+		_exit(0);
+	}
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+}
+
+/*
+ * Verify that passing cap_bset = 0 with CLONE_NEWUSER_BSET drops all
+ * capabilities in the new user namespace.
+ */
+TEST_F(userns_bset, clone3_bset_empty)
+{
+	struct __clone_args args = {
+		.flags = CLONE_NEWUSER | CLONE_NEWUSER_BSET,
+		.exit_signal = SIGCHLD,
+		.cap_bset = 0,
+	};
+	pid_t pid;
+	int status;
+
+	ASSERT_EQ(0, write_sysctl_caps(cap_full_set));
+
+	pid = sys_clone3(&args, sizeof(args));
+	ASSERT_GE(pid, 0);
+
+	if (pid == 0) {
+		if (!cap_dropped(CAP_SYS_ADMIN))
+			_exit(1);
+		if (!cap_dropped(CAP_NET_ADMIN))
+			_exit(2);
+		_exit(0);
+	}
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+}
+
+/*
+ * Verify that an unprivileged caller cannot use CLONE_NEWUSER_BSET to bypass
+ * the global sysctl ceiling.
+ */
+TEST_F(userns_bset, clone3_bset_cannot_bypass_sysctl)
+{
+	uint64_t restricted_mask = cap_full_set & ~(1ULL << CAP_NET_ADMIN);
+	pid_t pid;
+	int status;
+
+	ASSERT_EQ(0, write_sysctl_caps(restricted_mask));
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+
+	if (pid == 0) {
+		struct __clone_args args = {
+			.flags = CLONE_NEWUSER | CLONE_NEWUSER_BSET,
+			.exit_signal = SIGCHLD,
+			.cap_bset = cap_full_set,
+		};
+		int child_status;
+		pid_t child;
+
+		if (drop_privileges() != 0)
+			_exit(1);
+
+		child = sys_clone3(&args, sizeof(args));
+		if (child < 0)
+			_exit(2);
+
+		if (child == 0) {
+			if (!cap_raised(CAP_SYS_ADMIN))
+				_exit(3);
+			if (!cap_dropped(CAP_NET_ADMIN))
+				_exit(4);
+			_exit(0);
+		}
+
+		if (waitpid(child, &child_status, 0) != child ||
+		    !WIFEXITED(child_status) || WEXITSTATUS(child_status) != 0)
+			_exit(5);
+
+		_exit(0);
+	}
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+}
+
+/*
+ * Verify clone3() argument validation for CLONE_NEWUSER_BSET.
+ */
+TEST_F(userns_bset, clone3_bset_invalid_args)
+{
+	struct __clone_args args;
+
+	/* CLONE_NEWUSER_BSET without CLONE_NEWUSER must fail */
+	memset(&args, 0, sizeof(args));
+	args.flags = CLONE_NEWUSER_BSET;
+	args.exit_signal = SIGCHLD;
+	args.cap_bset = cap_full_set;
+	EXPECT_EQ(-1, sys_clone3(&args, sizeof(args)));
+	EXPECT_EQ(EINVAL, errno);
+
+	/* Non-zero cap_bset without CLONE_NEWUSER_BSET must fail */
+	memset(&args, 0, sizeof(args));
+	args.flags = CLONE_NEWUSER;
+	args.exit_signal = SIGCHLD;
+	args.cap_bset = cap_full_set;
+	EXPECT_EQ(-1, sys_clone3(&args, sizeof(args)));
+	EXPECT_EQ(EINVAL, errno);
+
+	/* CLONE_NEWUSER_BSET with struct size < VER3 must fail */
+	memset(&args, 0, sizeof(args));
+	args.flags = CLONE_NEWUSER | CLONE_NEWUSER_BSET;
+	args.exit_signal = SIGCHLD;
+	EXPECT_EQ(-1, sys_clone3(&args, CLONE_ARGS_SIZE_VER2));
+	EXPECT_EQ(EINVAL, errno);
+}
+
 TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/clone3/clone3_selftests.h b/tools/testing/selftests/clone3/clone3_selftests.h
index a0593e8950f0..2145d14956e5 100644
--- a/tools/testing/selftests/clone3/clone3_selftests.h
+++ b/tools/testing/selftests/clone3/clone3_selftests.h
@@ -31,6 +31,7 @@ struct __clone_args {
 	__aligned_u64 set_tid;
 	__aligned_u64 set_tid_size;
 	__aligned_u64 cgroup;
+	__aligned_u64 cap_bset;
 };
 
 static pid_t sys_clone3(struct __clone_args *args, size_t size)
-- 
2.56.0.313.g2479322e68-goog


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 8/8] selftests/nsfs: add tests for NS_USER_GET_INFO ioctl
  2026-10-28 22:16 [PATCH 0/8] userns: per-namespace capability bounding set and opt-in netns loopback Hallgrimur H. Gunnarsson
                   ` (6 preceding siblings ...)
  2026-10-28 22:17 ` [PATCH 7/8] selftests/capabilities: add clone3() tests for CLONE_NEWUSER_BSET Hallgrimur H. Gunnarsson
@ 2026-10-28 22:17 ` Hallgrimur H. Gunnarsson
  2026-10-28 22:56 ` [PATCH 0/8] userns: per-namespace capability bounding set and opt-in netns loopback Andy Lutomirski
  8 siblings, 0 replies; 10+ messages in thread
From: Hallgrimur H. Gunnarsson @ 2026-10-28 22:17 UTC (permalink / raw)
  To: Christian Brauner, Alexander Viro, Paul Moore, Serge Hallyn,
	Kees Cook, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: Jan Kara, James Morris, Simon Horman, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Jann Horn, Günther Noack, KP Singh,
	Jonathan Calmels, Casey Schaufler, linux-fsdevel,
	linux-security-module, linux-api, containers, netdev, linux-doc,
	linux-kselftest, linux-kernel, Hallgrimur H. Gunnarsson

Add tests for the NS_USER_GET_INFO ioctl to verify user namespace
capability bounding set inspection and validate error paths.

Signed-off-by: Hallgrimur H. Gunnarsson <halg@google.com>
---
 .../selftests/filesystems/nsfs/.gitignore     |   1 +
 .../selftests/filesystems/nsfs/Makefile       |   4 +-
 .../selftests/filesystems/nsfs/userns.c       | 120 ++++++++++++++++++
 3 files changed, 123 insertions(+), 2 deletions(-)
 create mode 100644 tools/testing/selftests/filesystems/nsfs/userns.c

diff --git a/tools/testing/selftests/filesystems/nsfs/.gitignore b/tools/testing/selftests/filesystems/nsfs/.gitignore
index 92a8249006d1..b0a59f5d49fc 100644
--- a/tools/testing/selftests/filesystems/nsfs/.gitignore
+++ b/tools/testing/selftests/filesystems/nsfs/.gitignore
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
 owner
 pidns
+userns
 iterate_mntns
diff --git a/tools/testing/selftests/filesystems/nsfs/Makefile b/tools/testing/selftests/filesystems/nsfs/Makefile
index 231aaa7dfd95..9408d7948621 100644
--- a/tools/testing/selftests/filesystems/nsfs/Makefile
+++ b/tools/testing/selftests/filesystems/nsfs/Makefile
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0-only
-TEST_GEN_PROGS := owner pidns iterate_mntns
+TEST_GEN_PROGS := owner pidns iterate_mntns userns
 
-CFLAGS := -Wall -Werror
+CFLAGS += -Wall -Werror $(KHDR_INCLUDES)
 
 include ../../lib.mk
diff --git a/tools/testing/selftests/filesystems/nsfs/userns.c b/tools/testing/selftests/filesystems/nsfs/userns.c
new file mode 100644
index 000000000000..70661a495afc
--- /dev/null
+++ b/tools/testing/selftests/filesystems/nsfs/userns.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <sys/ioctl.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <linux/nsfs.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+
+#include "../../clone3/clone3_selftests.h"
+#include "../../kselftest_harness.h"
+
+TEST(user_get_info_self)
+{
+	struct user_ns_info info = {};
+	int fd;
+
+	fd = open("/proc/self/ns/user", O_RDONLY | O_CLOEXEC);
+	ASSERT_GE(fd, 0) {
+		TH_LOG("Failed to open /proc/self/ns/user: %m");
+	}
+
+	ASSERT_EQ(0, ioctl(fd, NS_USER_GET_INFO, &info)) {
+		TH_LOG("ioctl(NS_USER_GET_INFO) failed on self: %m");
+	}
+	close(fd);
+
+	EXPECT_EQ(USER_NS_INFO_SIZE_VER0, info.size);
+	EXPECT_EQ(0, info.spare);
+	EXPECT_NE(0ULL, info.cap_bset);
+}
+
+TEST(user_get_info_restricted_bset)
+{
+	struct __clone_args args = {
+		.flags = CLONE_NEWUSER | CLONE_NEWUSER_BSET,
+		.exit_signal = SIGCHLD,
+		.cap_bset = 0,
+	};
+	int status;
+	pid_t pid;
+
+	pid = sys_clone3(&args, sizeof(args));
+	ASSERT_GE(pid, 0);
+
+	if (pid == 0) {
+		struct user_ns_info info = {};
+		int fd;
+
+		fd = open("/proc/self/ns/user", O_RDONLY | O_CLOEXEC);
+		if (fd < 0)
+			_exit(1);
+
+		if (ioctl(fd, NS_USER_GET_INFO, &info) != 0) {
+			close(fd);
+			_exit(2);
+		}
+		close(fd);
+
+		if (info.cap_bset != 0)
+			_exit(3);
+
+		_exit(0);
+	}
+
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	EXPECT_EQ(0, WEXITSTATUS(status));
+}
+
+TEST(user_get_info_invalid_ns_type)
+{
+	struct user_ns_info info = {};
+	int mntns_fd;
+
+	mntns_fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
+	ASSERT_GE(mntns_fd, 0) {
+		TH_LOG("Failed to open /proc/self/ns/mnt: %m");
+	}
+
+	EXPECT_EQ(-1, ioctl(mntns_fd, NS_USER_GET_INFO, &info));
+	EXPECT_EQ(EINVAL, errno);
+
+	close(mntns_fd);
+}
+
+TEST(user_get_info_buffer_too_small)
+{
+	__u32 small_buf = 0;
+	int fd;
+
+	fd = open("/proc/self/ns/user", O_RDONLY | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+
+	/* Encode a buffer size smaller than USER_NS_INFO_SIZE_VER0 (4B vs 16B) */
+	EXPECT_EQ(-1, ioctl(fd, _IOR(NSIO, 14, __u32), &small_buf));
+
+	close(fd);
+}
+
+TEST(user_get_info_null_arg)
+{
+	int fd;
+
+	fd = open("/proc/self/ns/user", O_RDONLY | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+
+	EXPECT_EQ(-1, ioctl(fd, NS_USER_GET_INFO, NULL));
+	EXPECT_EQ(EINVAL, errno);
+
+	close(fd);
+}
+
+TEST_HARNESS_MAIN
-- 
2.56.0.313.g2479322e68-goog


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH 0/8] userns: per-namespace capability bounding set and opt-in netns loopback
  2026-10-28 22:16 [PATCH 0/8] userns: per-namespace capability bounding set and opt-in netns loopback Hallgrimur H. Gunnarsson
                   ` (7 preceding siblings ...)
  2026-10-28 22:17 ` [PATCH 8/8] selftests/nsfs: add tests for NS_USER_GET_INFO ioctl Hallgrimur H. Gunnarsson
@ 2026-10-28 22:56 ` Andy Lutomirski
  8 siblings, 0 replies; 10+ messages in thread
From: Andy Lutomirski @ 2026-10-28 22:56 UTC (permalink / raw)
  To: Hallgrimur H. Gunnarsson
  Cc: Christian Brauner, Alexander Viro, Paul Moore, Serge Hallyn,
	Kees Cook, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Jan Kara, James Morris, Simon Horman,
	Jonathan Corbet, Shuah Khan, Randy Dunlap, Jann Horn,
	Günther Noack, KP Singh, Jonathan Calmels, Casey Schaufler,
	linux-fsdevel, linux-security-module, linux-api, containers,
	netdev, linux-doc, linux-kselftest, linux-kernel

On Wed, Oct 28, 2026 at 3:18 PM Hallgrimur H. Gunnarsson
<halg@google.com> 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().

Why should the task's cap masks affect this?  *Maybe* cap_bset makes
sense, but I'm struggling a bit to see why cap_permitted and
cap_effective should matter.

Also, I think that Docker in non-userns mode restricts cap_bset, and
the purpose of that restriction is rather at odds with interpreting it
as a restriction on newly created user namespaces for purposes of
vulnerability mitigation.

>
> 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.
>

The whole unshare/clone flag situation is getting out of hand, IMO.
And this new thing makes logical sense but is really about reducing
exposure.

Perhaps we should instead have a small list associated with a userns
(extensible in the future) of things that the ns's admin (i.e. whoever
has CAP_SYS_ADMIN relative to the ns) may do.  That list could
include:

- Create loopback with sensible defaults for ipv4 and/or ipv6 (i see
no obvious reason to separate them)
- Create tun/tap interfaces in that ns (and attach them, etc)

And add a very simple new API to do these things.  Actually, the
tun/tap could just use the normal mechanism.

Thoughts?

-- 
Andy Lutomirski
AMA Capital Management, LLC

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-10-28 22:56 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-28 22:16 [PATCH 0/8] userns: per-namespace capability bounding set and opt-in netns loopback Hallgrimur H. Gunnarsson
2026-10-28 22:16 ` [PATCH 1/8] net: introduce CLONE_NEWNET_LOOPBACK to auto-enable loopback Hallgrimur H. Gunnarsson
2026-10-28 22:16 ` [PATCH 2/8] userns: introduce capability bounding set for user namespaces Hallgrimur H. Gunnarsson
2026-10-28 22:17 ` [PATCH 3/8] userns: allow restricting capability bounding set via clone3() Hallgrimur H. Gunnarsson
2026-10-28 22:17 ` [PATCH 4/8] nsfs: add NS_USER_GET_INFO ioctl to inspect user namespace capabilities Hallgrimur H. Gunnarsson
2026-10-28 22:17 ` [PATCH 5/8] selftests/net: add test suite for CLONE_NEWNET_LOOPBACK Hallgrimur H. Gunnarsson
2026-10-28 22:17 ` [PATCH 6/8] selftests/capabilities: tests for user namespace capability bounding set Hallgrimur H. Gunnarsson
2026-10-28 22:17 ` [PATCH 7/8] selftests/capabilities: add clone3() tests for CLONE_NEWUSER_BSET Hallgrimur H. Gunnarsson
2026-10-28 22:17 ` [PATCH 8/8] selftests/nsfs: add tests for NS_USER_GET_INFO ioctl Hallgrimur H. Gunnarsson
2026-10-28 22:56 ` [PATCH 0/8] userns: per-namespace capability bounding set and opt-in netns loopback Andy Lutomirski

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox