collapse_file() uses xas_create_range() in a retry loop that calls xas_nomem() on -ENOMEM and then retries. xas_nomem() may allocate a spare xa_node and store it in xas->xa_alloc. If the lock is dropped after xas_nomem(), another thread can expand the xarray tree in the meantime. On the next retry, xas_create_range() can then succeed trivially without consuming the node stored in xas->xa_alloc. If we then either succeed or give up and go to the rollback path without calling xas_destroy(), that spare node leaks. Fix this by calling xas_destroy(&xas) in both the success case (!xas_error(&xas)) and the failure case where xas_nomem() returns false and we abort. xas_destroy() will free any unused spare node in xas->xa_alloc and is a no-op if there is nothing left to free. Link: https://syzkaller.appspot.com/bug?id=a274d65fc733448ed518ad15481ed575669dd98c Fixes: cae106dd67b9 ("mm/khugepaged: refactor collapse_file control flow") Signed-off-by: Shardul Bankar --- v2: - Call xas_destroy() on both success and failure - Explained retry semantics and xa_alloc / concurrency risk - Dropped cleanup_empty_nodes from previous proposal mm/khugepaged.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mm/khugepaged.c b/mm/khugepaged.c index abe54f0043c7..0794a99c807f 100644 --- a/mm/khugepaged.c +++ b/mm/khugepaged.c @@ -1872,11 +1872,14 @@ static int collapse_file(struct mm_struct *mm, unsigned long addr, do { xas_lock_irq(&xas); xas_create_range(&xas); - if (!xas_error(&xas)) + if (!xas_error(&xas)) { + xas_destroy(&xas); break; + } xas_unlock_irq(&xas); if (!xas_nomem(&xas, GFP_KERNEL)) { result = SCAN_FAIL; + xas_destroy(&xas); goto rollback; } } while (1); -- 2.34.1