From: Jan Beulich <jbeulich@suse.com>
Subject: gnttab: split gnttab_map_frame()

If a domain tries to map status frames in parallel to switching grant
table version from 2 to 1, the mapping operation may put in place P2M
entries referencing MFNs which gnttab_unpopulate_status_frames() is in the
process of freeing.

Ideally we would refcount pages when entered into P2M tables, but that's a
significant change. Extend the grant-table-locked region instead in
xenmem_add_to_physmap_one() (being the sole caller of gnttab_map_frame()),
such that a race with gnttab_unpopulate_status_frames() is no longer
possible.

This is XSA-486 / CVE-2026-23558.

Fixes: 5ce8fafa947c ("Dynamic grant-table sizing")
Fixes: a98dc13703e0 ("Introduce a grant_entry_v2 structure")
Reported-by: Rafal Wojtczuk <rafal.wojtczuk@7bulls.com>
Signed-off-by: Jan Beulich <jbeulich@suse.com>
Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>

--- a/xen/arch/arm/mm.c
+++ b/xen/arch/arm/mm.c
@@ -1210,7 +1210,8 @@ int xenmem_add_to_physmap_one(
             t = p2m_ram_rw;
         }
 
-        grant_write_unlock(d->grant_table);
+        if ( !page )
+            grant_write_unlock(d->grant_table);
 
         if ( mfn == mfn_x(INVALID_MFN) )
         {
@@ -1287,10 +1288,27 @@ int xenmem_add_to_physmap_one(
      * to drop the reference we took earlier. In all other cases we need to
      * drop any reference we took earlier (perhaps indirectly).
      */
-    if ( space == XENMAPSPACE_gmfn_foreign ? rc : page != NULL )
+    switch ( space )
     {
+    default:
+        if ( page )
+            put_page(page);
+        break;
+
+    case XENMAPSPACE_grant_table:
+        if ( page )
+        {
+            put_page(page);
+            grant_write_unlock(d->grant_table);
+        }
+        break;
+
+    case XENMAPSPACE_gmfn_foreign:
+        if ( !rc )
+            break;
         ASSERT(page != NULL);
         put_page(page);
+        break;
     }
 
     return rc;
--- a/xen/arch/x86/mm.c
+++ b/xen/arch/x86/mm.c
@@ -5867,7 +5867,8 @@ int xenmem_add_to_physmap_one(
                     mfn = virt_to_mfn(d->grant_table->shared_raw[idx]);
             }
 
-            grant_write_unlock(d->grant_table);
+            if ( !page )
+                grant_write_unlock(d->grant_table);
             break;
         case XENMAPSPACE_gmfn_range:
         case XENMAPSPACE_gmfn:
@@ -5960,8 +5961,13 @@ int xenmem_add_to_physmap_one(
     }
 
     if ( page )
+    {
         put_page(page);
 
+        if ( space == XENMAPSPACE_grant_table )
+            grant_write_unlock(d->grant_table);
+    }
+
     return rc;
 }
 
