]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
x86/kvm: Avoid freeing stack-allocated node in kvm_async_pf_queue_task
authorRyosuke Yasuoka <ryasuoka@redhat.com>
Sat, 6 Dec 2025 14:09:36 +0000 (23:09 +0900)
committerPaolo Bonzini <pbonzini@redhat.com>
Thu, 1 Jan 2026 09:01:32 +0000 (10:01 +0100)
kvm_async_pf_queue_task() can incorrectly try to kfree() a node
allocated on the stack of kvm_async_pf_task_wait_schedule().

This occurs when a task requests a PF while another task's PF request
with the same token is still pending. Since the token is derived from
the (u32)address in exc_page_fault(), two different tasks can generate
the same token.

Currently, kvm_async_pf_queue_task() assumes that any entry found in the
list is a dummy entry and tries to kfree() it. To fix this, add a flag
to the node structure to distinguish stack-allocated nodes, and only
kfree() the node if it is a dummy entry.

Signed-off-by: Ryosuke Yasuoka <ryasuoka@redhat.com>
Message-ID: <20251206140939.144038-1-ryasuoka@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
arch/x86/kernel/kvm.c

index df78ddee0abbb7d3f9d30ad8ed11266e817dfc6a..37dc8465e0f516150ce6398dec3142150e17b44c 100644 (file)
@@ -89,6 +89,7 @@ struct kvm_task_sleep_node {
        struct swait_queue_head wq;
        u32 token;
        int cpu;
+       bool dummy;
 };
 
 static struct kvm_task_sleep_head {
@@ -120,15 +121,26 @@ static bool kvm_async_pf_queue_task(u32 token, struct kvm_task_sleep_node *n)
        raw_spin_lock(&b->lock);
        e = _find_apf_task(b, token);
        if (e) {
-               /* dummy entry exist -> wake up was delivered ahead of PF */
-               hlist_del(&e->link);
+               struct kvm_task_sleep_node *dummy = NULL;
+
+               /*
+                * The entry can either be a 'dummy' entry (which is put on the
+                * list when wake-up happens ahead of APF handling completion)
+                * or a token from another task which should not be touched.
+                */
+               if (e->dummy) {
+                       hlist_del(&e->link);
+                       dummy = e;
+               }
+
                raw_spin_unlock(&b->lock);
-               kfree(e);
+               kfree(dummy);
                return false;
        }
 
        n->token = token;
        n->cpu = smp_processor_id();
+       n->dummy = false;
        init_swait_queue_head(&n->wq);
        hlist_add_head(&n->link, &b->list);
        raw_spin_unlock(&b->lock);
@@ -231,6 +243,7 @@ again:
                }
                dummy->token = token;
                dummy->cpu = smp_processor_id();
+               dummy->dummy = true;
                init_swait_queue_head(&dummy->wq);
                hlist_add_head(&dummy->link, &b->list);
                dummy = NULL;