When a guest maps pages via XENMEM_add_to_physmap to a GFN that already has an existing mapping, the old page at that GFN was not being removed, causing a memory leak. This affects all mapping spaces including XENMAPSPACE_shared_info, XENMAPSPACE_grant_table, and XENMAPSPACE_gmfn_foreign. The memory would be reclaimed on domain destruction.
Add logic to remove the previously mapped page before creating the new mapping, matching the x86 implementation approach. Additionally, skip removal if the same MFN is being remapped. Signed-off-by: Michal Orzel <[email protected]> --- I'm not sure where to point the Fixes tag to. --- xen/arch/arm/mm.c | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/xen/arch/arm/mm.c b/xen/arch/arm/mm.c index 6df8b616e464..b9f1a493dcd7 100644 --- a/xen/arch/arm/mm.c +++ b/xen/arch/arm/mm.c @@ -166,10 +166,11 @@ int xenmem_add_to_physmap_one( unsigned long idx, gfn_t gfn) { - mfn_t mfn = INVALID_MFN; + mfn_t mfn = INVALID_MFN, mfn_old; int rc; p2m_type_t t; struct page_info *page = NULL; + struct p2m_domain *p2m = p2m_get_hostp2m(d); switch ( space ) { @@ -244,6 +245,33 @@ int xenmem_add_to_physmap_one( return -ENOSYS; } + /* + * Remove previously mapped page if it was present, to avoid leaking + * memory. + */ + mfn_old = gfn_to_mfn(d, gfn); + + if ( mfn_valid(mfn_old) ) + { + if ( is_special_page(mfn_to_page(mfn_old)) ) + { + /* Just unmap, don't free */ + p2m_write_lock(p2m); + rc = p2m_set_entry(p2m, gfn, 1, INVALID_MFN, + p2m_invalid, p2m->default_access); + p2m_write_unlock(p2m); + if ( rc ) + return rc; + } + else if ( !mfn_eq(mfn, mfn_old) ) + { + /* Normal domain memory is freed, to avoid leaking memory */ + rc = guest_remove_page(d, gfn_x(gfn)); + if ( rc ) + return rc; + } + } + /* * Map at new location. Here we need to map xenheap RAM page differently * because we need to store the valid GFN and make sure that nothing was @@ -255,8 +283,6 @@ int xenmem_add_to_physmap_one( rc = guest_physmap_add_entry(d, gfn, mfn, 0, t); else { - struct p2m_domain *p2m = p2m_get_hostp2m(d); - p2m_write_lock(p2m); if ( gfn_eq(page_get_xenheap_gfn(mfn_to_page(mfn)), INVALID_GFN) ) { -- 2.43.0
