Merge: CVE-2026-52991: sched/psi: fix race between file release and pressure write

MR: https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-9/-/merge_requests/8578

JIRA: https://redhat.atlassian.net/browse/RHEL-232556
CVE: CVE-2026-52991
MR: https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-9/-/merge_requests/8578

This MR contains the following 3 commits.
 1) commit a5b98009f16d ("sched/psi: fix race between file release and pressure write")
 2) commit fadeedd7cfc5 ("sched/psi: Create the psimon kthread outside of cgroup_mutex")
 3) commit 94a4acfec146 ("cgroup/psi: Set of->priv to NULL upon file release")

Commit fadeedd7cfc5 relies on the last patch to correctly detect if a
file release has happened after releasing and re-acquiring the lock.

Signed-off-by: Waiman Long <longman@redhat.com>

Approved-by: Phil Auld <pauld@redhat.com>
Approved-by: Herton R. Krzesinski <herton@redhat.com>
Approved-by: Rafael Aquini <raquini@redhat.com>
Approved-by: CKI KWF Bot <cki-ci-bot+kwf-gitlab-com@redhat.com>

Merged-by: CKI GitLab Kmaint Pipeline Bot <26919896-cki-kmaint-pipeline-bot@users.noreply.gitlab.com>
This commit is contained in:
CKI KWF Bot
2026-08-18 21:59:40 +00:00
3 changed files with 95 additions and 26 deletions
+3 -1
View File
@@ -24,7 +24,9 @@ void psi_memstall_leave(unsigned long *flags);
int psi_show(struct seq_file *s, struct psi_group *group, enum psi_res res);
struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf,
enum psi_res res, struct file *file,
struct kernfs_open_file *of);
struct kernfs_open_file *of,
bool *need_rtpoll_worker);
int psi_trigger_create_rtpoll_worker(struct psi_group *group);
void psi_trigger_destroy(struct psi_trigger *t);
__poll_t psi_trigger_poll(void **trigger_ptr, struct file *file,
+39 -9
View File
@@ -3882,33 +3882,62 @@ static int cgroup_cpu_pressure_show(struct seq_file *seq, void *v)
static ssize_t pressure_write(struct kernfs_open_file *of, char *buf,
size_t nbytes, enum psi_res res)
{
struct cgroup_file_ctx *ctx = of->priv;
struct cgroup_file_ctx *ctx;
struct psi_trigger *new;
struct cgroup *cgrp;
struct psi_group *psi;
bool need_rtpoll_worker;
ssize_t ret = 0;
cgrp = cgroup_kn_lock_live(of->kn, false);
if (!cgrp)
return -ENODEV;
cgroup_get(cgrp);
cgroup_kn_unlock(of->kn);
ctx = of->priv;
if (!ctx) {
ret = -ENODEV;
goto out_unlock;
}
/* Allow only one trigger per file descriptor */
if (ctx->psi.trigger) {
cgroup_put(cgrp);
return -EBUSY;
ret = -EBUSY;
goto out_unlock;
}
psi = cgroup_psi(cgrp);
new = psi_trigger_create(psi, buf, res, of->file, of);
new = psi_trigger_create(psi, buf, res, of->file, of,
&need_rtpoll_worker);
if (IS_ERR(new)) {
cgroup_put(cgrp);
return PTR_ERR(new);
ret = PTR_ERR(new);
goto out_unlock;
}
/*
* The worker fork must run with neither cgroup_mutex nor the file's
* kernfs active reference held. The latter is broken since
* cgroup_kn_lock_live(). @of->priv may be released while unlocked, so
* recheck before publishing @new.
*/
if (need_rtpoll_worker) {
cgroup_unlock();
ret = psi_trigger_create_rtpoll_worker(psi);
cgroup_lock();
if (!ret && !of->priv)
ret = -ENODEV;
if (ret) {
psi_trigger_destroy(new);
goto out_unlock;
}
}
smp_store_release(&ctx->psi.trigger, new);
cgroup_put(cgrp);
out_unlock:
cgroup_kn_unlock(of->kn);
if (ret)
return ret;
return nbytes;
}
@@ -4168,6 +4197,7 @@ static void cgroup_file_release(struct kernfs_open_file *of)
cft->release(of);
put_cgroup_ns(ctx->ns);
kfree(ctx);
of->priv = NULL;
}
static ssize_t cgroup_file_write(struct kernfs_open_file *of, char *buf,
+53 -16
View File
@@ -1312,9 +1312,44 @@ static int psi_cpu_open(struct inode *inode, struct file *file)
return single_open(file, psi_cpu_show, NULL);
}
/*
* Create @group's rtpoll worker after psi_trigger_create() reported the need
* for one. kthread creation depends on the whole fork path and we don't want
* all of that nested inside cgroup_mutex, so the caller must drop it and any
* other lock that forks can wait behind. If two callers race, the loser stops
* its never-woken kthread.
*/
int psi_trigger_create_rtpoll_worker(struct psi_group *group)
{
struct task_struct *task;
task = kthread_create(psi_rtpoll_worker, group, "psimon");
if (IS_ERR(task))
return PTR_ERR(task);
scoped_guard(mutex, &group->rtpoll_trigger_lock) {
if (!rcu_access_pointer(group->rtpoll_task)) {
atomic_set(&group->rtpoll_wakeup, 0);
wake_up_process(task);
rcu_assign_pointer(group->rtpoll_task, task);
/*
* Poll once to catch up on scheduling attempts dropped
* while there was no rtpoll worker.
*/
psi_schedule_rtpoll_work(group, 1, true);
return 0;
}
}
kthread_stop(task);
return 0;
}
struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf,
enum psi_res res, struct file *file,
struct kernfs_open_file *of)
struct kernfs_open_file *of,
bool *need_rtpoll_worker)
{
struct psi_trigger *t;
enum psi_states state;
@@ -1322,6 +1357,8 @@ struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf,
bool privileged;
u32 window_us;
*need_rtpoll_worker = false;
if (static_branch_likely(&psi_disabled))
return ERR_PTR(-EOPNOTSUPP);
@@ -1381,26 +1418,14 @@ struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf,
if (privileged) {
mutex_lock(&group->rtpoll_trigger_lock);
if (!rcu_access_pointer(group->rtpoll_task)) {
struct task_struct *task;
task = kthread_create(psi_rtpoll_worker, group, "psimon");
if (IS_ERR(task)) {
kfree(t);
mutex_unlock(&group->rtpoll_trigger_lock);
return ERR_CAST(task);
}
atomic_set(&group->rtpoll_wakeup, 0);
wake_up_process(task);
rcu_assign_pointer(group->rtpoll_task, task);
}
list_add(&t->node, &group->rtpoll_triggers);
group->rtpoll_min_period = min(group->rtpoll_min_period,
div_u64(t->win.size, UPDATES_PER_WINDOW));
group->rtpoll_nr_triggers[t->state]++;
group->rtpoll_states |= (1 << t->state);
*need_rtpoll_worker = !rcu_access_pointer(group->rtpoll_task);
mutex_unlock(&group->rtpoll_trigger_lock);
} else {
mutex_lock(&group->avgs_lock);
@@ -1524,6 +1549,8 @@ static ssize_t psi_write(struct file *file, const char __user *user_buf,
size_t buf_size;
struct seq_file *seq;
struct psi_trigger *new;
bool need_rtpoll_worker;
int ret;
if (static_branch_likely(&psi_disabled))
return -EOPNOTSUPP;
@@ -1548,12 +1575,22 @@ static ssize_t psi_write(struct file *file, const char __user *user_buf,
return -EBUSY;
}
new = psi_trigger_create(&psi_system, buf, res, file, NULL);
new = psi_trigger_create(&psi_system, buf, res, file, NULL,
&need_rtpoll_worker);
if (IS_ERR(new)) {
mutex_unlock(&seq->lock);
return PTR_ERR(new);
}
if (need_rtpoll_worker) {
ret = psi_trigger_create_rtpoll_worker(&psi_system);
if (ret) {
psi_trigger_destroy(new);
mutex_unlock(&seq->lock);
return ret;
}
}
smp_store_release(&seq->private, new);
mutex_unlock(&seq->lock);