From ae5eb4f8660cdf3dcdc874b15a4bd841465f46e4 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Fri, 13 Nov 2026 14:34:16 -0800 Subject: [PATCH 1/2] xen/balloon: deflate from the OOM notifier without reclaiming out_of_memory() runs the OOM notifier chain with oom_lock held. On a PV domU the balloon's notifier deflates through balloon_deflate() -> xenmem_reservation_va_mapping_update() -> set_phys_to_machine(), and when the page's p2m leaf is still the shared p2m_missing page that allocates the leaf with a GFP_KERNEL __get_free_page(). Reclaim has already failed, so that allocation goes to __alloc_pages_may_oom(), whose mutex_trylock(&oom_lock) fails because we hold it; it counts that as progress, sleeps a jiffy and retries, forever. The OOM killer never runs and the guest hangs instead of killing a process. Pages with a missing leaf sit on the balloon in any PV domU built with maxmem above memory and without the unpopulated allocator, whose extra memory regions are appended at boot, and in one that hotplugged memory it has not populated yet. PVH and HVM guests are not affected, as the va-mapping update is a no-op there, nor is dom0, which does not register the notifier. Deflate under memalloc_noreclaim_save(): with PF_MEMALLOC set the allocator neither reclaims nor enters the OOM path, so the leaf either comes from the reserves or fails, and set_phys_to_machine() then fails the page cleanly. A leaf taken from the reserves lets 512 pages come back, so a memory-freeing context is the right one for this work. Signed-off-by: Ariadne Conill --- drivers/xen/balloon.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/drivers/xen/balloon.c b/drivers/xen/balloon.c index a12930a5969683..8a782143d16ca9 100644 --- a/drivers/xen/balloon.c +++ b/drivers/xen/balloon.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -1079,8 +1080,24 @@ static int balloon_oom_notify(struct notifier_block *nb, unsigned long dummy, before = balloon_stats.current_pages; /* runs XENMEM_populate_physmap, updates current_pages */ - if (nr > 0) + if (nr > 0) { + unsigned int noreclaim_flag; + + /* + * out_of_memory() calls us with oom_lock held. On PV, + * populating a page whose p2m leaf is still the shared missing + * page allocates the leaf with GFP_KERNEL; with reclaim already + * beaten, that allocation would try the OOM killer, find + * oom_lock taken, count that as progress and retry forever, + * and the OOM killer would never run. Run the deflate as a + * memory-freeing context: no reclaim, no OOM, and the reserves + * may be dipped into, which a leaf that lets 512 pages come + * back repays at once. + */ + noreclaim_flag = memalloc_noreclaim_save(); balloon_deflate(nr); + memalloc_noreclaim_restore(noreclaim_flag); + } /* * balloon_deflate() bumped current_pages by however many Xen From 8b9c0b001a6259d326711bc9dbfebe62c9528790 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Fri, 13 Nov 2026 14:34:16 -0800 Subject: [PATCH 2/2] xen/gntdev: check each unmap's status before freeing relocated placeholders gntdev_relocate_map() frees the old placeholder pages once gnttab_unmap_refs() returns 0, but that function returns 0 whenever the hypercall itself succeeded, whether or not every unmap_op in the batch was honoured; a refused op reports its failure in its own status and leaves its grant mapped. A page whose frame still carries a foreign mapping then goes back to the unpopulated pool, and the next use of that gfn has Xen crash the domain. Treat any op with a status other than GNTST_okay, for the kernel unmap ops too where they exist, as the failure it is, so the existing path that leaks the old pages and fails the map handles it. Signed-off-by: Ariadne Conill --- drivers/xen/gntdev.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/xen/gntdev.c b/drivers/xen/gntdev.c index 579e9104efa375..b0024c58f92ca0 100644 --- a/drivers/xen/gntdev.c +++ b/drivers/xen/gntdev.c @@ -384,6 +384,16 @@ static int gntdev_relocate_map(struct gntdev_grant_map *map) } err = gnttab_unmap_refs(map->unmap_ops, map->kunmap_ops, map->pages, map->count); + /* + * A successful hypercall only says the batch was processed; each op + * reports its own fate, and a refused one leaves its grant mapped. + */ + for (i = 0; !err && i < map->count; i++) { + if (map->unmap_ops[i].status != GNTST_okay || + (map->kunmap_ops && + map->kunmap_ops[i].status != GNTST_okay)) + err = -EIO; + } if (err) { /* * The old placeholders may still carry live foreign