[RFC PATCH] virtio_balloon: add VIRTIO_BALLOON_F_REPORTING_PM_SAFE feature bit

Link Lin posted 1 patch 2 hours ago
drivers/virtio/virtio_balloon.c     | 15 +++++++++++----
include/uapi/linux/virtio_balloon.h |  1 +
2 files changed, 12 insertions(+), 4 deletions(-)
[RFC PATCH] virtio_balloon: add VIRTIO_BALLOON_F_REPORTING_PM_SAFE feature bit
Posted by Link Lin 2 hours ago
Following up on the fix for the PM suspend Use-After-Free race condition in
mm/page_reporting (merged in mm-hotfixes-unstable: 
https://lore.kernel.org/all/20260723003650.CAAF01F000E9@smtp.kernel.org/), 
we face a hypervisor-side deployment dilemma.

Cloud hypervisors want to safely enable the Free Page Reporting (FPR) 
virtqueue across their fleets, but enabling it indiscriminately on guests 
without the recent suspend fix exposes them to UAF crashes. Relying on 
out-of-band metadata (e.g., OS image tags) to selectively enable the 
feature is fragile for custom user images or live-patched kernels.

To address this at the protocol level, we propose adding a new feature bit 
to the Virtio Specification: 
VIRTIO_BALLOON_F_REPORTING_PM_SAFE (Bit 6)

This establishes a formal device lifecycle contract for power management:
If negotiated, the driver MUST guarantee that all page reporting operations
are halted and pending requests are flushed before the device/system
transitions into a suspended state (e.g., ACPI S3/S4).

Deployment semantics:
- Hypervisors operating in a strict "safe mode" can offer Bit 6 exclusively 
  (suppressing Bit 5 / VIRTIO_BALLOON_F_REPORTING).
- Older, unpatched Linux guests will see Bit 5 is absent, ignore Bit 6, and 
  safely skip FPR initialization, preventing the suspend crash. Standard
  ballooning remains 100% functional.
- Patched Linux guests will recognize Bit 6 and safely initialize FPR.
- Note for fleet deployments: Non-Linux guests (e.g., Windows, FreeBSD) 
  that rely on Bit 5 will temporarily lose FPR if the hypervisor exclusively 
  offers Bit 6. This is considered an acceptable trade-off to globally 
  protect unpatched guests without relying on OS image tags, until those 
  respective virtio drivers adopt Bit 6.

Implementation Note on Upstream/Downstream Dependencies:
--------------------------------------------------------
Because it is critical that downstream Linux distros do not accidentally 
backport Bit 6 without the core MM UAF fix, the final upstream 
implementation of this patch will enforce a strict compile-time dependency. 
We plan to export a macro (e.g., PAGE_REPORTING_HAS_FREEZABLE_WQ) from the 
core MM fix, and wrap Bit 6 behind an #ifdef of that macro in 
virtio_balloon.c. This guarantees that compiler backports must consume the 
entire dependency chain to advertise the feature.

Below is the proposed Linux proof-of-concept based on upstream master. We 
introduce a helper virtio_balloon_has_reporting() to ensure virtqueues are 
properly allocated, torn down, and validated if either bit is negotiated.

If this architectural approach is acceptable for cloud deployments, we will 
formally submit this patch and open a corresponding issue for the OASIS 
Virtio specification.

Depends-on: <Message-ID: 20260723003650.CAAF01F000E9@smtp.kernel.org>
Signed-off-by: Link Lin <linkl@google.com>
---
 drivers/virtio/virtio_balloon.c     | 15 +++++++++++----
 include/uapi/linux/virtio_balloon.h |  1 +
 2 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c
index 581ac799d9..00e8273dc3 100644
--- a/drivers/virtio/virtio_balloon.c
+++ b/drivers/virtio/virtio_balloon.c
@@ -39,6 +39,12 @@
 	(1 << (VIRTIO_BALLOON_HINT_BLOCK_ORDER + PAGE_SHIFT))
 #define VIRTIO_BALLOON_HINT_BLOCK_PAGES (1 << VIRTIO_BALLOON_HINT_BLOCK_ORDER)
 
+static inline bool virtio_balloon_has_reporting(struct virtio_device *vdev)
+{
+	return virtio_has_feature(vdev, VIRTIO_BALLOON_F_REPORTING) ||
+	       virtio_has_feature(vdev, VIRTIO_BALLOON_F_REPORTING_PM_SAFE);
+}
+
 enum virtio_balloon_vq {
 	VIRTIO_BALLOON_VQ_INFLATE,
 	VIRTIO_BALLOON_VQ_DEFLATE,
@@ -598,7 +604,7 @@ static int init_vqs(struct virtio_balloon *vb)
 	if (virtio_has_feature(vb->vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT))
 		vqs_info[VIRTIO_BALLOON_VQ_FREE_PAGE].name = "free_page_vq";
 
-	if (virtio_has_feature(vb->vdev, VIRTIO_BALLOON_F_REPORTING)) {
+	if (virtio_balloon_has_reporting(vb->vdev)) {
 		vqs_info[VIRTIO_BALLOON_VQ_REPORTING].name = "reporting_vq";
 		vqs_info[VIRTIO_BALLOON_VQ_REPORTING].callback = balloon_ack;
 	}
@@ -635,7 +641,7 @@ static int init_vqs(struct virtio_balloon *vb)
 	if (virtio_has_feature(vb->vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT))
 		vb->free_page_vq = vqs[VIRTIO_BALLOON_VQ_FREE_PAGE];
 
-	if (virtio_has_feature(vb->vdev, VIRTIO_BALLOON_F_REPORTING))
+	if (virtio_balloon_has_reporting(vb->vdev))
 		vb->reporting_vq = vqs[VIRTIO_BALLOON_VQ_REPORTING];
 
 	return 0;
@@ -1013,7 +1019,7 @@ static int virtballoon_probe(struct virtio_device *vdev)
 	}
 
 	vb->pr_dev_info.report = virtballoon_free_page_report;
-	if (virtio_has_feature(vb->vdev, VIRTIO_BALLOON_F_REPORTING)) {
+	if (virtio_balloon_has_reporting(vb->vdev)) {
 		unsigned int capacity;
 
 		capacity = virtqueue_get_vring_size(vb->reporting_vq);
@@ -1099,7 +1105,7 @@ static void virtballoon_remove(struct virtio_device *vdev)
 {
 	struct virtio_balloon *vb = vdev->priv;
 
-	if (virtio_has_feature(vb->vdev, VIRTIO_BALLOON_F_REPORTING))
+	if (virtio_balloon_has_reporting(vb->vdev))
 		page_reporting_unregister(&vb->pr_dev_info);
 	if (virtio_has_feature(vb->vdev, VIRTIO_BALLOON_F_DEFLATE_ON_OOM))
 		unregister_oom_notifier(&vb->oom_nb);
@@ -1162,8 +1168,10 @@ static int virtballoon_validate(struct virtio_device *vdev)
 	 */
 	if (!want_init_on_free() && !page_poisoning_enabled_static())
 		__virtio_clear_bit(vdev, VIRTIO_BALLOON_F_PAGE_POISON);
-	else if (!virtio_has_feature(vdev, VIRTIO_BALLOON_F_PAGE_POISON))
+	else if (!virtio_has_feature(vdev, VIRTIO_BALLOON_F_PAGE_POISON)) {
 		__virtio_clear_bit(vdev, VIRTIO_BALLOON_F_REPORTING);
+		__virtio_clear_bit(vdev, VIRTIO_BALLOON_F_REPORTING_PM_SAFE);
+	}
 
 	__virtio_clear_bit(vdev, VIRTIO_F_ACCESS_PLATFORM);
 	return 0;
@@ -1176,6 +1184,7 @@ static unsigned int features[] = {
 	VIRTIO_BALLOON_F_FREE_PAGE_HINT,
 	VIRTIO_BALLOON_F_PAGE_POISON,
 	VIRTIO_BALLOON_F_REPORTING,
+	VIRTIO_BALLOON_F_REPORTING_PM_SAFE,
 };
 
 static struct virtio_driver virtio_balloon_driver = {
diff --git a/include/uapi/linux/virtio_balloon.h b/include/uapi/linux/virtio_balloon.h
index ee35a37280..d206f156d6 100644
--- a/include/uapi/linux/virtio_balloon.h
+++ b/include/uapi/linux/virtio_balloon.h
@@ -37,6 +37,7 @@
 #define VIRTIO_BALLOON_F_FREE_PAGE_HINT	3 /* VQ to report free pages */
 #define VIRTIO_BALLOON_F_PAGE_POISON	4 /* Guest is using page poisoning */
 #define VIRTIO_BALLOON_F_REPORTING	5 /* Page reporting virtqueue */
+#define VIRTIO_BALLOON_F_REPORTING_PM_SAFE	6 /* PM-safe page reporting */
 
 /* Size of a PFN in the balloon interface. */
 #define VIRTIO_BALLOON_PFN_SHIFT 12
--