* FAILED: patch "[PATCH] ucount: take the rlimit reference before checking the limit" failed to apply to 5.15-stable tree
@ 2026-11-07 11:10 Sasha Levin
2026-11-07 13:56 ` Alexey Gladkov
2026-11-10 22:41 ` [PATCH 5.15.y 1/4] ucounts: Use atomic_long_sub_return for clarity Sasha Levin
0 siblings, 2 replies; 6+ messages in thread
From: Sasha Levin @ 2026-11-07 11:10 UTC (permalink / raw)
To: nickolai, legion, brauner; +Cc: stable
The patch below does not apply to the 5.15-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-5.15.y
git checkout FETCH_HEAD
git cherry-pick -x b14fcabd34e02c40f72d3cd1e9bd4fd18df40ff1
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<stable@vger.kernel.org>' --in-reply-to '20261107061036-failed-5.15-b14fcabd34e0@kernel.org' --subject-prefix 'PATCH 5.15.y' 'HEAD^..'
Thanks,
Sasha
------------------ original commit in Linus's tree ------------------
From b14fcabd34e02c40f72d3cd1e9bd4fd18df40ff1 Mon Sep 17 00:00:00 2001
From: Nickolai Zeldovich <nickolai@csail.mit.edu>
Date: Mon, 19 Oct 2026 14:59:13 -0400
Subject: [PATCH] ucount: take the rlimit reference before checking the limit
inc_rlimit_get_ucounts() takes the reference that pins a ucounts level
while its rlimit count is non-zero only in the walk whose increment is
the 0 -> 1 transition, and only after that walk passed the limit check.
A walk that raises the count from zero and then fails the limit backs
out without the reference, while a concurrent walk that raised it past
one keeps its increment and never takes one either:
CPU0 (obeys the limit) CPU1 (overrides it)
new = add_return(1) -> 1
new = add_return(1) -> 2
new != 1, no get_ucounts()
new > max -> dec_unwind
sub_return(1) -> 1 (no put)
The count is 1 with no reference held. When CPU1 drops its charge,
dec_rlimit_put_ucounts() takes the count to 0 and puts a reference
nobody took, which frees the live struct ucounts, and the user_namespace
it pins, with kfree_rcu(). The mirror interleaving leaks the reference
instead.
An unprivileged user reaches this on a kernel with CONFIG_USER_NS=y: in
a user namespace with RLIMIT_SIGPENDING 0, one process taking
synchronous SIGSEGVs (forced, so override_rlimit is set) races a second
process in the same namespace and uid that queues real-time signals,
which obey the limit. The two hold different sighand->siglock but charge
the same per-namespace SIGPENDING ucounts. Unpatched, with rcuref
debugging:
rcuref - imbalanced put()
WARNING: lib/rcuref.c:266 at rcuref_put_slowpath+0xf4/0x120
put_ucounts+0x5a/0x180
do_dec_rlimit_put_ucounts+0x70/0x90
__sigqueue_free+0x33/0xc0
get_signal+0x56f/0xd20
arch_do_signal_or_restart+0x87/0x380
irqentry_exit+0x243/0x650
asm_exc_page_fault+0x26/0x30
Take the reference on the 0 -> 1 transition before the limit is checked,
so the walk that raises the count from zero owns that level's reference
even when its request is rejected, and unwind a rejected level through
do_dec_rlimit_put_ucounts(), which releases the reference when the count
returns to 0. The get_ucounts() failure path is unchanged. With the
patch the same interleaving, forced some 80000 times in a KASAN build,
produces no warning and no KASAN report.
Fixes: 15bc01effefe ("ucounts: Fix signal ucount refcounting")
Cc: stable@vger.kernel.org
Signed-off-by: Nickolai Zeldovich <nickolai@csail.mit.edu>
Link: https://patch.msgid.link/20261019185913.805378-1-nickolai@csail.mit.edu
Acked-by: Alexey Gladkov <legion@kernel.org>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
kernel/ucount.c | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/kernel/ucount.c b/kernel/ucount.c
index ec8b1445e2877..c3de5b94d25f7 100644
--- a/kernel/ucount.c
+++ b/kernel/ucount.c
@@ -308,23 +308,26 @@ long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum rlimit_type type,
for (iter = ucounts; iter; iter = iter->ns->ucounts) {
long new = atomic_long_add_return(1, &iter->rlimit[type]);
- if (new < 0 || new > max)
+ /*
+ * Take the extra reference on the 0 -> 1 transition before the
+ * limit check: a rejected walk must not leave a concurrent one
+ * holding the surviving increment with no reference.
+ */
+ if (new == 1 && !get_ucounts(iter))
goto dec_unwind;
+ if (new < 0 || new > max) {
+ /* undo iter too, releasing the reference taken above */
+ do_dec_rlimit_put_ucounts(ucounts, iter->ns->ucounts, type);
+ return 0;
+ }
if (iter == ucounts)
ret = new;
if (!override_rlimit)
max = get_userns_rlimit_max(iter->ns, type);
- /*
- * Grab an extra ucount reference for the caller when
- * the rlimit count was previously 0.
- */
- if (new != 1)
- continue;
- if (!get_ucounts(iter))
- goto dec_unwind;
}
return ret;
dec_unwind:
+ /* get_ucounts() failed for iter: undo iter without a put */
dec = atomic_long_sub_return(1, &iter->rlimit[type]);
WARN_ON_ONCE(dec < 0);
do_dec_rlimit_put_ucounts(ucounts, iter, type);
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: FAILED: patch "[PATCH] ucount: take the rlimit reference before checking the limit" failed to apply to 5.15-stable tree
2026-11-07 11:10 FAILED: patch "[PATCH] ucount: take the rlimit reference before checking the limit" failed to apply to 5.15-stable tree Sasha Levin
@ 2026-11-07 13:56 ` Alexey Gladkov
2026-11-10 22:41 ` [PATCH 5.15.y 1/4] ucounts: Use atomic_long_sub_return for clarity Sasha Levin
1 sibling, 0 replies; 6+ messages in thread
From: Alexey Gladkov @ 2026-11-07 13:56 UTC (permalink / raw)
To: Sasha Levin; +Cc: nickolai, brauner, stable
On Sat, Nov 07, 2026 at 06:10:36AM -0500, Sasha Levin wrote:
> The patch below does not apply to the 5.15-stable tree.
> If someone wants it applied there, or to any other stable or longterm
> tree, then please email the backport, including the original git commit
> id to <stable@vger.kernel.org>.
>
> To reproduce the conflict and resubmit, you may use the following commands:
>
> git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-5.15.y
> git checkout FETCH_HEAD
> git cherry-pick -x b14fcabd34e02c40f72d3cd1e9bd4fd18df40ff1
> # <resolve conflicts, build, test, etc.>
> git commit -s
> git send-email --to '<stable@vger.kernel.org>' --in-reply-to '20261107061036-failed-5.15-b14fcabd34e0@kernel.org' --subject-prefix 'PATCH 5.15.y' 'HEAD^..'
The 5.15-stable is too old for this change. The branch does not contain
commits that separate ucounts and rlimits.
9e05e5c7ee87 ("signal: restore the override_rlimit logic")
de399236e240 ("ucounts: Split rlimit and ucount values and max values")
(and most likely there are still a few more.)
I don't think it's worth backporting these changes.
> Thanks,
> Sasha
>
> ------------------ original commit in Linus's tree ------------------
>
> From b14fcabd34e02c40f72d3cd1e9bd4fd18df40ff1 Mon Sep 17 00:00:00 2001
> From: Nickolai Zeldovich <nickolai@csail.mit.edu>
> Date: Mon, 19 Oct 2026 14:59:13 -0400
> Subject: [PATCH] ucount: take the rlimit reference before checking the limit
>
> inc_rlimit_get_ucounts() takes the reference that pins a ucounts level
> while its rlimit count is non-zero only in the walk whose increment is
> the 0 -> 1 transition, and only after that walk passed the limit check.
> A walk that raises the count from zero and then fails the limit backs
> out without the reference, while a concurrent walk that raised it past
> one keeps its increment and never takes one either:
>
> CPU0 (obeys the limit) CPU1 (overrides it)
> new = add_return(1) -> 1
> new = add_return(1) -> 2
> new != 1, no get_ucounts()
> new > max -> dec_unwind
> sub_return(1) -> 1 (no put)
>
> The count is 1 with no reference held. When CPU1 drops its charge,
> dec_rlimit_put_ucounts() takes the count to 0 and puts a reference
> nobody took, which frees the live struct ucounts, and the user_namespace
> it pins, with kfree_rcu(). The mirror interleaving leaks the reference
> instead.
>
> An unprivileged user reaches this on a kernel with CONFIG_USER_NS=y: in
> a user namespace with RLIMIT_SIGPENDING 0, one process taking
> synchronous SIGSEGVs (forced, so override_rlimit is set) races a second
> process in the same namespace and uid that queues real-time signals,
> which obey the limit. The two hold different sighand->siglock but charge
> the same per-namespace SIGPENDING ucounts. Unpatched, with rcuref
> debugging:
>
> rcuref - imbalanced put()
> WARNING: lib/rcuref.c:266 at rcuref_put_slowpath+0xf4/0x120
> put_ucounts+0x5a/0x180
> do_dec_rlimit_put_ucounts+0x70/0x90
> __sigqueue_free+0x33/0xc0
> get_signal+0x56f/0xd20
> arch_do_signal_or_restart+0x87/0x380
> irqentry_exit+0x243/0x650
> asm_exc_page_fault+0x26/0x30
>
> Take the reference on the 0 -> 1 transition before the limit is checked,
> so the walk that raises the count from zero owns that level's reference
> even when its request is rejected, and unwind a rejected level through
> do_dec_rlimit_put_ucounts(), which releases the reference when the count
> returns to 0. The get_ucounts() failure path is unchanged. With the
> patch the same interleaving, forced some 80000 times in a KASAN build,
> produces no warning and no KASAN report.
>
> Fixes: 15bc01effefe ("ucounts: Fix signal ucount refcounting")
> Cc: stable@vger.kernel.org
> Signed-off-by: Nickolai Zeldovich <nickolai@csail.mit.edu>
> Link: https://patch.msgid.link/20261019185913.805378-1-nickolai@csail.mit.edu
> Acked-by: Alexey Gladkov <legion@kernel.org>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
> ---
> kernel/ucount.c | 21 ++++++++++++---------
> 1 file changed, 12 insertions(+), 9 deletions(-)
>
> diff --git a/kernel/ucount.c b/kernel/ucount.c
> index ec8b1445e2877..c3de5b94d25f7 100644
> --- a/kernel/ucount.c
> +++ b/kernel/ucount.c
> @@ -308,23 +308,26 @@ long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum rlimit_type type,
>
> for (iter = ucounts; iter; iter = iter->ns->ucounts) {
> long new = atomic_long_add_return(1, &iter->rlimit[type]);
> - if (new < 0 || new > max)
> + /*
> + * Take the extra reference on the 0 -> 1 transition before the
> + * limit check: a rejected walk must not leave a concurrent one
> + * holding the surviving increment with no reference.
> + */
> + if (new == 1 && !get_ucounts(iter))
> goto dec_unwind;
> + if (new < 0 || new > max) {
> + /* undo iter too, releasing the reference taken above */
> + do_dec_rlimit_put_ucounts(ucounts, iter->ns->ucounts, type);
> + return 0;
> + }
> if (iter == ucounts)
> ret = new;
> if (!override_rlimit)
> max = get_userns_rlimit_max(iter->ns, type);
> - /*
> - * Grab an extra ucount reference for the caller when
> - * the rlimit count was previously 0.
> - */
> - if (new != 1)
> - continue;
> - if (!get_ucounts(iter))
> - goto dec_unwind;
> }
> return ret;
> dec_unwind:
> + /* get_ucounts() failed for iter: undo iter without a put */
> dec = atomic_long_sub_return(1, &iter->rlimit[type]);
> WARN_ON_ONCE(dec < 0);
> do_dec_rlimit_put_ucounts(ucounts, iter, type);
> --
> 2.53.0
>
>
>
>
--
Rgrds, legion
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 5.15.y 1/4] ucounts: Use atomic_long_sub_return for clarity
2026-11-07 11:10 FAILED: patch "[PATCH] ucount: take the rlimit reference before checking the limit" failed to apply to 5.15-stable tree Sasha Levin
2026-11-07 13:56 ` Alexey Gladkov
@ 2026-11-10 22:41 ` Sasha Levin
2026-11-10 22:41 ` [PATCH 5.15.y 2/4] ucounts: Split rlimit and ucount values and max values Sasha Levin
` (2 more replies)
1 sibling, 3 replies; 6+ messages in thread
From: Sasha Levin @ 2026-11-10 22:41 UTC (permalink / raw)
To: stable; +Cc: Eric W. Biederman, Yu Zhao, Alexey Gladkov, Sasha Levin
From: "Eric W. Biederman" <ebiederm@xmission.com>
[ Upstream commit 32342701b4ba57a6fd77e8aca2f65f68c0fa1da6 ]
Decrement ucounts using atomic_long_sub_return to make it
clear the point is for the ucount to decrease.
Not a big deal but it should make it easier to catch bugs.
Suggested-by: Yu Zhao <yuzhao@google.com>
Link: https://lkml.kernel.org/r/87k0iaqkqj.fsf_-_@disp2133
Tested-by: Yu Zhao <yuzhao@google.com>
Reviewed-by: Alexey Gladkov <legion@kernel.org>
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
Stable-dep-of: b14fcabd34e0 ("ucount: take the rlimit reference before checking the limit")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
kernel/ucount.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/kernel/ucount.c b/kernel/ucount.c
index cc8ec071d46d3..a751fb6e94e51 100644
--- a/kernel/ucount.c
+++ b/kernel/ucount.c
@@ -279,7 +279,7 @@ bool dec_rlimit_ucounts(struct ucounts *ucounts, enum ucount_type type, long v)
struct ucounts *iter;
long new = -1; /* Silence compiler warning */
for (iter = ucounts; iter; iter = iter->ns->ucounts) {
- long dec = atomic_long_add_return(-v, &iter->ucount[type]);
+ long dec = atomic_long_sub_return(v, &iter->ucount[type]);
WARN_ON_ONCE(dec < 0);
if (iter == ucounts)
new = dec;
@@ -292,7 +292,7 @@ static void do_dec_rlimit_put_ucounts(struct ucounts *ucounts,
{
struct ucounts *iter, *next;
for (iter = ucounts; iter != last; iter = next) {
- long dec = atomic_long_add_return(-1, &iter->ucount[type]);
+ long dec = atomic_long_sub_return(1, &iter->ucount[type]);
WARN_ON_ONCE(dec < 0);
next = iter->ns->ucounts;
if (dec == 0)
@@ -330,7 +330,7 @@ long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum ucount_type type)
}
return ret;
dec_unwind:
- dec = atomic_long_add_return(-1, &iter->ucount[type]);
+ dec = atomic_long_sub_return(1, &iter->ucount[type]);
WARN_ON_ONCE(dec < 0);
do_dec_rlimit_put_ucounts(ucounts, iter, type);
return 0;
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 5.15.y 2/4] ucounts: Split rlimit and ucount values and max values
2026-11-10 22:41 ` [PATCH 5.15.y 1/4] ucounts: Use atomic_long_sub_return for clarity Sasha Levin
@ 2026-11-10 22:41 ` Sasha Levin
2026-11-10 22:41 ` [PATCH 5.15.y 3/4] signal: restore the override_rlimit logic Sasha Levin
2026-11-10 22:41 ` [PATCH 5.15.y 4/4] ucount: take the rlimit reference before checking the limit Sasha Levin
2 siblings, 0 replies; 6+ messages in thread
From: Sasha Levin @ 2026-11-10 22:41 UTC (permalink / raw)
To: stable; +Cc: Alexey Gladkov, kernel test robot, Eric W. Biederman, Sasha Levin
From: Alexey Gladkov <legion@kernel.org>
[ Upstream commit de399236e240743ad2dd10d719c37b97ddf31996 ]
Since the semantics of maximum rlimit values are different, it would be
better not to mix ucount and rlimit values. This will prevent the error
of using inc_count/dec_ucount for rlimit parameters.
This patch also renames the functions to emphasize the lack of
connection between rlimit and ucount.
v3:
- Fix BUG:KASAN:use-after-free_in_dec_ucount.
v2:
- Fix the array-index-out-of-bounds that was found by the lkp project.
Reported-by: kernel test robot <oliver.sang@intel.com>
Signed-off-by: Alexey Gladkov <legion@kernel.org>
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
Link: https://lkml.kernel.org/r/20220518171730.l65lmnnjtnxnftpq@example.org
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
Stable-dep-of: b14fcabd34e0 ("ucount: take the rlimit reference before checking the limit")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/exec.c | 2 +-
fs/proc/array.c | 2 +-
include/linux/user_namespace.h | 35 +++++++++++++++++++++-------------
kernel/fork.c | 12 ++++++------
kernel/sys.c | 2 +-
kernel/ucount.c | 34 +++++++++++++++------------------
kernel/user_namespace.c | 10 +++++-----
7 files changed, 51 insertions(+), 46 deletions(-)
diff --git a/fs/exec.c b/fs/exec.c
index 5efe7ae6f9685..e573d35cb2037 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1914,7 +1914,7 @@ static int do_execveat_common(int fd, struct filename *filename,
* whether NPROC limit is still exceeded.
*/
if ((current->flags & PF_NPROC_EXCEEDED) &&
- is_ucounts_overlimit(current_ucounts(), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) {
+ is_rlimit_overlimit(current_ucounts(), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) {
retval = -EAGAIN;
goto out_ret;
}
diff --git a/fs/proc/array.c b/fs/proc/array.c
index 65f4e4df785d6..d7101eb734316 100644
--- a/fs/proc/array.c
+++ b/fs/proc/array.c
@@ -274,7 +274,7 @@ static inline void task_sig(struct seq_file *m, struct task_struct *p)
collect_sigign_sigcatch(p, &ignored, &caught);
num_threads = get_nr_threads(p);
rcu_read_lock(); /* FIXME: is this correct? */
- qsize = get_ucounts_value(task_ucounts(p), UCOUNT_RLIMIT_SIGPENDING);
+ qsize = get_rlimit_value(task_ucounts(p), UCOUNT_RLIMIT_SIGPENDING);
rcu_read_unlock();
qlim = task_rlimit(p, RLIMIT_SIGPENDING);
unlock_task_sighand(p, &flags);
diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
index 33a4240e6a6f1..45f09bec02c48 100644
--- a/include/linux/user_namespace.h
+++ b/include/linux/user_namespace.h
@@ -54,15 +54,17 @@ enum ucount_type {
UCOUNT_FANOTIFY_GROUPS,
UCOUNT_FANOTIFY_MARKS,
#endif
+ UCOUNT_COUNTS,
+};
+
+enum rlimit_type {
UCOUNT_RLIMIT_NPROC,
UCOUNT_RLIMIT_MSGQUEUE,
UCOUNT_RLIMIT_SIGPENDING,
UCOUNT_RLIMIT_MEMLOCK,
- UCOUNT_COUNTS,
+ UCOUNT_RLIMIT_COUNTS,
};
-#define MAX_PER_NAMESPACE_UCOUNTS UCOUNT_RLIMIT_NPROC
-
struct user_namespace {
struct uid_gid_map uid_map;
struct uid_gid_map gid_map;
@@ -99,6 +101,7 @@ struct user_namespace {
#endif
struct ucounts *ucounts;
long ucount_max[UCOUNT_COUNTS];
+ long rlimit_max[UCOUNT_RLIMIT_COUNTS];
} __randomize_layout;
struct ucounts {
@@ -107,6 +110,7 @@ struct ucounts {
kuid_t uid;
atomic_t count;
atomic_long_t ucount[UCOUNT_COUNTS];
+ atomic_long_t rlimit[UCOUNT_RLIMIT_COUNTS];
};
extern struct user_namespace init_user_ns;
@@ -120,21 +124,26 @@ struct ucounts *alloc_ucounts(struct user_namespace *ns, kuid_t uid);
struct ucounts * __must_check get_ucounts(struct ucounts *ucounts);
void put_ucounts(struct ucounts *ucounts);
-static inline long get_ucounts_value(struct ucounts *ucounts, enum ucount_type type)
+static inline long get_rlimit_value(struct ucounts *ucounts, enum rlimit_type type)
{
- return atomic_long_read(&ucounts->ucount[type]);
+ return atomic_long_read(&ucounts->rlimit[type]);
}
-long inc_rlimit_ucounts(struct ucounts *ucounts, enum ucount_type type, long v);
-bool dec_rlimit_ucounts(struct ucounts *ucounts, enum ucount_type type, long v);
-long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum ucount_type type);
-void dec_rlimit_put_ucounts(struct ucounts *ucounts, enum ucount_type type);
-bool is_ucounts_overlimit(struct ucounts *ucounts, enum ucount_type type, unsigned long max);
+long inc_rlimit_ucounts(struct ucounts *ucounts, enum rlimit_type type, long v);
+bool dec_rlimit_ucounts(struct ucounts *ucounts, enum rlimit_type type, long v);
+long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum rlimit_type type);
+void dec_rlimit_put_ucounts(struct ucounts *ucounts, enum rlimit_type type);
+bool is_rlimit_overlimit(struct ucounts *ucounts, enum rlimit_type type, unsigned long max);
+
+static inline long get_userns_rlimit_max(struct user_namespace *ns, enum rlimit_type type)
+{
+ return READ_ONCE(ns->rlimit_max[type]);
+}
-static inline void set_rlimit_ucount_max(struct user_namespace *ns,
- enum ucount_type type, unsigned long max)
+static inline void set_userns_rlimit_max(struct user_namespace *ns,
+ enum rlimit_type type, unsigned long max)
{
- ns->ucount_max[type] = max <= LONG_MAX ? max : LONG_MAX;
+ ns->rlimit_max[type] = max <= LONG_MAX ? max : LONG_MAX;
}
#ifdef CONFIG_USER_NS
diff --git a/kernel/fork.c b/kernel/fork.c
index 25fb13362d38c..87c08297fbee6 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -850,13 +850,13 @@ void __init fork_init(void)
init_task.signal->rlim[RLIMIT_SIGPENDING] =
init_task.signal->rlim[RLIMIT_NPROC];
- for (i = 0; i < MAX_PER_NAMESPACE_UCOUNTS; i++)
+ for (i = 0; i < UCOUNT_COUNTS; i++)
init_user_ns.ucount_max[i] = max_threads/2;
- set_rlimit_ucount_max(&init_user_ns, UCOUNT_RLIMIT_NPROC, RLIM_INFINITY);
- set_rlimit_ucount_max(&init_user_ns, UCOUNT_RLIMIT_MSGQUEUE, RLIM_INFINITY);
- set_rlimit_ucount_max(&init_user_ns, UCOUNT_RLIMIT_SIGPENDING, RLIM_INFINITY);
- set_rlimit_ucount_max(&init_user_ns, UCOUNT_RLIMIT_MEMLOCK, RLIM_INFINITY);
+ set_userns_rlimit_max(&init_user_ns, UCOUNT_RLIMIT_NPROC, RLIM_INFINITY);
+ set_userns_rlimit_max(&init_user_ns, UCOUNT_RLIMIT_MSGQUEUE, RLIM_INFINITY);
+ set_userns_rlimit_max(&init_user_ns, UCOUNT_RLIMIT_SIGPENDING, RLIM_INFINITY);
+ set_userns_rlimit_max(&init_user_ns, UCOUNT_RLIMIT_MEMLOCK, RLIM_INFINITY);
#ifdef CONFIG_VMAP_STACK
cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, "fork:vm_stack_cache",
@@ -2176,7 +2176,7 @@ static __latent_entropy struct task_struct *copy_process(
goto bad_fork_free;
retval = -EAGAIN;
- if (is_ucounts_overlimit(task_ucounts(p), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) {
+ if (is_rlimit_overlimit(task_ucounts(p), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) {
if (p->real_cred->user != INIT_USER &&
!capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
goto bad_fork_cleanup_count;
diff --git a/kernel/sys.c b/kernel/sys.c
index 3aa55bdcb7e41..89d8c850c13a1 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -489,7 +489,7 @@ static void flag_nproc_exceeded(struct cred *new)
* for programs doing set*uid()+execve() by harmlessly deferring the
* failure to the execve() stage.
*/
- if (is_ucounts_overlimit(new->ucounts, UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC)) &&
+ if (is_rlimit_overlimit(new->ucounts, UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC)) &&
new->user != INIT_USER)
current->flags |= PF_NPROC_EXCEEDED;
else
diff --git a/kernel/ucount.c b/kernel/ucount.c
index a751fb6e94e51..2e2c9c2096781 100644
--- a/kernel/ucount.c
+++ b/kernel/ucount.c
@@ -87,10 +87,6 @@ static struct ctl_table user_table[] = {
UCOUNT_ENTRY("max_fanotify_groups"),
UCOUNT_ENTRY("max_fanotify_marks"),
#endif
- { },
- { },
- { },
- { },
{ }
};
#endif /* CONFIG_SYSCTL */
@@ -257,29 +253,29 @@ void dec_ucount(struct ucounts *ucounts, enum ucount_type type)
put_ucounts(ucounts);
}
-long inc_rlimit_ucounts(struct ucounts *ucounts, enum ucount_type type, long v)
+long inc_rlimit_ucounts(struct ucounts *ucounts, enum rlimit_type type, long v)
{
struct ucounts *iter;
long max = LONG_MAX;
long ret = 0;
for (iter = ucounts; iter; iter = iter->ns->ucounts) {
- long new = atomic_long_add_return(v, &iter->ucount[type]);
+ long new = atomic_long_add_return(v, &iter->rlimit[type]);
if (new < 0 || new > max)
ret = LONG_MAX;
else if (iter == ucounts)
ret = new;
- max = READ_ONCE(iter->ns->ucount_max[type]);
+ max = get_userns_rlimit_max(iter->ns, type);
}
return ret;
}
-bool dec_rlimit_ucounts(struct ucounts *ucounts, enum ucount_type type, long v)
+bool dec_rlimit_ucounts(struct ucounts *ucounts, enum rlimit_type type, long v)
{
struct ucounts *iter;
long new = -1; /* Silence compiler warning */
for (iter = ucounts; iter; iter = iter->ns->ucounts) {
- long dec = atomic_long_sub_return(v, &iter->ucount[type]);
+ long dec = atomic_long_sub_return(v, &iter->rlimit[type]);
WARN_ON_ONCE(dec < 0);
if (iter == ucounts)
new = dec;
@@ -288,11 +284,11 @@ bool dec_rlimit_ucounts(struct ucounts *ucounts, enum ucount_type type, long v)
}
static void do_dec_rlimit_put_ucounts(struct ucounts *ucounts,
- struct ucounts *last, enum ucount_type type)
+ struct ucounts *last, enum rlimit_type type)
{
struct ucounts *iter, *next;
for (iter = ucounts; iter != last; iter = next) {
- long dec = atomic_long_sub_return(1, &iter->ucount[type]);
+ long dec = atomic_long_sub_return(1, &iter->rlimit[type]);
WARN_ON_ONCE(dec < 0);
next = iter->ns->ucounts;
if (dec == 0)
@@ -300,12 +296,12 @@ static void do_dec_rlimit_put_ucounts(struct ucounts *ucounts,
}
}
-void dec_rlimit_put_ucounts(struct ucounts *ucounts, enum ucount_type type)
+void dec_rlimit_put_ucounts(struct ucounts *ucounts, enum rlimit_type type)
{
do_dec_rlimit_put_ucounts(ucounts, NULL, type);
}
-long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum ucount_type type)
+long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum rlimit_type type)
{
/* Caller must hold a reference to ucounts */
struct ucounts *iter;
@@ -313,12 +309,12 @@ long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum ucount_type type)
long dec, ret = 0;
for (iter = ucounts; iter; iter = iter->ns->ucounts) {
- long new = atomic_long_add_return(1, &iter->ucount[type]);
+ long new = atomic_long_add_return(1, &iter->rlimit[type]);
if (new < 0 || new > max)
goto dec_unwind;
if (iter == ucounts)
ret = new;
- max = READ_ONCE(iter->ns->ucount_max[type]);
+ max = get_userns_rlimit_max(iter->ns, type);
/*
* Grab an extra ucount reference for the caller when
* the rlimit count was previously 0.
@@ -330,23 +326,23 @@ long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum ucount_type type)
}
return ret;
dec_unwind:
- dec = atomic_long_sub_return(1, &iter->ucount[type]);
+ dec = atomic_long_sub_return(1, &iter->rlimit[type]);
WARN_ON_ONCE(dec < 0);
do_dec_rlimit_put_ucounts(ucounts, iter, type);
return 0;
}
-bool is_ucounts_overlimit(struct ucounts *ucounts, enum ucount_type type, unsigned long rlimit)
+bool is_rlimit_overlimit(struct ucounts *ucounts, enum rlimit_type type, unsigned long rlimit)
{
struct ucounts *iter;
long max = rlimit;
if (rlimit > LONG_MAX)
max = LONG_MAX;
for (iter = ucounts; iter; iter = iter->ns->ucounts) {
- long val = get_ucounts_value(iter, type);
+ long val = get_rlimit_value(iter, type);
if (val < 0 || val > max)
return true;
- max = READ_ONCE(iter->ns->ucount_max[type]);
+ max = get_userns_rlimit_max(iter->ns, type);
}
return false;
}
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 5481ba44a8d68..981bb2d10d836 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -131,13 +131,13 @@ int create_user_ns(struct cred *new)
ns->owner = owner;
ns->group = group;
INIT_WORK(&ns->work, free_user_ns);
- for (i = 0; i < MAX_PER_NAMESPACE_UCOUNTS; i++) {
+ for (i = 0; i < UCOUNT_COUNTS; i++) {
ns->ucount_max[i] = INT_MAX;
}
- set_rlimit_ucount_max(ns, UCOUNT_RLIMIT_NPROC, enforced_nproc_rlimit());
- set_rlimit_ucount_max(ns, UCOUNT_RLIMIT_MSGQUEUE, rlimit(RLIMIT_MSGQUEUE));
- set_rlimit_ucount_max(ns, UCOUNT_RLIMIT_SIGPENDING, rlimit(RLIMIT_SIGPENDING));
- set_rlimit_ucount_max(ns, UCOUNT_RLIMIT_MEMLOCK, rlimit(RLIMIT_MEMLOCK));
+ set_userns_rlimit_max(ns, UCOUNT_RLIMIT_NPROC, enforced_nproc_rlimit());
+ set_userns_rlimit_max(ns, UCOUNT_RLIMIT_MSGQUEUE, rlimit(RLIMIT_MSGQUEUE));
+ set_userns_rlimit_max(ns, UCOUNT_RLIMIT_SIGPENDING, rlimit(RLIMIT_SIGPENDING));
+ set_userns_rlimit_max(ns, UCOUNT_RLIMIT_MEMLOCK, rlimit(RLIMIT_MEMLOCK));
ns->ucounts = ucounts;
/* Inherit USERNS_SETGROUPS_ALLOWED from our parent */
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 5.15.y 3/4] signal: restore the override_rlimit logic
2026-11-10 22:41 ` [PATCH 5.15.y 1/4] ucounts: Use atomic_long_sub_return for clarity Sasha Levin
2026-11-10 22:41 ` [PATCH 5.15.y 2/4] ucounts: Split rlimit and ucount values and max values Sasha Levin
@ 2026-11-10 22:41 ` Sasha Levin
2026-11-10 22:41 ` [PATCH 5.15.y 4/4] ucount: take the rlimit reference before checking the limit Sasha Levin
2 siblings, 0 replies; 6+ messages in thread
From: Sasha Levin @ 2026-11-10 22:41 UTC (permalink / raw)
To: stable
Cc: Roman Gushchin, Andrei Vagin, Oleg Nesterov, Alexey Gladkov,
Kees Cook, Eric W. Biederman, Andrew Morton, Sasha Levin
From: Roman Gushchin <roman.gushchin@linux.dev>
[ Upstream commit 9e05e5c7ee8758141d2db7e8fea2cab34500c6ed ]
Prior to commit d64696905554 ("Reimplement RLIMIT_SIGPENDING on top of
ucounts") UCOUNT_RLIMIT_SIGPENDING rlimit was not enforced for a class of
signals. However now it's enforced unconditionally, even if
override_rlimit is set. This behavior change caused production issues.
For example, if the limit is reached and a process receives a SIGSEGV
signal, sigqueue_alloc fails to allocate the necessary resources for the
signal delivery, preventing the signal from being delivered with siginfo.
This prevents the process from correctly identifying the fault address and
handling the error. From the user-space perspective, applications are
unaware that the limit has been reached and that the siginfo is
effectively 'corrupted'. This can lead to unpredictable behavior and
crashes, as we observed with java applications.
Fix this by passing override_rlimit into inc_rlimit_get_ucounts() and skip
the comparison to max there if override_rlimit is set. This effectively
restores the old behavior.
Link: https://lkml.kernel.org/r/20241104195419.3962584-1-roman.gushchin@linux.dev
Fixes: d64696905554 ("Reimplement RLIMIT_SIGPENDING on top of ucounts")
Signed-off-by: Roman Gushchin <roman.gushchin@linux.dev>
Co-developed-by: Andrei Vagin <avagin@google.com>
Signed-off-by: Andrei Vagin <avagin@google.com>
Acked-by: Oleg Nesterov <oleg@redhat.com>
Acked-by: Alexey Gladkov <legion@kernel.org>
Cc: Kees Cook <kees@kernel.org>
Cc: "Eric W. Biederman" <ebiederm@xmission.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
override_rlimit is set. This behavior change caused production issues.
For example, if the limit is reached and a process receives a SIGSEGV
signal, sigqueue_alloc fails to allocate the necessary resources for the
signal delivery, preventing the signal from being delivered with siginfo.
Stable-dep-of: b14fcabd34e0 ("ucount: take the rlimit reference before checking the limit")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
include/linux/user_namespace.h | 3 ++-
kernel/signal.c | 3 ++-
kernel/ucount.c | 6 ++++--
3 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
index 45f09bec02c48..733f2a97589b2 100644
--- a/include/linux/user_namespace.h
+++ b/include/linux/user_namespace.h
@@ -131,7 +131,8 @@ static inline long get_rlimit_value(struct ucounts *ucounts, enum rlimit_type ty
long inc_rlimit_ucounts(struct ucounts *ucounts, enum rlimit_type type, long v);
bool dec_rlimit_ucounts(struct ucounts *ucounts, enum rlimit_type type, long v);
-long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum rlimit_type type);
+long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum rlimit_type type,
+ bool override_rlimit);
void dec_rlimit_put_ucounts(struct ucounts *ucounts, enum rlimit_type type);
bool is_rlimit_overlimit(struct ucounts *ucounts, enum rlimit_type type, unsigned long max);
diff --git a/kernel/signal.c b/kernel/signal.c
index c4091812ea89b..5d110fd089d10 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -426,7 +426,8 @@ __sigqueue_alloc(int sig, struct task_struct *t, gfp_t gfp_flags,
*/
rcu_read_lock();
ucounts = task_ucounts(t);
- sigpending = inc_rlimit_get_ucounts(ucounts, UCOUNT_RLIMIT_SIGPENDING);
+ sigpending = inc_rlimit_get_ucounts(ucounts, UCOUNT_RLIMIT_SIGPENDING,
+ override_rlimit);
rcu_read_unlock();
if (!sigpending)
return NULL;
diff --git a/kernel/ucount.c b/kernel/ucount.c
index 2e2c9c2096781..1459c1c579dc0 100644
--- a/kernel/ucount.c
+++ b/kernel/ucount.c
@@ -301,7 +301,8 @@ void dec_rlimit_put_ucounts(struct ucounts *ucounts, enum rlimit_type type)
do_dec_rlimit_put_ucounts(ucounts, NULL, type);
}
-long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum rlimit_type type)
+long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum rlimit_type type,
+ bool override_rlimit)
{
/* Caller must hold a reference to ucounts */
struct ucounts *iter;
@@ -314,7 +315,8 @@ long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum rlimit_type type)
goto dec_unwind;
if (iter == ucounts)
ret = new;
- max = get_userns_rlimit_max(iter->ns, type);
+ if (!override_rlimit)
+ max = get_userns_rlimit_max(iter->ns, type);
/*
* Grab an extra ucount reference for the caller when
* the rlimit count was previously 0.
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 5.15.y 4/4] ucount: take the rlimit reference before checking the limit
2026-11-10 22:41 ` [PATCH 5.15.y 1/4] ucounts: Use atomic_long_sub_return for clarity Sasha Levin
2026-11-10 22:41 ` [PATCH 5.15.y 2/4] ucounts: Split rlimit and ucount values and max values Sasha Levin
2026-11-10 22:41 ` [PATCH 5.15.y 3/4] signal: restore the override_rlimit logic Sasha Levin
@ 2026-11-10 22:41 ` Sasha Levin
2 siblings, 0 replies; 6+ messages in thread
From: Sasha Levin @ 2026-11-10 22:41 UTC (permalink / raw)
To: stable
Cc: Nickolai Zeldovich, Alexey Gladkov, Christian Brauner (Amutable),
Sasha Levin
From: Nickolai Zeldovich <nickolai@csail.mit.edu>
[ Upstream commit b14fcabd34e02c40f72d3cd1e9bd4fd18df40ff1 ]
inc_rlimit_get_ucounts() takes the reference that pins a ucounts level
while its rlimit count is non-zero only in the walk whose increment is
the 0 -> 1 transition, and only after that walk passed the limit check.
A walk that raises the count from zero and then fails the limit backs
out without the reference, while a concurrent walk that raised it past
one keeps its increment and never takes one either:
CPU0 (obeys the limit) CPU1 (overrides it)
new = add_return(1) -> 1
new = add_return(1) -> 2
new != 1, no get_ucounts()
new > max -> dec_unwind
sub_return(1) -> 1 (no put)
The count is 1 with no reference held. When CPU1 drops its charge,
dec_rlimit_put_ucounts() takes the count to 0 and puts a reference
nobody took, which frees the live struct ucounts, and the user_namespace
it pins, with kfree_rcu(). The mirror interleaving leaks the reference
instead.
An unprivileged user reaches this on a kernel with CONFIG_USER_NS=y: in
a user namespace with RLIMIT_SIGPENDING 0, one process taking
synchronous SIGSEGVs (forced, so override_rlimit is set) races a second
process in the same namespace and uid that queues real-time signals,
which obey the limit. The two hold different sighand->siglock but charge
the same per-namespace SIGPENDING ucounts. Unpatched, with rcuref
debugging:
rcuref - imbalanced put()
WARNING: lib/rcuref.c:266 at rcuref_put_slowpath+0xf4/0x120
put_ucounts+0x5a/0x180
do_dec_rlimit_put_ucounts+0x70/0x90
__sigqueue_free+0x33/0xc0
get_signal+0x56f/0xd20
arch_do_signal_or_restart+0x87/0x380
irqentry_exit+0x243/0x650
asm_exc_page_fault+0x26/0x30
Take the reference on the 0 -> 1 transition before the limit is checked,
so the walk that raises the count from zero owns that level's reference
even when its request is rejected, and unwind a rejected level through
do_dec_rlimit_put_ucounts(), which releases the reference when the count
returns to 0. The get_ucounts() failure path is unchanged. With the
patch the same interleaving, forced some 80000 times in a KASAN build,
produces no warning and no KASAN report.
Fixes: 15bc01effefe ("ucounts: Fix signal ucount refcounting")
Cc: stable@vger.kernel.org
Signed-off-by: Nickolai Zeldovich <nickolai@csail.mit.edu>
Link: https://patch.msgid.link/20261019185913.805378-1-nickolai@csail.mit.edu
Acked-by: Alexey Gladkov <legion@kernel.org>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
kernel/ucount.c | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/kernel/ucount.c b/kernel/ucount.c
index 1459c1c579dc0..f483f83fc750e 100644
--- a/kernel/ucount.c
+++ b/kernel/ucount.c
@@ -311,23 +311,26 @@ long inc_rlimit_get_ucounts(struct ucounts *ucounts, enum rlimit_type type,
for (iter = ucounts; iter; iter = iter->ns->ucounts) {
long new = atomic_long_add_return(1, &iter->rlimit[type]);
- if (new < 0 || new > max)
+ /*
+ * Take the extra reference on the 0 -> 1 transition before the
+ * limit check: a rejected walk must not leave a concurrent one
+ * holding the surviving increment with no reference.
+ */
+ if (new == 1 && !get_ucounts(iter))
goto dec_unwind;
+ if (new < 0 || new > max) {
+ /* undo iter too, releasing the reference taken above */
+ do_dec_rlimit_put_ucounts(ucounts, iter->ns->ucounts, type);
+ return 0;
+ }
if (iter == ucounts)
ret = new;
if (!override_rlimit)
max = get_userns_rlimit_max(iter->ns, type);
- /*
- * Grab an extra ucount reference for the caller when
- * the rlimit count was previously 0.
- */
- if (new != 1)
- continue;
- if (!get_ucounts(iter))
- goto dec_unwind;
}
return ret;
dec_unwind:
+ /* get_ucounts() failed for iter: undo iter without a put */
dec = atomic_long_sub_return(1, &iter->rlimit[type]);
WARN_ON_ONCE(dec < 0);
do_dec_rlimit_put_ucounts(ucounts, iter, type);
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-11-10 22:41 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-07 11:10 FAILED: patch "[PATCH] ucount: take the rlimit reference before checking the limit" failed to apply to 5.15-stable tree Sasha Levin
2026-11-07 13:56 ` Alexey Gladkov
2026-11-10 22:41 ` [PATCH 5.15.y 1/4] ucounts: Use atomic_long_sub_return for clarity Sasha Levin
2026-11-10 22:41 ` [PATCH 5.15.y 2/4] ucounts: Split rlimit and ucount values and max values Sasha Levin
2026-11-10 22:41 ` [PATCH 5.15.y 3/4] signal: restore the override_rlimit logic Sasha Levin
2026-11-10 22:41 ` [PATCH 5.15.y 4/4] ucount: take the rlimit reference before checking the limit Sasha Levin
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.