mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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


  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®