Subject: gnttab: check values against active entry when copying an already-pinned one
From: Jan Beulich <[email-masked]>

acquire_grant_for_copy() passes to its caller both an MFN and a struct
page_info *. The two really need to be in sync for the get_page()
underlying get_paged_frame() and get_page_type() (both acting on the
passed back struct page_info *) and the map_domain_page() (acting on the
passed back MFN) to achieve the intended effect.

Go further and also check other properties: GTF_transitive / GTF_sub_page
may have been flipped in the shared entry, so respective fields / values
also may not match.

The one field which we can be sure does match (as it was checked earlier
in the function) is ->domid. Add an assertion nevertheless.

This is CVE-2026-62428 / XSA-500.

Fixes: d8cbecb1eeed ("grant-tables: Use get_page_from_gfn() instead of get_gfn()/put_gfn")
Signed-off-by: Jan Beulich <[email-masked]>
Reviewed-by: Juergen Gross <[email-masked]>

diff --git a/xen/common/grant_table.c b/xen/common/grant_table.c
index 17e1af9fcf8a..b83dd7bc2af2 100644
--- a/xen/common/grant_table.c
+++ b/xen/common/grant_table.c
@@ -2804,6 +2804,21 @@ acquire_grant_for_copy(
             act->trans_gref = trans_gref;
             act->mfn = grant_mfn;
         }
+        else if ( !mfn_eq(act->mfn, grant_mfn) ||
+                  act->src_domid != td->domain_id ||
+                  act->trans_gref != trans_gref ||
+                  (act->is_sub_page &&
+                   (!is_sub_page ||
+                    act->start != trans_page_off ||
+                    act->length != trans_length)) )
+        {
+            put_page(*page);
+            *page = NULL;
+            rc = GNTST_general_error;
+            goto unlock_out_clear;
+        }
+        else
+            ASSERT(act->domid == ldom);
     }
     else
     {