]> git.baikalelectronics.ru Git - kernel.git/commitdiff
xen/gnttab: fix gnttab_end_foreign_access() without page specified
authorJuergen Gross <jgross@suse.com>
Mon, 7 Mar 2022 08:48:55 +0000 (09:48 +0100)
committerJuergen Gross <jgross@suse.com>
Mon, 7 Mar 2022 08:48:55 +0000 (09:48 +0100)
gnttab_end_foreign_access() is used to free a grant reference and
optionally to free the associated page. In case the grant is still in
use by the other side processing is being deferred. This leads to a
problem in case no page to be freed is specified by the caller: the
caller doesn't know that the page is still mapped by the other side
and thus should not be used for other purposes.

The correct way to handle this situation is to take an additional
reference to the granted page in case handling is being deferred and
to drop that reference when the grant reference could be freed
finally.

This requires that there are no users of gnttab_end_foreign_access()
left directly repurposing the granted page after the call, as this
might result in clobbered data or information leaks via the not yet
freed grant reference.

This is part of CVE-2022-23041 / XSA-396.

Reported-by: Simon Gaiser <simon@invisiblethingslab.com>
Signed-off-by: Juergen Gross <jgross@suse.com>
Reviewed-by: Jan Beulich <jbeulich@suse.com>
---
V4:
- expand comment in header
V5:
- get page ref in case of kmalloc() failure, too

drivers/xen/grant-table.c
include/xen/grant_table.h

index e6548910e79ff8186a862ae54378af5b7f559805..5c83d41766c8522cb0fbf65f28469b818f7b1ed0 100644 (file)
@@ -133,6 +133,10 @@ struct gnttab_ops {
         * return the frame.
         */
        unsigned long (*end_foreign_transfer_ref)(grant_ref_t ref);
+       /*
+        * Read the frame number related to a given grant reference.
+        */
+       unsigned long (*read_frame)(grant_ref_t ref);
 };
 
 struct unmap_refs_callback_data {
@@ -330,6 +334,16 @@ int gnttab_end_foreign_access_ref(grant_ref_t ref, int readonly)
 }
 EXPORT_SYMBOL_GPL(gnttab_end_foreign_access_ref);
 
+static unsigned long gnttab_read_frame_v1(grant_ref_t ref)
+{
+       return gnttab_shared.v1[ref].frame;
+}
+
+static unsigned long gnttab_read_frame_v2(grant_ref_t ref)
+{
+       return gnttab_shared.v2[ref].full_page.frame;
+}
+
 struct deferred_entry {
        struct list_head list;
        grant_ref_t ref;
@@ -359,12 +373,9 @@ static void gnttab_handle_deferred(struct timer_list *unused)
                spin_unlock_irqrestore(&gnttab_list_lock, flags);
                if (_gnttab_end_foreign_access_ref(entry->ref, entry->ro)) {
                        put_free_entry(entry->ref);
-                       if (entry->page) {
-                               pr_debug("freeing g.e. %#x (pfn %#lx)\n",
-                                        entry->ref, page_to_pfn(entry->page));
-                               put_page(entry->page);
-                       } else
-                               pr_info("freeing g.e. %#x\n", entry->ref);
+                       pr_debug("freeing g.e. %#x (pfn %#lx)\n",
+                                entry->ref, page_to_pfn(entry->page));
+                       put_page(entry->page);
                        kfree(entry);
                        entry = NULL;
                } else {
@@ -389,9 +400,18 @@ static void gnttab_handle_deferred(struct timer_list *unused)
 static void gnttab_add_deferred(grant_ref_t ref, bool readonly,
                                struct page *page)
 {
-       struct deferred_entry *entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
+       struct deferred_entry *entry;
+       gfp_t gfp = (in_atomic() || irqs_disabled()) ? GFP_ATOMIC : GFP_KERNEL;
        const char *what = KERN_WARNING "leaking";
 
+       entry = kmalloc(sizeof(*entry), gfp);
+       if (!page) {
+               unsigned long gfn = gnttab_interface->read_frame(ref);
+
+               page = pfn_to_page(gfn_to_pfn(gfn));
+               get_page(page);
+       }
+
        if (entry) {
                unsigned long flags;
 
@@ -1404,6 +1424,7 @@ static const struct gnttab_ops gnttab_v1_ops = {
        .update_entry                   = gnttab_update_entry_v1,
        .end_foreign_access_ref         = gnttab_end_foreign_access_ref_v1,
        .end_foreign_transfer_ref       = gnttab_end_foreign_transfer_ref_v1,
+       .read_frame                     = gnttab_read_frame_v1,
 };
 
 static const struct gnttab_ops gnttab_v2_ops = {
@@ -1415,6 +1436,7 @@ static const struct gnttab_ops gnttab_v2_ops = {
        .update_entry                   = gnttab_update_entry_v2,
        .end_foreign_access_ref         = gnttab_end_foreign_access_ref_v2,
        .end_foreign_transfer_ref       = gnttab_end_foreign_transfer_ref_v2,
+       .read_frame                     = gnttab_read_frame_v2,
 };
 
 static bool gnttab_need_v2(void)
index ab9e692a0ef4a41eb224e22079df5c0ff98d3efd..c9fea9389ebec74c13a26227f4901f8f577f6c7b 100644 (file)
@@ -107,7 +107,12 @@ int gnttab_end_foreign_access_ref(grant_ref_t ref, int readonly);
  * Note that the granted page might still be accessed (read or write) by the
  * other side after gnttab_end_foreign_access() returns, so even if page was
  * specified as 0 it is not allowed to just reuse the page for other
- * purposes immediately.
+ * purposes immediately. gnttab_end_foreign_access() will take an additional
+ * reference to the granted page in this case, which is dropped only after
+ * the grant is no longer in use.
+ * This requires that multi page allocations for areas subject to
+ * gnttab_end_foreign_access() are done via alloc_pages_exact() (and freeing
+ * via free_pages_exact()) in order to avoid high order pages.
  */
 void gnttab_end_foreign_access(grant_ref_t ref, int readonly,
                               unsigned long page);