From nobody Fri Sep 25 06:47:20 2026 Received: from mail-4322.protonmail.ch (mail-4322.protonmail.ch [185.70.43.22]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1C27F4398F2; Tue, 15 Sep 2026 17:53:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.70.43.22 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789494835; cv=none; b=s10DHrWhP+b/lyw8qlG+DZGUvfjMIo1yOgAo537Zzgq2y37Q0YCt1w3NoQUp8p3i4D8KdqM5LDsFBHpAk6Vk+kGdL8JZdt1DHwTeHM8NNXuI3iF/wZ7VEWOCoIySusqrtkSax1doI96E8AQBJJPsc68p1HQw+ykN2k0OF3vdBGg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789494835; c=relaxed/simple; bh=w/JotG/w8Y7N4t3Kkq1rsKQJNYp3wTyHKEJsL7bhERo=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=VmWLYgh5GUm+LRKg8ST8eLazHjUM7yqrytMloUby0pCPb8r+56MzGtTLNT+Wf2E3a1VeMUvDDwmPljLucffuUbDVUMXui8TtjZOBDWz+85N3EhyUK7vBHff8/9DXOrGPDppf5l6cKiA8AUT2KnD5I6rrUESsNou2/hPFxAMeR/U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=proton.me; spf=pass smtp.mailfrom=proton.me; dkim=pass (2048-bit key) header.d=proton.me header.i=@proton.me header.b=Bw8gxOC9; arc=none smtp.client-ip=185.70.43.22 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=proton.me Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=proton.me Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=proton.me header.i=@proton.me header.b="Bw8gxOC9" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=proton.me; s=protonmail; t=1789494829; x=1789754029; bh=ZaEH1QYOsw9tgItJ4v4fWLtqFRZdql3u3G/MJ4VVsfc=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=Bw8gxOC9VoIDXEO9sobxCg89tfXtrqfZ9Ej6dS9TqiMr5aqlup3eTPj9thtQuLARE VlU4/btc6i536ldlghJITLoGyO+D+l6IDsg8/w+h8TSmvxHQdUAFIh/tOh3ZziuK/v V2L4CFbFy7l1cB9OYvto8FHk3kc76k9P8KS0jmJmaYfa+ye7GEbzBtm0LrnwoL/CBy 5WRObouaxnYIWSTmhOY+/yrjFXfrdlGNdBMJAvNl4hrQPiQk47w1DTIavuxNosuqTD 5PxNSPpZUDBGgsMNJ0lO/4CaYB7O0zGQ94vxn1XOb7AmsmtbIb0G8DF2KwkDqncJpi ngT4p0IVV7brQ== Date: Tue, 15 Sep 2026 17:53:47 +0000 To: pbonzini@redhat.com, seanjc@google.com From: David Ballesteros Cc: kvm@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 1/3] KVM: Release memory-attribute reservations abandoned on ENOMEM Message-ID: <20260915175335.138547-2-davimaba.v@proton.me> In-Reply-To: <20260915175335.138547-1-davimaba.v@proton.me> References: <20260915175335.138547-1-davimaba.v@proton.me> Feedback-ID: 219068424:user:proton X-Pm-Message-ID: 7ccba3ddc29020674a2e39f701e2d1a392c39b7c Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" kvm_vm_set_mem_attributes() reserves an xarray entry for every GFN in the range before storing anything, so that the store phase cannot fail partway through. When a reservation fails, the loop jumps to out_unlock and the reservations already made are abandoned: nothing in the call releases them. A later request that clears a range covering them does erase them, as the clear stores NULL over the reservation, but nothing obliges userspace to issue one and a caller exploiting this will not; absent such a call the entries live until kvm_destroy_vm(). An unprivileged user with /dev/kvm on a VM with private-memory support can therefore leak kernel memory across calls (a 576-byte xa_node per 64 GFNs) for the life of the VM fd. The abandoned entries are not inert. A bare reservation is an XA_ZERO_ENTRY. kvm_range_has_memory_attributes() is inconsistent about it: the end =3D=3D start + 1 path and the general loop treat it as absent (matc= hing kvm_get_memory_attributes(), which maps it to NULL via xa_load()), but the !attrs fast path calls xas_find() directly, which returns the zero entry as present. Via hugepage_has_attrs(), that makes kvm_arch_post_set_memory_attributes() mark a straddling head/tail hugepage "mixed" for a range whose attributes are in fact uniform, so KVM stops using a hugepage there until a later request re-covers it. Release the reservations this call made on the failure path. xa_release() erases an entry only while it is still a reservation, so value entries that predate this call are left untouched; it takes no gfp and cannot fail. Only [start, i) is walked, i being the index whose reservation failed (no entry was created at or beyond it). Runtime-verified on v6.18.48 (isolated sw-protected VM, no KASAN, no fault injection; the reservations are left behind by real memcg pressure via clone(CLONE_VM), not by fault injection). Unpatched, a failed request retains on the order of 450000 xa_nodes (~250 MiB), and with one of them inside a 2 MiB region a clear of the region's head page leaves pages_2m unchanged (the reservation is invisible to xa_load), a clear of two pages drops pages_2m by one and raises pages_4k by 512 (the hugepage is degraded), and a clear covering the whole 2 MiB restores it. With this patch the same run retains under 2000 nodes -- three orders of magnitude less, at the level of run-to-run noise -- and pages_2m stays at 16 across all three clears: the reservations are released, so the hugepage is never degraded. Both arms used the same kernel config and the same test binary. The bug reproduces with XA_FLAGS_ACCOUNT applied (patch 3/3), i.e. accounting alone does not fix it. Found by an AI-assisted security audit. Fixes: 5a475554db1e ("KVM: Introduce per-page memory attributes") Cc: stable@vger.kernel.org Assisted-by: Claude-Code:claude-opus-5 Signed-off-by: David Ballesteros --- virt/kvm/kvm_main.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -2575,7 +2575,7 @@ static int kvm_vm_set_mem_attributes(struct kvm *kvm,= gfn_t start, gfn_t end, for (i =3D start; i < end; i++) { r =3D xa_reserve(&kvm->mem_attr_array, i, GFP_KERNEL_ACCOUNT); if (r) - goto out_unlock; + goto out_release; =20 cond_resched(); } @@ -2594,6 +2594,28 @@ static int kvm_vm_set_mem_attributes(struct kvm *kvm= , gfn_t start, gfn_t end, out_unlock: mutex_unlock(&kvm->slots_lock); =20 + return r; + +out_release: + /* + * The reservation loop failed at @i; the entries in [start, i) were + * reserved by this call and, without releasing them here, would be + * retained until userspace happens to clear a range covering them, or + * until the VM is destroyed. The retained entries are not inert: + * a bare reservation is an XA_ZERO_ENTRY, which the !attrs fast path of + * kvm_range_has_memory_attributes() counts as present (it calls + * xas_find() directly) even though kvm_get_memory_attributes() reports + * it as absent, so a straddling hugepage over such an entry gets marked + * mixed and KVM stops using a hugepage for a range whose attributes are + * uniform. xa_release() erases an entry only while it is still a + * reservation, so value entries that predate this call are untouched. + */ + while (i-- > start) { + xa_release(&kvm->mem_attr_array, i); + cond_resched(); + } + mutex_unlock(&kvm->slots_lock); + return r; } static int kvm_vm_ioctl_set_mem_attributes(struct kvm *kvm, From nobody Fri Sep 25 06:47:20 2026 Received: from mail-106120.protonmail.ch (mail-106120.protonmail.ch [79.135.106.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2D414439327 for ; Tue, 15 Sep 2026 17:53:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=79.135.106.120 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789494844; cv=none; b=F3mU4zsIAmIGm7XOBccaanIMnRwnjEzi+ix3VdT9LUQoeNIomA/K5St0fyMlIE3oX3sWF1wFBK7WCA+eH1mEmEGYUPoX/RbvoZBuIQbjy9PHUlvdIRzT9NLf0z90bcu1Laq5v5vdxDg/Nx4dER7nNQlJtQKBu+n2BEr5MK7/JHo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789494844; c=relaxed/simple; bh=W1hyEI6kOMOo8EHgS2PCjxcbqIuADogaI33wCiuwb9s=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=NPzD2lodIVh2ugh/hRrNuOwd1pqS5g8wMUB1PXuEoGnTUPc3sPEU+EI02+61xH42nb27HcXsPCL445+Q2WensDkkObQtJbhcm5p4Pq9yuPY4UglJIRtIeTn4GrdmH+A916fHtQBWJI8ZYD4ZrQOHN58OjCGvfwt4v2MemC0eQ0I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=proton.me; spf=pass smtp.mailfrom=proton.me; dkim=pass (2048-bit key) header.d=proton.me header.i=@proton.me header.b=dtweb133; arc=none smtp.client-ip=79.135.106.120 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=proton.me Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=proton.me Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=proton.me header.i=@proton.me header.b="dtweb133" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=proton.me; s=ev7gdfhd5fdgbih4uqu2gprziq.protonmail; t=1789494836; x=1789754036; bh=vyAXEBM6MVKBMtMkLJ4x+mOU4Va7jwOb58XaLGnaMPI=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=dtweb133ChTn59DTZCM3+j85MhJudihtH+4x6riZl1Wndpf3OY/a5D48OyCWQRl32 5d3+J8XxPXWwKWvv19DG1eWI8/Hmutsj7/yp6FmyqsAp4hePJCJLN2z5hWoQrDLSGE n11UNbx14d/RY0tofBSBqfR5g2WBgZINP3OnZaXx6JFko3uxsWXZLI2sdV2ojrISJG JjoQj6weieqI4+p3Ad+rrox0Yf9zdIfiNGBTYUqfeemwXqIGvCtx585Ah53HTG1KrX etm5nC2MmFv7wLKTpBgbkI09JorU4Ang00gZxc/qq4i4vcTDB6PiCsNCDt/Ch/q/V3 oniOdPwE4BgYg== Date: Tue, 15 Sep 2026 17:53:52 +0000 To: pbonzini@redhat.com, seanjc@google.com From: David Ballesteros Cc: kvm@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 2/3] KVM: Make kvm_range_has_memory_attributes() consistent about reservations Message-ID: <20260915175335.138547-3-davimaba.v@proton.me> In-Reply-To: <20260915175335.138547-1-davimaba.v@proton.me> References: <20260915175335.138547-1-davimaba.v@proton.me> Feedback-ID: 219068424:user:proton X-Pm-Message-ID: b8f2b0ac85f47a753093554cc655603fcd4f6f35 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Make the !attrs fast path of kvm_range_has_memory_attributes() skip bare reservations, so that all three of the function's query paths agree on what an XA_ZERO_ENTRY means. A reservation carries no attributes, and two of the three paths already treat it as absent: the end =3D=3D start + 1 path reads it through kvm_get_memory_attributes(), which maps it to NULL via xa_load(), and the general loop skips it via xas_retry(). Only the !attrs fast path calls xas_find() directly, which returns the reservation as a present entry, so it reports a range that is in fact all-shared as not-all-shared. Through hugepage_has_attrs(), that marks a straddling hugepage mixed for a range whose attributes are uniform. This is a consistency fix rather than a fix for a reachable bug, and is not tagged for stable. Every caller of kvm_range_has_memory_attributes() holds kvm->slots_lock -- the idempotency check in kvm_vm_set_mem_attributes(), hugepage_has_attrs() from both kvm_arch_post_set_memory_attributes() and kvm_mmu_init_memslot_memory_attributes(), and __kvm_gmem_populate() -- and slots_lock excludes the only writer, so with patch 1/3 applied no caller can observe a reservation. The function should not have to depend on that to answer consistently. Note this patch depends on 1/3 and must not be applied without it. Today a clear over a range that holds only reservations does not take the idempotency early-out in kvm_vm_set_mem_attributes(), because the fast path reports the range as not-all-clear; the clear therefore proceeds and its xa_store(NULL) loop erases the reservations as a side effect. Teaching the fast path to skip reservations makes that early-out fire and removes the accidental cleanup, so the patch that stops the reservations from being abandoned in the first place has to come first. Found by an AI-assisted security audit. Fixes: 5a475554db1e ("KVM: Introduce per-page memory attributes") Assisted-by: Claude-Code:claude-opus-5 Signed-off-by: David Ballesteros --- virt/kvm/kvm_main.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 45e7844..4e5e497 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -2446,8 +2446,17 @@ bool kvm_range_has_memory_attributes(struct kvm *kvm= , gfn_t start, gfn_t end, return (kvm_get_memory_attributes(kvm, start) & mask) =3D=3D attrs; guard(rcu)(); - if (!attrs) - return !xas_find(&xas, end - 1); + if (!attrs) { + /* + * Skip reservations: a bare XA_ZERO_ENTRY carries no + * attributes, but xas_find() returns it raw. + */ + do { + entry =3D xas_find(&xas, end - 1); + } while (xas_retry(&xas, entry)); + + return !entry; + } for (index =3D start; index < end; index++) { do { From nobody Fri Sep 25 06:47:20 2026 Received: from mail-10628.protonmail.ch (mail-10628.protonmail.ch [79.135.106.28]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 82FE8418A28 for ; Tue, 15 Sep 2026 17:54:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=79.135.106.28 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789494849; cv=none; b=sv8fOAbHDAEXgVSN+P1FeFTEm0tUZRVvc4t0ueLJ7zf452AmO6hqx5a9twu3Z1egqO8e7/Zy0eCH/0fqG3ywjgN98ZSX4R4z4n2hAeiiT+iHgTDLendDVgoqjCCFYhKy6ShAcKXCd4Vei5tyQFE2JOCX+lQdplZlaq/HUKjgXwg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789494849; c=relaxed/simple; bh=l5wJLZ+qYp4YnALEuMvRCtAcsZGAclRAMwQTu1jrhWk=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=nLba/TKSQCcMIQidc7cVxLZismPm4rYdhFX+uo+W5X9324BIrv3D0g7SfYLEjaB0jBmHpeHVxkJgvntq4kd5UueAmKsOiIEr0HjixdAEjH80pp6Dz9fIh1TagzNxVzxOQt64m9Jb4molSy6PPqfVa07i6Lfxaarrq9UyHK1KIy8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=proton.me; spf=pass smtp.mailfrom=proton.me; dkim=pass (2048-bit key) header.d=proton.me header.i=@proton.me header.b=Mn4EBae+; arc=none smtp.client-ip=79.135.106.28 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=proton.me Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=proton.me Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=proton.me header.i=@proton.me header.b="Mn4EBae+" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=proton.me; s=bbvwys7yf5dzdi3hk7qoqfoho4.protonmail; t=1789494843; x=1789754043; bh=+a8hOjyqA/nNyf5/vNUyHc7QBQwDmTAe84U8l3tR9CU=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=Mn4EBae+3ZtqVcQjmpVCN5jJaCYM2sFRzEfB0RGS25dyT9Ez9/jtFFrqRfH08e1gU kpM1vzy/LQx63NIN5gDUmZ06qGPYLhkFgiJAVNKTQXPreU2HdOrQ1oCxJEi5nd8Niu EOstnJHdkrKOT6bt7brcYhn/ZzEVTHfr9P3V41b7EtZMFS8KmxVEmDfBjj9KbvgoIl lLIhl/6+MR9lLyvegnbZARDa4DR52/m+mh6RkEW+fr4sz93JeBtyGmL6ndISx+dY+K uz8+gUqn04OJti5FVwkPqlSdZOec2eItv88wruZYvgvav2q9JVhHOfPVcpC8IaI7Ab ee4z5ZnpgMLvA== Date: Tue, 15 Sep 2026 17:53:57 +0000 To: pbonzini@redhat.com, seanjc@google.com From: David Ballesteros Cc: kvm@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 3/3] KVM: Account mem_attr_array nodes to the caller's memcg Message-ID: <20260915175335.138547-4-davimaba.v@proton.me> In-Reply-To: <20260915175335.138547-1-davimaba.v@proton.me> References: <20260915175335.138547-1-davimaba.v@proton.me> Feedback-ID: 219068424:user:proton X-Pm-Message-ID: 7011f62075307131ca7d0a6488dabe2651063920 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" kvm_vm_set_mem_attributes() passes GFP_KERNEL_ACCOUNT when reserving xarray entries, but the nodes are allocated by xas_alloc(), which hardcodes GFP_NOWAIT and only adds __GFP_ACCOUNT when the xarray carries XA_FLAGS_ACCOUNT. mem_attr_array is initialized with plain xa_init(), so the flag is never set and nodes taken from that fast path -- the overwhelming majority -- are not charged to the caller; only the rare __xas_nomem() slow path is, because it receives the caller's gfp. Measured on v6.18.48: a process in a cgroup limited to 256 MiB grew radix_tree_node slab by ~512 MiB while its memory.current stayed near 0. Per-tenant memcg limits therefore do not contain the growth. Set XA_FLAGS_ACCOUNT so the intended accounting takes effect. Note this is a change in reachability, not in contract. KVM_SET_MEMORY_ATTRIBUTES could already return -ENOMEM, but only under global memory pressure, since the fast path allocated with plain GFP_NOWAIT. With the nodes accounted, a tenant under a memory.max limit can now hit it from a cgroup-local condition, i.e. conversions that previously succeeded may fail. That is intended and matches every other GFP_KERNEL_ACCOUNT allocation in KVM; the alternative is letting the tenant grow host memory that is never charged to it. Userspace driving conversions from guest KVM_HC_MAP_GPA_RANGE hypercalls surfaces the failure on that path. Runtime-verified on v6.18.48 (isolated VM, no KASAN): without the flag a process in a 256 MiB cgroup materializes 512 MiB of radix_tree_node slab with memory.current flat (the memcg is inert); with the flag the same process is contained by the cgroup -- the memcg OOM killer selects the attacker inside its own slice (CONSTRAINT_MEMCG) instead of exhausting global memory. Found by an AI-assisted security audit. Not tagged for stable: unlike 1/3 and 2/3, which are pure corrections, this one changes observable behaviour, and new -ENOMEM returns for cgroup-limited tenants are a poor fit for stable's regression-risk bar. Happy to send it to stable separately if maintainers disagree. Fixes: 5a475554db1e ("KVM: Introduce per-page memory attributes") Assisted-by: Claude-Code:claude-opus-5 Signed-off-by: David Ballesteros --- virt/kvm/kvm_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -1116,7 +1116,7 @@ static struct kvm *kvm_create_vm(unsigned long type, = const char *fdname) rcuwait_init(&kvm->mn_memslots_update_rcuwait); xa_init(&kvm->vcpu_array); #ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES - xa_init(&kvm->mem_attr_array); + xa_init_flags(&kvm->mem_attr_array, XA_FLAGS_ACCOUNT); #endif INIT_LIST_HEAD(&kvm->gpc_list);