x86/mm: relinquish_memory: Grab an extra type ref when setting PGT_partial

The PGT_partial bit in page->type_info holds both a type count and a
general ref count.  During domain tear-down, when free_page_type()
returns -ERESTART, relinquish_memory() correctly handles the general
ref count, but fails to grab an extra type count when setting
PGT_partial.  When this bit is eventually cleared, type_count underflows
and triggers the following BUG in page_alloc.c:free_domheap_pages():

    BUG_ON((pg[i].u.inuse.type_info & PGT_count_mask) != 0);

As far as we can tell, this page underflow cannot be exploited any any
other way: The page can't be used as a pagetable by the dying domain
because it's dying; it can't be used as a pagetable by any other
domain since it belongs to the dying domain; and ownership can't
transfer to any other domain without hitting the BUG_ON() in
free_domheap_pages().

(steal_page() won't work on a page in this state, since it requires
PGC_allocated to be set, and PGC_allocated will already have been
cleared.)

Fix this by grabbing an extra type ref if setting PGT_partial in
relinquish_memory.

This is part of XSA-310.

Signed-off-by: George Dunlap <george.dunlap@citrix.com>
Acked-by: Jan Beulich <jbeulich@suse.com>
master commit: 66bdc16aee
master date: 2019-12-11 14:55:08 +0100
This commit is contained in:
George Dunlap authored and Jan Beulich committed 2019-12-11 15:59:39 +01:00
1 parent d46f8e0822
commit e60c7186b4
1 file changed
+19
+19
View File
@@ -2718,6 +2718,25 @@ static int relinquish_memory(
goto out;
case -ERESTART:
page_list_add(page, list);
/*
* PGT_partial holds a type ref and a general ref.
* If we came in with PGT_partial set, then we 1)
* don't need to grab an extra type count, and 2)
* do need to drop the extra page ref we grabbed
* at the top of the loop. If we didn't come in
* with PGT_partial set, we 1) do need to drab an
* extra type count, but 2) can transfer the page
* ref we grabbed above to it.
*
* Note that we must increment type_info before
* setting PGT_partial. Theoretically it should
* be safe to drop the page ref before setting
* PGT_partial, but do it afterwards just to be
* extra safe.
*/
if ( !(x & PGT_partial) )
page->u.inuse.type_info++;
smp_wmb();
page->u.inuse.type_info |= PGT_partial;
if ( x & PGT_partial )
put_page(page);
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.