From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=100.103.45.18 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1794063587; b=JUd9bkWrJ+FpW/3E/bwKI7rQc/42yxc3oO7gB27+nZ7DV2gG8LnZrhm5mW4g0SMu9WoG qRIzk9ivPaApCPSJ2Q/Z6g7w6NkJQi2QkrazqBdj5EWpbayQSlk9Lr6waelJ5xGjWJuvW loYoiymufVX5bLXvDjzxULJ+wUlsYhjAhi/M79buyOeg4XvUZ+gl+e0cjekWLAYiMetXP JOuU2fLrd3a2BidsyaEUv7lIE+8UI1Cpa5XGKukFHSG3LB35lJd/FJRl4bqs6YRzVllo8 LGusxw/NzWr6QluYryk5IQo6SxnVsq+/z4LUMOLilfwxphBB5UXr6HwRUk4YQH688cg== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1794063587; h=Received:DKIM-Signature:From:To:Cc:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:User-Agent:X-stable:X-Patchwork-Hint: MIME-Version:Content-Transfer-Encoding; bh=fqGW0xC5W03wMTZOMl8Ap56+sdUyx6oK3nqaD0x93aI=; b=4y1q0yTDMRpgABOaUOqnnDjWGAh+JWu5cZpfMOyTkBVnkSS802YrxAXD8JqPrE572XyT 4Y4a4EhzXPLsyGbR59BhFvw7HysbIpPa7xhuV8n43LwH5ptP6tBZdFukRYYVUeIAppx3P q7I9OkHgkl8leKW9yd9C/QTW1mCJu1MxxGy+XxJ5QxqTcLBTbJ+0fdMuzXmB8JZ6oY6FT vwBpQekgdKhPLpnsdqUNapQ0e63pcSltrzLGIkzEvNY7gHR+5E2oRLj2XvXxosKg4PRVT w00QdDx5pBG+z3WBDEcecDxulToUtvGGNVfJuPY2/CzCULQeH4bPaSbivsOOnJrJ1bQ== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass header.d=linuxfoundation.org header.i=@linuxfoundation.org header.a=rsa-sha256 header.s=korg header.b=G2lZgeFS; arc=none smtp.remote-ip=100.103.45.18 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B663660A263F; Sat, 7 Nov 2026 14:59:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.a=rsa-sha256 header.s=korg header.b=G2lZgeFS Received: by smtp.kernel.org (Postfix) with ESMTPSA id 630CD1F008A3; Sat, 7 Nov 2026 14:59:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linuxfoundation.org; s=korg; t=1794063586; bh=fqGW0xC5W03wMTZOMl8Ap56+sdUyx6oK3nqaD0x93aI=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=G2lZgeFS740KADDmqHCz8d3GLM/6TOKy/eLeFxXG7yIKyuBuY4LA9CoMUlxjF+Xr8 HiTkI8jeqgcBNj+Q7hXJ2GB6Lht1jiwuvm72SkNX+nJTPyZMehZAuVbvh5Lvh0ru0H rsoTr88Nf8KUz1zfKW/IevDgcEzOZvO2gF/CLK/I= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, Nickolai Zeldovich , Alexey Gladkov , "Christian Brauner (Amutable)" , Sasha Levin Subject: [PATCH 6.18 713/869] ucount: take the rlimit reference before checking the limit Date: Sat, 7 Nov 2026 08:56:35 -0500 Message-ID: <20261107134917.049053012@linuxfoundation.org> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261107134852.069646174@linuxfoundation.org> References: <20261107134852.069646174@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore X-Mailing-List: stable@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 6.18-stable review patch. If anyone has any objections, please let me know. ------------------ From: Nickolai Zeldovich [ 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 Link: https://patch.msgid.link/20261019185913.805378-1-nickolai@csail.mit.edu Acked-by: Alexey Gladkov Signed-off-by: Christian Brauner (Amutable) Signed-off-by: Sasha Levin --- kernel/ucount.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/kernel/ucount.c b/kernel/ucount.c index fc4a8f2d30965..5cbf1553658ea 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -302,23 +302,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