[PATCH RFC v3 51/90] cgroup: let nothing but a spawn into a budget
From: Josef Bacik
Date: Thu Nov 05 2026 - 11:50:45 EST
A kill has to remove everything from the budget, and the cell is reported
dead once the budget is empty.
In its kthread mode, vhost attaches a kernel thread to the cgroups of the
owner of the device with cgroup_attach_task_all(). A kill does not remove
a kernel thread. So a cell that was given a vhost device could prevent
itself from ever being reported dead.
Refuse to move a task that is in no cell into a budget or below one. Do
this in cgroup_attach_task(), which every way of attaching a task goes
through, and for CLONE_INTO_CGROUP. Budgets do not nest and a task of a
cell stays within its own budget, so no other case needs a check.
Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
---
kernel/cgroup/cgroup.c | 39 +++++++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index 1286f5d318b0..3fc1f9ac2268 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -3087,6 +3087,30 @@ int cgroup_migrate(struct task_struct *leader, bool threadgroup,
return cgroup_migrate_execute(mgctx);
}
+/*
+ * May @task, which cell_may_migrate() has no objection to, be put into @dst?
+ * Budgets do not nest, so a task of a cell is within its own.
+ */
+static bool cgroup_cell_may_enter(struct task_struct *task, struct cgroup *dst)
+{
+ bool in_cell;
+
+ if (!IS_ENABLED(CONFIG_CELLS))
+ return true;
+
+ rcu_read_lock();
+ in_cell = task_cell(task);
+ rcu_read_unlock();
+ if (in_cell)
+ return true;
+
+ for (; dst; dst = cgroup_parent(dst)) {
+ if (test_bit(CGRP_CELL_BUDGET, &dst->flags))
+ return false;
+ }
+ return true;
+}
+
/**
* cgroup_attach_task - attach a task or a whole threadgroup to a cgroup
* @dst_cgrp: the cgroup to attach to
@@ -3110,6 +3134,14 @@ int cgroup_attach_task(struct cgroup *dst_cgrp, struct task_struct *leader,
*/
if (!cell_may_migrate(leader, dst_cgrp))
return -EPERM;
+
+ /*
+ * Nothing else gets into a budget. A kill is to take everything there
+ * with it, which it could not do to a kernel thread.
+ */
+ if (!cgroup_cell_may_enter(leader, dst_cgrp))
+ return -EPERM;
+
/* look up all src csets */
spin_lock_irq(&css_set_lock);
task = leader;
@@ -7024,6 +7056,13 @@ static int cgroup_css_set_fork(struct kernel_clone_args *kargs)
ret = -EPERM;
goto err;
}
+
+ /* And nobody else forks into one. */
+ if (!cgroup_cell_may_enter(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