b2a8d1d
From: Jan Beulich <jbeulich@suse.com>
b2a8d1d
Subject: gnttab: also validate PTE permissions upon destroy/replace
b2a8d1d
b2a8d1d
In order for PTE handling to match up with the reference counting done
b2a8d1d
by common code, presence and writability of grant mapping PTEs must
b2a8d1d
also be taken into account; validating just the frame number is not
b2a8d1d
enough. This is in particular relevant if a guest fiddles with grant
b2a8d1d
PTEs via non-grant hypercalls.
b2a8d1d
b2a8d1d
Note that the flags being passed to replace_grant_host_mapping()
b2a8d1d
already happen to be those of the existing mapping, so no new function
b2a8d1d
parameter is needed.
b2a8d1d
b2a8d1d
This is XSA-234.
b2a8d1d
b2a8d1d
Reported-by: Andrew Cooper <andrew.cooper3@citrix.com>
b2a8d1d
Signed-off-by: Jan Beulich <jbeulich@suse.com>
b2a8d1d
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>
b2a8d1d
b2a8d1d
--- a/xen/arch/x86/mm.c
b2a8d1d
+++ b/xen/arch/x86/mm.c
b2a8d1d
@@ -4017,7 +4017,8 @@ static int create_grant_pte_mapping(
b2a8d1d
 }
b2a8d1d
 
b2a8d1d
 static int destroy_grant_pte_mapping(
b2a8d1d
-    uint64_t addr, unsigned long frame, struct domain *d)
b2a8d1d
+    uint64_t addr, unsigned long frame, unsigned int grant_pte_flags,
b2a8d1d
+    struct domain *d)
b2a8d1d
 {
b2a8d1d
     int rc = GNTST_okay;
b2a8d1d
     void *va;
b2a8d1d
@@ -4063,16 +4064,27 @@ static int destroy_grant_pte_mapping(
b2a8d1d
 
b2a8d1d
     ol1e = *(l1_pgentry_t *)va;
b2a8d1d
     
b2a8d1d
-    /* Check that the virtual address supplied is actually mapped to frame. */
b2a8d1d
-    if ( unlikely(l1e_get_pfn(ol1e) != frame) )
b2a8d1d
+    /*
b2a8d1d
+     * Check that the PTE supplied actually maps frame (with appropriate
b2a8d1d
+     * permissions).
b2a8d1d
+     */
b2a8d1d
+    if ( unlikely(l1e_get_pfn(ol1e) != frame) ||
b2a8d1d
+         unlikely((l1e_get_flags(ol1e) ^ grant_pte_flags) &
b2a8d1d
+                  (_PAGE_PRESENT | _PAGE_RW)) )
b2a8d1d
     {
b2a8d1d
         page_unlock(page);
b2a8d1d
-        MEM_LOG("PTE entry %lx for address %"PRIx64" doesn't match frame %lx",
b2a8d1d
-                (unsigned long)l1e_get_intpte(ol1e), addr, frame);
b2a8d1d
+        MEM_LOG("PTE %"PRIpte" at %"PRIx64" doesn't match grant (%"PRIpte")",
b2a8d1d
+                l1e_get_intpte(ol1e), addr,
b2a8d1d
+                l1e_get_intpte(l1e_from_pfn(frame, grant_pte_flags)));
b2a8d1d
         rc = GNTST_general_error;
b2a8d1d
         goto failed;
b2a8d1d
     }
b2a8d1d
 
b2a8d1d
+    if ( unlikely((l1e_get_flags(ol1e) ^ grant_pte_flags) &
b2a8d1d
+                  ~(_PAGE_AVAIL | PAGE_CACHE_ATTRS)) )
b2a8d1d
+        MEM_LOG("PTE flags %x at %"PRIx64" don't match grant (%x)\n",
b2a8d1d
+                l1e_get_flags(ol1e), addr, grant_pte_flags);
b2a8d1d
+
b2a8d1d
     /* Delete pagetable entry. */
b2a8d1d
     if ( unlikely(!UPDATE_ENTRY
b2a8d1d
                   (l1, 
b2a8d1d
@@ -4081,7 +4093,7 @@ static int destroy_grant_pte_mapping(
b2a8d1d
                    0)) )
b2a8d1d
     {
b2a8d1d
         page_unlock(page);
b2a8d1d
-        MEM_LOG("Cannot delete PTE entry at %p", va);
b2a8d1d
+        MEM_LOG("Cannot delete PTE entry at %"PRIx64, addr);
b2a8d1d
         rc = GNTST_general_error;
b2a8d1d
         goto failed;
b2a8d1d
     }
b2a8d1d
@@ -4149,7 +4161,8 @@ static int create_grant_va_mapping(
b2a8d1d
 }
b2a8d1d
 
b2a8d1d
 static int replace_grant_va_mapping(
b2a8d1d
-    unsigned long addr, unsigned long frame, l1_pgentry_t nl1e, struct vcpu *v)
b2a8d1d
+    unsigned long addr, unsigned long frame, unsigned int grant_pte_flags,
b2a8d1d
+    l1_pgentry_t nl1e, struct vcpu *v)
b2a8d1d
 {
b2a8d1d
     l1_pgentry_t *pl1e, ol1e;
b2a8d1d
     unsigned long gl1mfn;
b2a8d1d
@@ -4185,19 +4198,30 @@ static int replace_grant_va_mapping(
b2a8d1d
 
b2a8d1d
     ol1e = *pl1e;
b2a8d1d
 
b2a8d1d
-    /* Check that the virtual address supplied is actually mapped to frame. */
b2a8d1d
-    if ( unlikely(l1e_get_pfn(ol1e) != frame) )
b2a8d1d
-    {
b2a8d1d
-        MEM_LOG("PTE entry %lx for address %lx doesn't match frame %lx",
b2a8d1d
-                l1e_get_pfn(ol1e), addr, frame);
b2a8d1d
+    /*
b2a8d1d
+     * Check that the virtual address supplied is actually mapped to frame
b2a8d1d
+     * (with appropriate permissions).
b2a8d1d
+     */
b2a8d1d
+    if ( unlikely(l1e_get_pfn(ol1e) != frame) ||
b2a8d1d
+         unlikely((l1e_get_flags(ol1e) ^ grant_pte_flags) &
b2a8d1d
+                  (_PAGE_PRESENT | _PAGE_RW)) )
b2a8d1d
+    {
b2a8d1d
+        MEM_LOG("PTE %"PRIpte" for %lx doesn't match grant (%"PRIpte")",
b2a8d1d
+                l1e_get_intpte(ol1e), addr,
b2a8d1d
+                l1e_get_intpte(l1e_from_pfn(frame, grant_pte_flags)));
b2a8d1d
         rc = GNTST_general_error;
b2a8d1d
         goto unlock_and_out;
b2a8d1d
     }
b2a8d1d
 
b2a8d1d
+    if ( unlikely((l1e_get_flags(ol1e) ^ grant_pte_flags) &
b2a8d1d
+                  ~(_PAGE_AVAIL | PAGE_CACHE_ATTRS)) )
b2a8d1d
+        MEM_LOG("PTE flags %x for %"PRIx64" don't match grant (%x)",
b2a8d1d
+                l1e_get_flags(ol1e), addr, grant_pte_flags);
b2a8d1d
+
b2a8d1d
     /* Delete pagetable entry. */
b2a8d1d
     if ( unlikely(!UPDATE_ENTRY(l1, pl1e, ol1e, nl1e, gl1mfn, v, 0)) )
b2a8d1d
     {
b2a8d1d
-        MEM_LOG("Cannot delete PTE entry at %p", (unsigned long *)pl1e);
b2a8d1d
+        MEM_LOG("Cannot delete PTE entry for %"PRIx64, addr);
b2a8d1d
         rc = GNTST_general_error;
b2a8d1d
         goto unlock_and_out;
b2a8d1d
     }
b2a8d1d
@@ -4211,9 +4235,11 @@ static int replace_grant_va_mapping(
b2a8d1d
 }
b2a8d1d
 
b2a8d1d
 static int destroy_grant_va_mapping(
b2a8d1d
-    unsigned long addr, unsigned long frame, struct vcpu *v)
b2a8d1d
+    unsigned long addr, unsigned long frame, unsigned int grant_pte_flags,
b2a8d1d
+    struct vcpu *v)
b2a8d1d
 {
b2a8d1d
-    return replace_grant_va_mapping(addr, frame, l1e_empty(), v);
b2a8d1d
+    return replace_grant_va_mapping(addr, frame, grant_pte_flags,
b2a8d1d
+                                    l1e_empty(), v);
b2a8d1d
 }
b2a8d1d
 
b2a8d1d
 static int create_grant_p2m_mapping(uint64_t addr, unsigned long frame,
b2a8d1d
@@ -4307,21 +4333,40 @@ int replace_grant_host_mapping(
b2a8d1d
     unsigned long gl1mfn;
b2a8d1d
     struct page_info *l1pg;
b2a8d1d
     int rc;
b2a8d1d
+    unsigned int grant_pte_flags;
b2a8d1d
     
b2a8d1d
     if ( paging_mode_external(current->domain) )
b2a8d1d
         return replace_grant_p2m_mapping(addr, frame, new_addr, flags);
b2a8d1d
 
b2a8d1d
+    grant_pte_flags =
b2a8d1d
+        _PAGE_PRESENT | _PAGE_ACCESSED | _PAGE_DIRTY | _PAGE_GNTTAB | _PAGE_NX;
b2a8d1d
+
b2a8d1d
+    if ( flags & GNTMAP_application_map )
b2a8d1d
+        grant_pte_flags |= _PAGE_USER;
b2a8d1d
+    if ( !(flags & GNTMAP_readonly) )
b2a8d1d
+        grant_pte_flags |= _PAGE_RW;
b2a8d1d
+    /*
b2a8d1d
+     * On top of the explicit settings done by create_grant_host_mapping()
b2a8d1d
+     * also open-code relevant parts of adjust_guest_l1e(). Don't mirror
b2a8d1d
+     * available and cachability flags, though.
b2a8d1d
+     */
b2a8d1d
+    if ( !is_pv_32bit_domain(curr->domain) )
b2a8d1d
+        grant_pte_flags |= (grant_pte_flags & _PAGE_USER)
b2a8d1d
+                           ? _PAGE_GLOBAL
b2a8d1d
+                           : _PAGE_GUEST_KERNEL | _PAGE_USER;
b2a8d1d
+
b2a8d1d
     if ( flags & GNTMAP_contains_pte )
b2a8d1d
     {
b2a8d1d
         if ( !new_addr )
b2a8d1d
-            return destroy_grant_pte_mapping(addr, frame, curr->domain);
b2a8d1d
+            return destroy_grant_pte_mapping(addr, frame, grant_pte_flags,
b2a8d1d
+                                             curr->domain);
b2a8d1d
         
b2a8d1d
         MEM_LOG("Unsupported grant table operation");
b2a8d1d
         return GNTST_general_error;
b2a8d1d
     }
b2a8d1d
 
b2a8d1d
     if ( !new_addr )
b2a8d1d
-        return destroy_grant_va_mapping(addr, frame, curr);
b2a8d1d
+        return destroy_grant_va_mapping(addr, frame, grant_pte_flags, curr);
b2a8d1d
 
b2a8d1d
     pl1e = guest_map_l1e(new_addr, &gl1mfn);
b2a8d1d
     if ( !pl1e )
b2a8d1d
@@ -4369,7 +4414,7 @@ int replace_grant_host_mapping(
b2a8d1d
     put_page(l1pg);
b2a8d1d
     guest_unmap_l1e(pl1e);
b2a8d1d
 
b2a8d1d
-    rc = replace_grant_va_mapping(addr, frame, ol1e, curr);
b2a8d1d
+    rc = replace_grant_va_mapping(addr, frame, grant_pte_flags, ol1e, curr);
b2a8d1d
     if ( rc && !paging_mode_refcounts(curr->domain) )
b2a8d1d
         put_page_from_l1e(ol1e, curr->domain);
b2a8d1d