aboutsummaryrefslogtreecommitdiff
path: root/emulators/xen-kernel/files/0001-gnttab-Fix-handling-of-dev_bus_addr-during-unmap.patch
blob: 3b2c7471a7f64c162c943dda8a1856716fafb65e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
From fd97f5f5ba9375163c8d8771fe551bb4a6423b36 Mon Sep 17 00:00:00 2001
From: George Dunlap <george.dunlap@citrix.com>
Date: Thu, 15 Jun 2017 16:24:02 +0100
Subject: [PATCH 1/4] gnttab: Fix handling of dev_bus_addr during unmap

If a grant has been mapped with the GNTTAB_device_map flag, calling
grant_unmap_ref() with dev_bus_addr set to zero should cause the
GNTTAB_device_map part of the mapping to be left alone.

Unfortunately, at the moment, op->dev_bus_addr is implicitly checked
before clearing the map and adjusting the pin count, but only the bits
above 12; and it is not checked at all before dropping page
references.  This means a guest can repeatedly make such a call to
cause the reference count to drop to zero, causing the page to be
freed and re-used, even though it's still mapped in its pagetables.

To fix this, always check op->dev_bus_addr explicitly for being
non-zero, as well as op->flag & GNTMAP_device_map, before doing
operations on the device_map.

While we're here, make the logic a bit cleaner:

* Always initialize op->frame to zero and set it from act->frame, to reduce the
chance of untrusted input being used

* Explicitly check the full dev_bus_addr against act->frame <<
  PAGE_SHIFT, rather than ignoring the lower 12 bits

This is part of XSA-224.

Reported-by: Jan Beulich <jbeulich@suse.com>
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
Signed-off-by: Jan Beulich <jbeulich@suse.com>
---
 xen/common/grant_table.c | 23 +++++++++++------------
 1 file changed, 11 insertions(+), 12 deletions(-)

diff --git a/xen/common/grant_table.c b/xen/common/grant_table.c
index c4d73af..69cbdb6 100644
--- a/xen/common/grant_table.c
+++ b/xen/common/grant_table.c
@@ -1089,8 +1089,6 @@ __gnttab_unmap_common(
     ld = current->domain;
     lgt = ld->grant_table;
 
-    op->frame = (unsigned long)(op->dev_bus_addr >> PAGE_SHIFT);
-
     if ( unlikely(op->handle >= lgt->maptrack_limit) )
     {
         gdprintk(XENLOG_INFO, "Bad handle (%d).\n", op->handle);
@@ -1174,16 +1172,14 @@ __gnttab_unmap_common(
         goto act_release_out;
     }
 
-    if ( op->frame == 0 )
-    {
-        op->frame = act->frame;
-    }
-    else
+    op->frame = act->frame;
+
+    if ( op->dev_bus_addr )
     {
-        if ( unlikely(op->frame != act->frame) )
+        if ( unlikely(op->dev_bus_addr != pfn_to_paddr(act->frame)) )
             PIN_FAIL(act_release_out, GNTST_general_error,
-                     "Bad frame number doesn't match gntref. (%lx != %lx)\n",
-                     op->frame, act->frame);
+                     "Bus address doesn't match gntref (%"PRIx64" != %"PRIpaddr")\n",
+                     op->dev_bus_addr, pfn_to_paddr(act->frame));
 
         map->flags &= ~GNTMAP_device_map;
     }
@@ -1276,7 +1272,8 @@ __gnttab_unmap_common_complete(struct gnttab_unmap_common *op)
     else
         status = &status_entry(rgt, op->ref);
 
-    if ( unlikely(op->frame != act->frame) ) 
+    if ( op->dev_bus_addr &&
+         unlikely(op->dev_bus_addr != pfn_to_paddr(act->frame)) )
     {
         /*
          * Suggests that __gntab_unmap_common failed early and so
@@ -1287,7 +1284,7 @@ __gnttab_unmap_common_complete(struct gnttab_unmap_common *op)
 
     pg = mfn_to_page(op->frame);
 
-    if ( op->flags & GNTMAP_device_map ) 
+    if ( op->dev_bus_addr && (op->flags & GNTMAP_device_map) )
     {
         if ( !is_iomem_page(act->frame) )
         {
@@ -1358,6 +1355,7 @@ __gnttab_unmap_grant_ref(
     /* Intialise these in case common contains old state */
     common->new_addr = 0;
     common->rd = NULL;
+    common->frame = 0;
 
     __gnttab_unmap_common(common);
     op->status = common->status;
@@ -1422,6 +1420,7 @@ __gnttab_unmap_and_replace(
     /* Intialise these in case common contains old state */
     common->dev_bus_addr = 0;
     common->rd = NULL;
+    common->frame = 0;
 
     __gnttab_unmap_common(common);
     op->status = common->status;
-- 
2.1.4