[PATCH RFC v3 16/90] cgroup: let a cgroup become the budget of a cell
From: Josef Bacik
Date: Thu Nov 05 2026 - 10:27:27 EST
A cell needs a limit on the resources it can use, and a way to find every
task in it, including forks that are in flight. A cgroup provides both.
Add cgroup_cell_budget_get(). It takes a cgroup2 directory as the budget
of a cell and marks it with CGRP_CELL_BUDGET.
>From then on, holding the cellfd is enough to put a task into that cgroup.
So the caller is checked here instead. It must pass every check that
moving one of its own tasks into the cgroup would require, and it must own
the directory.
Killing a cell kills everything in its budget, so a budget can only belong
to one cell. The cgroup must be empty, must have no children, and must not
be the budget of another cell or lie inside of one.
Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
---
include/linux/cgroup-defs.h | 3 ++
include/linux/cgroup.h | 2 ++
kernel/cgroup/cgroup.c | 82 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 87 insertions(+)
diff --git a/include/linux/cgroup-defs.h b/include/linux/cgroup-defs.h
index cbfe8937647c..4626adbf45d6 100644
--- a/include/linux/cgroup-defs.h
+++ b/include/linux/cgroup-defs.h
@@ -72,6 +72,9 @@ enum {
/* Cgroup is frozen. */
CGRP_FROZEN,
+
+ /* Cgroup is the budget of a cell. */
+ CGRP_CELL_BUDGET,
};
/* cgroup_root->flags */
diff --git a/include/linux/cgroup.h b/include/linux/cgroup.h
index 2afb4cb2bb4f..a5ef01688e7f 100644
--- a/include/linux/cgroup.h
+++ b/include/linux/cgroup.h
@@ -152,6 +152,8 @@ struct cgroup_subsys_state *css_tryget_online_from_dir(struct dentry *dentry,
struct cgroup *cgroup_get_from_path(const char *path);
struct cgroup *cgroup_get_from_fd(int fd);
+
+struct cgroup *cgroup_cell_budget_get(struct file *file, kuid_t *uid, kgid_t *gid);
struct cgroup *cgroup_v1v2_get_from_fd(int fd);
/* Was this controller blocked from v1 hierarchies by cgroup_no_v1= ? */
diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index a284225f8dc9..c4bbb70f490f 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -7044,6 +7044,88 @@ static void cgroup_css_set_put_fork(struct kernel_clone_args *kargs)
}
}
+#ifdef CONFIG_CELLS
+/**
+ * cgroup_cell_budget_get - take a cgroup as the budget of a cell
+ * @file: a cgroup2 directory
+ * @uid: returns who owns it now
+ * @gid: likewise
+ *
+ * From here on holding the cellfd is all it takes to put a task into the
+ * cgroup, and sealing the cell hands the cgroup to the cell's root. So whoever
+ * fills the slot must be allowed both: everything moving a task of its own
+ * there would take, and ownership of the directory.
+ *
+ * Killing the cell kills everything in the cgroup. So it must be empty, have no
+ * children, and neither be nor lie within the budget of another cell.
+ *
+ * Return: the cgroup, marked as a budget and with a reference, both for
+ * cgroup_cell_budget_put() to undo, or an ERR_PTR(). Among the errors are
+ * -EBADF if @file is no cgroup2 directory, -EPERM or -EACCES if the caller may
+ * not, -EBUSY if the cgroup will not do and -ENODEV if it is dead.
+ */
+struct cgroup *cgroup_cell_budget_get(struct file *file, kuid_t *uid, kgid_t *gid)
+{
+ struct super_block *sb = file->f_path.dentry->d_sb;
+ struct cgroup *cgrp, *anc;
+ struct css_set *cset;
+ struct kstat stat;
+ int ret;
+
+ cgrp = cgroup_get_from_file(file);
+ if (IS_ERR(cgrp))
+ return cgrp;
+
+ /* Also brings the inode up to date with the kernfs node. */
+ ret = vfs_getattr_nosec(&file->f_path, &stat, STATX_UID | STATX_GID, 0);
+ if (ret) {
+ cgroup_put(cgrp);
+ return ERR_PTR(ret);
+ }
+
+ cgroup_lock();
+
+ spin_lock_irq(&css_set_lock);
+ cset = task_css_set(current);
+ get_css_set(cset);
+ spin_unlock_irq(&css_set_lock);
+
+ ret = -ENODEV;
+ if (cgroup_is_dead(cgrp))
+ goto out;
+ ret = -EPERM;
+ if (!inode_owner_or_capable(&nop_mnt_idmap, file_inode(file)))
+ goto out;
+ ret = cgroup_may_write(cgrp, sb);
+ if (ret)
+ goto out;
+ ret = cgroup_attach_permissions(cset->dfl_cgrp, cgrp, sb, true,
+ current->nsproxy->cgroup_ns);
+ if (ret)
+ goto out;
+ ret = -EBUSY;
+ if (cgroup_is_populated(cgrp) || css_has_online_children(&cgrp->self))
+ goto out;
+ for (anc = cgrp; anc; anc = cgroup_parent(anc)) {
+ if (test_bit(CGRP_CELL_BUDGET, &anc->flags))
+ goto out;
+ }
+
+ set_bit(CGRP_CELL_BUDGET, &cgrp->flags);
+ *uid = stat.uid;
+ *gid = stat.gid;
+ ret = 0;
+out:
+ put_css_set(cset);
+ cgroup_unlock();
+ if (ret) {
+ cgroup_put(cgrp);
+ return ERR_PTR(ret);
+ }
+ return cgrp;
+}
+#endif /* CONFIG_CELLS */
+
/**
* cgroup_can_fork - called on a new task before the process is exposed
* @child: the child process
--
2.55.0