[PATCH RFC v3 84/90] net: honour the rights of a cell in net_may()
From: Josef Bacik
Date: Thu Nov 05 2026 - 10:41:25 EST
net_may() requires a capability over the user namespace that owns the
network namespace. A task of a cell never has that, because a cell does
not own a network namespace.
For a task of a cell, use the "net-rights" slot instead. net_may()
succeeds if all of these hold:
- the network namespace is the one in the "net" slot of the cell
- the builder granted the right in the "net-rights" slot
- the task has the capability that the right maps to, in the user
namespace of the cell
The slot decides what the cell may do, and the capability decides which
tasks of the cell may do it. An init can set up netfilter rules and then
drop CAP_NET_ADMIN before it starts anything else. A task that is not root
in the cell cannot undo those rules. LSMs and audit still see a capability
check.
The network namespace still belongs to whoever built the cell. The builder
sets it up with the usual tools and can enter it at any time.
A check that has not been converted to net_may() still requires the
capability over the owner of the namespace, so it fails for every task of
a cell. Anything that nobody has looked at is therefore refused, and each
conversion adds one thing that a builder can allow.
The rights apply to the network namespace of the cell only:
- A socket that was created in another network namespace and passed to
the cell carries no rights.
- A request that names another network namespace as the place to move a
device to, or to create one in, is checked against that namespace and
fails. This matters because such a namespace can be named from inside
a cell: a veth device whose peer is in another namespace gives its own
namespace an id for the other one, and any task can read that id.
Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
---
include/linux/cell.h | 11 +++++++++++
kernel/cell/boundary.c | 19 +++++++++++++++++++
net/core/net_namespace.c | 8 ++++++++
3 files changed, 38 insertions(+)
diff --git a/include/linux/cell.h b/include/linux/cell.h
index b12e2f124769..e79ac7e9ec60 100644
--- a/include/linux/cell.h
+++ b/include/linux/cell.h
@@ -256,6 +256,17 @@ bool cell_no_net(void);
static inline bool cell_no_net(void) { return false; }
#endif
+/* What a cell may do to its network. */
+#ifdef CONFIG_CELLS
+bool cell_has_net_right(const struct cred *cred, const struct net *net, unsigned int right);
+#else
+static inline bool cell_has_net_right(const struct cred *cred, const struct net *net,
+ unsigned int right)
+{
+ return false;
+}
+#endif
+
/* A mount is idmapped for a cell. */
#ifdef CONFIG_CELLS
struct user_namespace *cell_idmap_userns(const struct file *file);
diff --git a/kernel/cell/boundary.c b/kernel/cell/boundary.c
index af67ef68d4a2..dd49a6e4b39c 100644
--- a/kernel/cell/boundary.c
+++ b/kernel/cell/boundary.c
@@ -104,3 +104,22 @@ bool cell_no_net(void)
return cell && cell->no_net;
}
+
+/**
+ * cell_has_net_right - has a cell been given a right over a network namespace?
+ * @cred: credentials of a task of the cell
+ * @net: the network namespace
+ * @right: an enum net_right
+ *
+ * The rights of a cell are over the network namespace in its "net" slot and over no
+ * other, so a socket that was made elsewhere and handed to the cell comes with none.
+ *
+ * Return: true if @net is that of the cell of @cred and @right is in its
+ * "net-rights" slot.
+ */
+bool cell_has_net_right(const struct cred *cred, const struct net *net, unsigned int right)
+{
+ const struct cell *cell = cred_cell(cred);
+
+ return cell && READ_ONCE(cell->net) == net && (cell->net_rights & BIT_ULL(right));
+}
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index d211631f8c42..be29eddaa9a2 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -16,6 +16,7 @@
#include <linux/file.h>
#include <linux/export.h>
#include <linux/user_namespace.h>
+#include <linux/cell.h>
#include <linux/net_namespace.h>
#include <linux/sched/task.h>
#include <linux/uidgid.h>
@@ -371,6 +372,13 @@ static int net_right_cap(enum net_right right)
static struct user_namespace *net_right_ns(const struct cred *cred, const struct net *net,
enum net_right right)
{
+ /*
+ * A cell owns no network namespace. What its tasks may do to the one it was
+ * given is what its builder named, and who among them may do it is whoever
+ * has the capability in there.
+ */
+ if (userns_is_cell(cred->user_ns))
+ return cell_has_net_right(cred, net, right) ? cred->user_ns : NULL;
return net->user_ns;
}
--
2.55.0