From: Josef Bacik <josef@toxicpanda.com>
To: Christian Brauner <brauner@kernel.org>,
James Bottomley <James.Bottomley@HansenPartnership.com>,
Andrew Halaney <andrew@amutable.com>,
Jeremy Bingham <jbingham@gmail.com>,
Tahera Fahimi <taherafahimi@linux.microsoft.com>,
Serge Hallyn <sergeh@kernel.org>
Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
Josef Bacik <josef@toxicpanda.com>
Subject: [PATCH RFC v3 30/90] cells: lend a cell a block of ids
Date: Thu, 05 Nov 2026 15:02:22 +0000 [thread overview]
Message-ID: <20261105-b4-cells-v3-30-bc9095937a84@toxicpanda.com> (raw)
In-Reply-To: <20261105-b4-cells-v3-0-bc9095937a84@toxicpanda.com>
With the value 65536 for "ids", the tasks of the cell see uids and gids 0
to 65535. These map to a block of the window that the kernel allocates.
The builder never names a kuid.
An ordinary user namespace needs a privilege check before it may map a
range of ids. Here the check is replaced by the guarantee that nobody else
holds an id of the block. The allocator enforces that guarantee rather
than assuming it:
- A block stays allocated for as long as the user namespace that it was
lent to exists.
- A released block is quarantined. It returns to the pool only once the
tables that are indexed by a bare kuid hold nothing in its range.
- The same tables are checked again before a block is handed out,
because nothing stops a task of the host from running with an id of
the window.
- Blocks are handed out round robin, so that a block is reused as late
as possible.
- A quarantined block counts as used. A block whose ids could not be
removed from the budget is retired permanently.
So a leak shows up as ENOSPC, and never as one cell owning what another
cell left behind.
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
kernel/cell/ids.c | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 156 insertions(+)
diff --git a/kernel/cell/ids.c b/kernel/cell/ids.c
index aaf7e7ed9826..edf625fa03a1 100644
--- a/kernel/cell/ids.c
+++ b/kernel/cell/ids.c
@@ -2,10 +2,137 @@
/*
* Cells: the ids of a cell.
*/
+#include <linux/bitmap.h>
+#include <linux/key.h>
+#include <linux/sched/user.h>
+#include <linux/spinlock.h>
#include <linux/user_namespace.h>
#include "internal.h"
+/*
+ * The kuid block allocator.
+ *
+ * "Nobody else holds an id of this block" is what stands in for the privilege
+ * check a legacy user namespace needs before it may map a range. So it is
+ * enforced rather than assumed:
+ *
+ * - a block lives as long as the user namespace object it was given to, which
+ * every credential, and every object owned by the namespace, pins;
+ * - no create, chown or ACL stores an id of the window in a filesystem the cell
+ * does not own, see kuid_storable();
+ * - a legacy user namespace cannot map into the window, see map_write();
+ * - an idmapping of a mount that was made for the cell pins the namespace too,
+ * see alloc_mnt_idmap();
+ * - a released block is quarantined, and goes back to the pool only once the
+ * tables that are keyed by bare kuid hold nothing in its range. The same is
+ * asked before a block is handed out.
+ *
+ * Blocks are handed out round robin so that reuse comes as late as it can. A
+ * quarantined block counts as used: a leak shows up as -ENOSPC, never as one
+ * tenant owning what another left behind.
+ */
+static DEFINE_SPINLOCK(cell_block_lock);
+static DECLARE_BITMAP(cell_block_used, CELL_ID_NR_BLOCKS);
+static DECLARE_BITMAP(cell_block_quarantine, CELL_ID_NR_BLOCKS);
+static unsigned int cell_block_cursor;
+
+static void cell_block_sweep(struct work_struct *work);
+static DECLARE_DELAYED_WORK(cell_block_sweep_work, cell_block_sweep);
+
+static inline u32 cell_block_first(u32 block)
+{
+ return CELL_ID_WINDOW_FIRST + block * CELL_ID_BLOCK_SIZE;
+}
+
+static int cell_block_take(void)
+{
+ unsigned int block;
+
+ guard(spinlock)(&cell_block_lock);
+
+ block = find_next_zero_bit(cell_block_used, CELL_ID_NR_BLOCKS,
+ cell_block_cursor);
+ if (block >= CELL_ID_NR_BLOCKS)
+ block = find_first_zero_bit(cell_block_used, CELL_ID_NR_BLOCKS);
+ if (block >= CELL_ID_NR_BLOCKS)
+ return -ENOSPC;
+
+ __set_bit(block, cell_block_used);
+ cell_block_cursor = (block + 1) % CELL_ID_NR_BLOCKS;
+ return block;
+}
+
+/* Does anything that is keyed by a bare kuid still name an id of @block? */
+static bool cell_block_busy(u32 block)
+{
+ kuid_t first = KUIDT_INIT(cell_block_first(block));
+ kuid_t last = KUIDT_INIT(cell_block_first(block) + CELL_ID_BLOCK_SIZE - 1);
+
+ return uid_range_in_use(first, last) || key_user_range_in_use(first, last);
+}
+
+/* Returns a block number, or a negative error. */
+static int cell_block_alloc(void)
+{
+ int block, tries;
+
+ for (tries = 0; tries < 64; tries++) {
+ block = cell_block_take();
+ if (block < 0 || !cell_block_busy(block))
+ return block;
+ /*
+ * Nothing keeps a task of the host from running on an id of the
+ * window. A block it does that in is not for lending.
+ */
+ cell_block_release(block);
+ }
+ return -ENOSPC;
+}
+
+static void cell_block_sweep(struct work_struct *work)
+{
+ bool again = false;
+ unsigned int block;
+
+ for_each_set_bit(block, cell_block_quarantine, CELL_ID_NR_BLOCKS) {
+ if (cell_block_busy(block)) {
+ again = true;
+ continue;
+ }
+ guard(spinlock)(&cell_block_lock);
+ __clear_bit(block, cell_block_quarantine);
+ __clear_bit(block, cell_block_used);
+ }
+
+ if (again)
+ schedule_delayed_work(&cell_block_sweep_work, 10 * HZ);
+}
+
+/**
+ * cell_block_release - the user namespace that was lent a block is gone
+ * @block: the number of the block
+ *
+ * The block goes into quarantine. It returns to the pool once the sweep finds
+ * nothing left that names one of its ids.
+ *
+ * Context: Process context.
+ */
+void cell_block_release(u32 block)
+{
+ scoped_guard(spinlock, &cell_block_lock)
+ __set_bit(block, cell_block_quarantine);
+ schedule_delayed_work(&cell_block_sweep_work, 0);
+}
+
+static int __init cell_init(void)
+{
+ /* No one user gets to use up the window. */
+ init_user_ns.ucount_max[UCOUNT_CELL_ID_BLOCKS] = CELL_ID_NR_BLOCKS / 8;
+ return 0;
+}
+late_initcall(cell_init);
+
/* The id of whoever made the cell, under its own number. */
static int cell_set_one_id(struct cell *cell)
{
@@ -31,6 +158,32 @@ static int cell_set_one_id(struct cell *cell)
return userns_set_cell_map(ns, &uids, &gids, 0, NULL);
}
+/* A block gives access to nothing, but there are only so many. */
+static int cell_set_id_block(struct cell *cell)
+{
+ struct user_namespace *ns = cell->user_ns;
+ struct uid_gid_extent ids = { .count = CELL_ID_BLOCK_SIZE };
+ struct ucounts *ucounts;
+ int block, ret;
+
+ ucounts = inc_ucount(ns->parent, ns->owner, UCOUNT_CELL_ID_BLOCKS);
+ if (!ucounts)
+ return -ENOSPC;
+ block = cell_block_alloc();
+ if (block < 0) {
+ dec_ucount(ucounts, UCOUNT_CELL_ID_BLOCKS);
+ return block;
+ }
+ /* From 0 up on the inside, uids and gids alike. */
+ ids.lower_first = cell_block_first(block);
+ ret = userns_set_cell_map(ns, &ids, &ids, block + 1, ucounts);
+ if (ret) {
+ cell_block_release(block);
+ dec_ucount(ucounts, UCOUNT_CELL_ID_BLOCKS);
+ }
+ return ret;
+}
+
/**
* cell_set_ids - fill the "ids" slot
* @cell: a cell that is being built
@@ -52,6 +205,9 @@ int cell_set_ids(struct cell *cell, u32 count)
case 1:
ret = cell_set_one_id(cell);
break;
+ case CELL_ID_BLOCK_SIZE:
+ ret = cell_set_id_block(cell);
+ break;
default:
return -EINVAL;
}
--
2.55.0
next prev parent reply other threads:[~2026-11-05 15:11 UTC|newest]
Thread overview: 93+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-05 14:56 [PATCH RFC v3 00/90] cells: a container as one kernel object Josef Bacik
2026-11-05 14:56 ` [PATCH RFC v3 01/90] Documentation: add a design document for cells Josef Bacik
2026-11-05 17:06 ` Serge E. Hallyn
2026-11-05 18:23 ` Josef Bacik
2026-11-05 14:56 ` [PATCH RFC v3 02/90] Documentation: add examples " Josef Bacik
2026-11-05 14:56 ` [PATCH RFC v3 03/90] cells: reserve a window of ids Josef Bacik
2026-11-05 14:56 ` [PATCH RFC v3 04/90] user_namespace: let a cell own a user namespace Josef Bacik
2026-11-05 14:56 ` [PATCH RFC v3 05/90] user_namespace: refuse setns() into a cell Josef Bacik
2026-11-05 14:56 ` [PATCH RFC v3 06/90] capability: give the owner no say over " Josef Bacik
2026-11-05 14:56 ` [PATCH RFC v3 07/90] ucount: count the id blocks a user holds Josef Bacik
2026-11-05 14:56 ` [PATCH RFC v3 08/90] user_namespace: let the kernel map the ids of a cell Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 09/90] user_namespace: keep legacy maps out of the window Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 10/90] user: add uid_range_in_use() Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 11/90] keys: add key_user_range_in_use() Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 12/90] fs: add kuid_has_fsmapping() and kgid_has_fsmapping() Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 13/90] fs: never store an id that was lent to a cell Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 14/90] fs: take no id of a cell for a mount option Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 15/90] fs: let an idmapping pin the ids of a cell Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 16/90] cgroup: let a cgroup become the budget " Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 17/90] cgroup: add cgroup_cell_chown() Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 18/90] cgroup: give the budget of a cell back Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 19/90] cgroup: add cgroup_cell_kill() Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 20/90] cgroup: root a cgroup namespace at a given cgroup Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 21/90] fs: seal a mount namespace for a cell Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 22/90] nsfs: make open_namespace() available outside of fs/ Josef Bacik
2026-11-05 14:57 ` [PATCH RFC v3 23/90] nsproxy: build the nsproxy the tasks of a cell share Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 24/90] cells: add the cell object and cell_create() Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 25/90] cells: add cell_kill() Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 26/90] cells: add cell_configure() Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 27/90] cells: add the "budget" slot Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 28/90] cells: add the "root" slot Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 29/90] cells: add the "ids" slot, for a single id Josef Bacik
2026-11-05 15:02 ` Josef Bacik [this message]
2026-11-05 15:02 ` [PATCH RFC v3 31/90] cells: reserve the "spawn" slot Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 32/90] cells: add CELL_CMD_SEAL Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 33/90] cells: add cell_open() Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 34/90] pid: allocate a struct pid ahead of its numbers Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 35/90] fork: let copy_process() place a task in a cell Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 36/90] cgroup: fork a task of a cell into the budget Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 37/90] x86: give a task without an mm no user state Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 38/90] uprobes: cope with a new task that has no mm Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 39/90] fork: add tasks that start out with nothing Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 40/90] pidfd: add PIDFD_EMPTY Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 41/90] pidfd: add pidfd_configure() Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 42/90] pidfd: let pidfd_configure() place files Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 43/90] pidfd: let pidfd_configure() set credentials Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 44/90] pidfd: let pidfd_configure() name a cgroup to start in Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 45/90] ptrace: keep everybody off an embryo Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 46/90] cells: end a cell when its init exits Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 47/90] cells: refuse a task of a cell a change of namespaces Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 48/90] fs: refuse a task of a cell a new mount namespace Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 49/90] nsproxy: refuse to enter a namespace of a cell Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 50/90] cgroup: keep a task of a cell within the budget Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 51/90] cgroup: let nothing but a spawn into a budget Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 52/90] kernfs: let a filesystem refuse a change to a node Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 53/90] cgroup: let a cell change nothing outside its budget Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 54/90] cgroup: leave the limits of a budget to who is above Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 55/90] fs: let a filesystem hold ids that go with the cell Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 56/90] cgroup: let a cell make cgroups below its budget Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 57/90] resctrl: let a cell change nothing Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 58/90] cred: let no id count across cells Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 59/90] ptrace: refuse PTRACE_TRACEME " Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 60/90] keys: ignore an equal uid or gid " Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 61/90] fs: accept a cellfd for MOUNT_ATTR_IDMAP Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 62/90] fs: let a cell set no set-id bits Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 63/90] fs: drop privileges from what a cell may write to Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 64/90] cells: add the "caps" slot Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 65/90] capabilities: take nothing from a file in a cell Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 66/90] capabilities: let a cell shrink its bounding sets Josef Bacik
2026-11-05 15:02 ` [PATCH RFC v3 67/90] af_unix: deliver ancillary data only within a cell Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 68/90] af_unix: tell a sender that control data will not cross Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 69/90] net: do not identify a peer in another cell Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 70/90] seccomp: let a listener put no file into a cell Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 71/90] umh: run no program for a task of " Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 72/90] coredump: let no core dump leave " Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 73/90] cells: add the "net" slot Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 74/90] net: give a cell without a network no sockets Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 75/90] net: add net_may() Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 76/90] rtnetlink: use net_may() for requests that change state Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 77/90] net: use net_may() in the configuration ioctls Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 78/90] netfilter: use net_may() for configuration Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 79/90] tun: use net_may() to create and attach to devices Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 80/90] net: use net_may() for the net sysctls Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 81/90] net: use net_may() for privileged socket options Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 82/90] net: use net_may() for raw and packet sockets Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 83/90] cells: add the "net-rights" slot Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 84/90] net: honour the rights of a cell in net_may() Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 85/90] tun: ignore an equal uid or gid across cells Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 86/90] cells: add cell_info() Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 87/90] cells: add CONFIG_CELLS Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 88/90] arch: wire up the cell system calls Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 89/90] MAINTAINERS: add an entry for cells Josef Bacik
2026-11-05 15:03 ` [PATCH RFC v3 90/90] selftests: add tests " Josef Bacik
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261105-b4-cells-v3-30-bc9095937a84@toxicpanda.com \
--to=josef@toxicpanda.com \
--cc=James.Bottomley@HansenPartnership.com \
--cc=andrew@amutable.com \
--cc=brauner@kernel.org \
--cc=jbingham@gmail.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=sergeh@kernel.org \
--cc=taherafahimi@linux.microsoft.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®