During PM freeze (e.g. S3 suspend or S4 hibernation), device drivers like
virtio_balloon reset their underlying virtio devices and delete their
virtqueues via vdev->config->del_vqs().

However, page reporting work (page_reporting_process) was scheduled on
the global system_wq. Because system_wq lacks the WQ_FREEZABLE flag, the
PM freezer skips it, leaving page_reporting_process active during suspend.

If pages are freed into the buddy allocator while suspending (for example,
when core MM invokes the balloon shrinker during S4 hibernation image
saving), page reporting triggers virtballoon_free_page_report() on deleted
virtqueues, resulting in a Use-After-Free / General Protection Fault:

    [  196.795226] general protection fault, probably for non-canonical address 
0xaa1436fe70dae6df: 0000 [#1] SMP NOPTI
    [  196.825967] Workqueue: events page_reporting_process
    [  196.831038] RIP: 0010:virtqueue_add_split+0x233/0x4c0 [virtio_ring]
    [  196.927073] virtballoon_free_page_report+0x3a/0xe0 [virtio_balloon]
    [  196.946943] page_reporting_process+0x370/0x4f0

Fix this by switching page reporting work to system_freezable_wq. This
ensures that the PM freezer pauses page_reporting_process before device
drivers destroy their reporting virtqueues. Because the reporting worker
is frozen, memory reclamation/freeing (e.g. via shrinker execution) can
safely return pages to MM during freeze without triggering unfrozen
reporting work on deleted virtqueues.

This aligns with the driver's existing design. The comment in
virtballoon_freeze() states:
    /*
     * The workqueue is already frozen by the PM core before this
     * function is called.
     */

Testing:
I have verified these fixes using Google’s virtualization infrastructure by
running continuous suspend/resume iterations (40+ cycles) while churning
memory using stress-ng (`stress-ng --vm 4 --vm-bytes 60% --timeout 1`) to
constantly create free pages for the buddy allocator. We also set the
`page_reporting_order` parameter to 0 to make the page reporting worker
highly sensitive, forcing it to pick up any 4K free pages. This confirmed
that the UAF crashes are no longer reproducible.

Fixes: 924a663f75e2 ("virtio-balloon: Reporting free page reservations")
Cc: [email protected]
Suggested-by: David Hildenbrand (Arm) <[email protected]>
Suggested-by: Michael S. Tsirkin <[email protected]>
Acked-by: David Rientjes <[email protected]>
Acked-by: David Hildenbrand (Arm) <[email protected]>
Acked-by: Michael S. Tsirkin <[email protected]>
Signed-off-by: Link Lin <[email protected]>
---
v3:
  - Dropped Patch 2/2 (virtio_balloon shrinker flag). Freeing pages via
    the shrinker only returns pages to MM; with page reporting work now
    properly serialized on system_freezable_wq, shrinker execution during
    freeze is harmless and requires no additional locking/flags in
    virtio_balloon.
  - Link to v2: 
https://lore.kernel.org/all/[email protected]/

v2:
  - Split into a 2-patch series including explicit shrinker fencing.
  - Link to RFC: 
https://lore.kernel.org/all/[email protected]/

 mm/page_reporting.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/mm/page_reporting.c b/mm/page_reporting.c
index 7418f2e500..4dc6f4b852 100644
--- a/mm/page_reporting.c
+++ b/mm/page_reporting.c
@@ -80,7 +80,8 @@ __page_reporting_request(struct page_reporting_dev_info 
*prdev)
         * now we are limiting this to running no more than once every
         * couple of seconds.
         */
-       schedule_delayed_work(&prdev->work, PAGE_REPORTING_DELAY);
+       queue_delayed_work(system_freezable_wq, &prdev->work,
+                          PAGE_REPORTING_DELAY);
 }
 
 /* notify prdev of free page reporting request */
@@ -343,7 +344,8 @@ static void page_reporting_process(struct work_struct *work)
         */
        state = atomic_cmpxchg(&prdev->state, state, PAGE_REPORTING_IDLE);
        if (state == PAGE_REPORTING_REQUESTED)
-               schedule_delayed_work(&prdev->work, PAGE_REPORTING_DELAY);
+               queue_delayed_work(system_freezable_wq, &prdev->work,
+                                  PAGE_REPORTING_DELAY);
 }
 
 static DEFINE_MUTEX(page_reporting_mutex);
-- 
2.55.0

Reply via email to