[PATCH RFC v3 50/90] cgroup: keep a task of a cell within the budget
From: Josef Bacik
Date: Thu Nov 05 2026 - 10:25:56 EST
The kernel finds the tasks of a cell through the budget. A task that got
out of the budget would survive a kill, and the cell would be reported
dead while that task is still running.
Refuse to migrate a task of a cell out of the budget, no matter who asks.
Refuse CLONE_INTO_CGROUP by a task of a cell if the target is outside of
the budget. On every other hierarchy, refuse to move the task away from
the cgroup it was born in.
The kernel itself may still move such a task. cgroup_transfer_tasks()
empties a legacy cpuset that has lost its last cpu, and those tasks have
to go somewhere. It does not operate on the default hierarchy, so it never
touches a budget.
Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
---
include/linux/cell.h | 7 +++++++
kernel/cell/Makefile | 1 +
kernel/cell/boundary.c | 36 ++++++++++++++++++++++++++++++++++++
kernel/cgroup/cgroup.c | 13 +++++++++++++
4 files changed, 57 insertions(+)
diff --git a/include/linux/cell.h b/include/linux/cell.h
index 86999efc083d..0c3c65b93f2b 100644
--- a/include/linux/cell.h
+++ b/include/linux/cell.h
@@ -235,4 +235,11 @@ void cell_init_exited(struct cell *cell, struct task_struct *tsk);
static inline void cell_init_exited(struct cell *cell, struct task_struct *tsk) { }
#endif
+/* A task of a cell stays within the budget. */
+#ifdef CONFIG_CELLS
+bool cell_may_migrate(struct task_struct *task, struct cgroup *dst);
+#else
+static inline bool cell_may_migrate(struct task_struct *t, struct cgroup *d) { return true; }
+#endif
+
#endif /* _LINUX_CELL_H */
diff --git a/kernel/cell/Makefile b/kernel/cell/Makefile
index 1a2dcd469d10..0e5bc34808db 100644
--- a/kernel/cell/Makefile
+++ b/kernel/cell/Makefile
@@ -3,3 +3,4 @@ obj-y += core.o
obj-y += slots.o
obj-y += ids.o
obj-y += spawn.o
+obj-y += boundary.o
diff --git a/kernel/cell/boundary.c b/kernel/cell/boundary.c
new file mode 100644
index 000000000000..251a8516e76a
--- /dev/null
+++ b/kernel/cell/boundary.c
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Cells: what other subsystems ask about the boundary of a cell.
+ */
+#include <linux/cgroup.h>
+#include <linux/user_namespace.h>
+#include <uapi/linux/cell.h>
+
+#include "internal.h"
+
+/**
+ * cell_may_migrate - may a task be moved to a cgroup?
+ * @task: the task
+ * @dst: the cgroup
+ *
+ * A task of a cell stays within the budget of the cell, whoever asks. On a
+ * hierarchy other than that of the budget no request moves it from where it was
+ * born. This is asked for every request. It is not asked when the kernel itself
+ * empties a legacy cpuset that lost its last cpu, see cgroup_transfer_tasks(),
+ * which works on legacy hierarchies only and so never touches a budget.
+ *
+ * Return: true if @task is in no cell, or @dst is the budget of its cell or
+ * lies below it.
+ */
+bool cell_may_migrate(struct task_struct *task, struct cgroup *dst)
+{
+ struct cgroup *budget;
+ struct cell *cell;
+
+ guard(rcu)();
+ cell = task_cell(task);
+ if (!cell)
+ return true;
+ budget = READ_ONCE(cell->budget);
+ return budget && cgroup_is_descendant(dst, budget);
+}
diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index d3a36b586cd3..1286f5d318b0 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -3102,6 +3102,14 @@ int cgroup_attach_task(struct cgroup *dst_cgrp, struct task_struct *leader,
struct task_struct *task;
int ret = 0;
+ /*
+ * A task of a cell stays within the cell's budget, whoever asks. On
+ * any other hierarchy no request moves it from where it was born. The
+ * kernel itself may, with cgroup_transfer_tasks(), out of a legacy
+ * cpuset that lost its last cpu. That never touches a budget.
+ */
+ if (!cell_may_migrate(leader, dst_cgrp))
+ return -EPERM;
/* look up all src csets */
spin_lock_irq(&css_set_lock);
task = leader;
@@ -7011,6 +7019,11 @@ static int cgroup_css_set_fork(struct kernel_clone_args *kargs)
goto err;
}
+ /* A task of a cell stays within the cell's budget, children and all. */
+ if (!cell_may_migrate(current, dst_cgrp)) {
+ ret = -EPERM;
+ goto err;
+ }
/*
* Verify that we the target cgroup is writable for us. This is
* usually done by the vfs layer but since we're not going through
--
2.55.0