Skip to content

Commit 42baefa

Browse files
committed
xen/gnttab: fix gnttab_end_foreign_access() without page specified
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 <[email protected]> Signed-off-by: Juergen Gross <[email protected]> Reviewed-by: Jan Beulich <[email protected]> --- V4: - expand comment in header V5: - get page ref in case of kmalloc() failure, too
1 parent b0576cc commit 42baefa

File tree

2 files changed

+35
-8
lines changed

2 files changed

+35
-8
lines changed

drivers/xen/grant-table.c

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ struct gnttab_ops {
133133
* return the frame.
134134
*/
135135
unsigned long (*end_foreign_transfer_ref)(grant_ref_t ref);
136+
/*
137+
* Read the frame number related to a given grant reference.
138+
*/
139+
unsigned long (*read_frame)(grant_ref_t ref);
136140
};
137141

138142
struct unmap_refs_callback_data {
@@ -330,6 +334,16 @@ int gnttab_end_foreign_access_ref(grant_ref_t ref, int readonly)
330334
}
331335
EXPORT_SYMBOL_GPL(gnttab_end_foreign_access_ref);
332336

337+
static unsigned long gnttab_read_frame_v1(grant_ref_t ref)
338+
{
339+
return gnttab_shared.v1[ref].frame;
340+
}
341+
342+
static unsigned long gnttab_read_frame_v2(grant_ref_t ref)
343+
{
344+
return gnttab_shared.v2[ref].full_page.frame;
345+
}
346+
333347
struct deferred_entry {
334348
struct list_head list;
335349
grant_ref_t ref;
@@ -359,12 +373,9 @@ static void gnttab_handle_deferred(struct timer_list *unused)
359373
spin_unlock_irqrestore(&gnttab_list_lock, flags);
360374
if (_gnttab_end_foreign_access_ref(entry->ref, entry->ro)) {
361375
put_free_entry(entry->ref);
362-
if (entry->page) {
363-
pr_debug("freeing g.e. %#x (pfn %#lx)\n",
364-
entry->ref, page_to_pfn(entry->page));
365-
put_page(entry->page);
366-
} else
367-
pr_info("freeing g.e. %#x\n", entry->ref);
376+
pr_debug("freeing g.e. %#x (pfn %#lx)\n",
377+
entry->ref, page_to_pfn(entry->page));
378+
put_page(entry->page);
368379
kfree(entry);
369380
entry = NULL;
370381
} else {
@@ -389,9 +400,18 @@ static void gnttab_handle_deferred(struct timer_list *unused)
389400
static void gnttab_add_deferred(grant_ref_t ref, bool readonly,
390401
struct page *page)
391402
{
392-
struct deferred_entry *entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
403+
struct deferred_entry *entry;
404+
gfp_t gfp = (in_atomic() || irqs_disabled()) ? GFP_ATOMIC : GFP_KERNEL;
393405
const char *what = KERN_WARNING "leaking";
394406

407+
entry = kmalloc(sizeof(*entry), gfp);
408+
if (!page) {
409+
unsigned long gfn = gnttab_interface->read_frame(ref);
410+
411+
page = pfn_to_page(gfn_to_pfn(gfn));
412+
get_page(page);
413+
}
414+
395415
if (entry) {
396416
unsigned long flags;
397417

@@ -1404,6 +1424,7 @@ static const struct gnttab_ops gnttab_v1_ops = {
14041424
.update_entry = gnttab_update_entry_v1,
14051425
.end_foreign_access_ref = gnttab_end_foreign_access_ref_v1,
14061426
.end_foreign_transfer_ref = gnttab_end_foreign_transfer_ref_v1,
1427+
.read_frame = gnttab_read_frame_v1,
14071428
};
14081429

14091430
static const struct gnttab_ops gnttab_v2_ops = {
@@ -1415,6 +1436,7 @@ static const struct gnttab_ops gnttab_v2_ops = {
14151436
.update_entry = gnttab_update_entry_v2,
14161437
.end_foreign_access_ref = gnttab_end_foreign_access_ref_v2,
14171438
.end_foreign_transfer_ref = gnttab_end_foreign_transfer_ref_v2,
1439+
.read_frame = gnttab_read_frame_v2,
14181440
};
14191441

14201442
static bool gnttab_need_v2(void)

include/xen/grant_table.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,12 @@ int gnttab_end_foreign_access_ref(grant_ref_t ref, int readonly);
107107
* Note that the granted page might still be accessed (read or write) by the
108108
* other side after gnttab_end_foreign_access() returns, so even if page was
109109
* specified as 0 it is not allowed to just reuse the page for other
110-
* purposes immediately.
110+
* purposes immediately. gnttab_end_foreign_access() will take an additional
111+
* reference to the granted page in this case, which is dropped only after
112+
* the grant is no longer in use.
113+
* This requires that multi page allocations for areas subject to
114+
* gnttab_end_foreign_access() are done via alloc_pages_exact() (and freeing
115+
* via free_pages_exact()) in order to avoid high order pages.
111116
*/
112117
void gnttab_end_foreign_access(grant_ref_t ref, int readonly,
113118
unsigned long page);

0 commit comments

Comments
 (0)