[PATCH v4 3/3] KVM: Account mem_attr_array nodes to the caller's memcg
From: David Ballesteros
Date: Tue Sep 15 2026 - 13:59:24 EST
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 <davimaba.v@xxxxxxxxx>
---
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);