[PATCH RFC v3 08/90] user_namespace: let the kernel map the ids of a cell
From: Josef Bacik
Date: Thu Nov 05 2026 - 09:59:38 EST
The id maps of an ordinary user namespace are written by a task, and
map_write() makes many checks on that task before it accepts the map.
The maps of the user namespace of a cell are set once, by the kernel, to
ids that the kernel chose. Add userns_set_cell_map() for that. It takes
one extent for the uid map and one for the gid map. The checks in
map_write() are about the writer, so they do not apply here.
If the cell has a single gid there are no groups to drop or to add, so
setgroups() is denied permanently.
The namespace records which block it was lent and which ucount the block
was charged to. Both are released when the namespace is freed, not when
the cell dies. Every credential and every object that the namespace owns
pins the namespace, and may still name one of its ids.
Add userns_retire_cell_block() for the case where something that is owned
by those ids could not be cleaned up. The block is then never handed out
again.
Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
---
include/linux/cell.h | 7 +++++
include/linux/user_namespace.h | 7 +++++
kernel/user_namespace.c | 65 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 79 insertions(+)
diff --git a/include/linux/cell.h b/include/linux/cell.h
index 6a65d035ffca..37fde4bfea44 100644
--- a/include/linux/cell.h
+++ b/include/linux/cell.h
@@ -145,4 +145,11 @@ static inline void cell_put(struct cell *cell) { }
#endif
+/* The id block allocator. */
+#ifdef CONFIG_CELLS
+void cell_block_release(u32 block);
+#else
+static inline void cell_block_release(u32 block) { }
+#endif
+
#endif /* _LINUX_CELL_H */
diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
index f0b0fe12c2fd..4a48ead45820 100644
--- a/include/linux/user_namespace.h
+++ b/include/linux/user_namespace.h
@@ -123,6 +123,8 @@ struct user_namespace {
#endif
#ifdef CONFIG_CELLS
struct cell *cell; /* whose namespace this is, counted */
+ u32 cell_block; /* kuid block + 1, 0 for none */
+ struct ucounts *cell_block_ucounts;
#endif
} __randomize_layout;
@@ -193,6 +195,11 @@ static inline struct user_namespace *get_user_ns(struct user_namespace *ns)
extern int create_user_ns(struct cred *new);
#ifdef CONFIG_CELLS
int create_cell_user_ns(struct cred *new, struct cell *cell);
+int userns_set_cell_map(struct user_namespace *ns,
+ const struct uid_gid_extent *uids,
+ const struct uid_gid_extent *gids,
+ u32 block, struct ucounts *ucounts);
+void userns_retire_cell_block(struct user_namespace *ns);
#endif
extern int unshare_userns(unsigned long unshare_flags, struct cred **new_cred);
extern void __put_user_ns(struct user_namespace *ns);
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 45699a0a0bfd..4155e2e77497 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -78,6 +78,66 @@ bool userns_is_cell(const struct user_namespace *ns)
{
return ns->cell;
}
+
+/**
+ * userns_set_cell_map - give the user namespace of a cell its one and only map
+ * @ns: the user namespace of a cell, which has no map yet
+ * @uids: the uids: .first as seen from the inside, .lower_first the kuid
+ * @gids: the gids likewise
+ * @block: the number of the block the ids are from plus 1, or 0 for none
+ * @ucounts: what the block was charged to, or NULL
+ *
+ * The kernel chose the ids, so none of what map_write() has to ask about the
+ * writer applies. With a single gid there are no groups to be dropped or taken
+ * up, so setgroups() is denied for good.
+ *
+ * @ns takes over @block and @ucounts and gives them back when it is freed.
+ *
+ * Return: 0, -EBUSY if @ns has a map, -EINVAL if what either starts at is not an
+ * id.
+ */
+int userns_set_cell_map(struct user_namespace *ns,
+ const struct uid_gid_extent *uids,
+ const struct uid_gid_extent *gids,
+ u32 block, struct ucounts *ucounts)
+{
+ guard(mutex)(&userns_state_mutex);
+
+ if (ns->uid_map.nr_extents || ns->gid_map.nr_extents)
+ return -EBUSY;
+ if (uids->first == (u32)-1 || gids->first == (u32)-1)
+ return -EINVAL;
+
+ ns->uid_map.extent[0] = *uids;
+ ns->gid_map.extent[0] = *gids;
+ /* With a single gid there are no groups to be dropped or taken up. */
+ if (gids->count == 1)
+ ns->flags &= ~USERNS_SETGROUPS_ALLOWED;
+ ns->cell_block = block;
+ ns->cell_block_ucounts = ucounts;
+ /* Pairs with the smp_rmb() of the lookups, as in map_write(). */
+ smp_wmb();
+ ns->uid_map.nr_extents = 1;
+ ns->gid_map.nr_extents = 1;
+ return 0;
+}
+
+/**
+ * userns_retire_cell_block - never hand the block of a user namespace out again
+ * @ns: the user namespace of a cell
+ *
+ * For when something that is owned by the ids of @ns could not be got rid of.
+ * The block stays marked as used for good, so what shows is -ENOSPC and never
+ * one tenant owning what another left behind.
+ */
+void userns_retire_cell_block(struct user_namespace *ns)
+{
+ guard(mutex)(&userns_state_mutex);
+
+ if (ns->cell_block)
+ pr_warn_ratelimited("cell: kuid block %u retired\n", ns->cell_block - 1);
+ ns->cell_block = 0;
+}
#endif
static unsigned long enforced_nproc_rlimit(void)
@@ -273,6 +333,11 @@ static void free_user_ns(struct work_struct *work)
retire_userns_sysctls(ns);
key_free_user_ns(ns);
#ifdef CONFIG_CELLS
+ /* Nothing that pins the namespace is left to name its ids. */
+ if (ns->cell_block)
+ cell_block_release(ns->cell_block - 1);
+ if (ns->cell_block_ucounts)
+ dec_ucount(ns->cell_block_ucounts, UCOUNT_CELL_ID_BLOCKS);
cell_put(ns->cell);
#endif
ns_common_free(ns);
--
2.55.0