From a507023e6ff1d0b4e7aac49b2f547fed0c21ea4e Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Wed, 29 Jul 2026 21:16:29 -0700 Subject: [PATCH] net/rds: unpin MR pages with unpin_user_pages_dirty_lock() The pages backing an RDS memory region are pinned in __rds_rdma_map() with rds_pin_pages(), which uses pin_user_pages_fast(): each page's refcount is biased by GUP_PIN_COUNTING_BIAS to account the pin. The scatterlist is then handed to the IB transport, and the transport releases the pages in __rds_ib_teardown_mr() with set_page_dirty(page); put_page(page); put_page() drops a single reference instead of removing the pin bias, so every MR teardown permanently strands the remaining references and the pages are never freed - a userspace-triggerable memory leak of up to RDS_MAX_MSG_SIZE per RDS_GET_MR/RDS_GET_MR_FOR_DEST call. The conversion to the pin API updated the unpin sites in rdma.c but missed this one on the transport side. Release the pages with unpin_user_pages_dirty_lock(), which removes the pin bias and also dirties the page under the folio lock, closing the truncation race that a bare set_page_dirty() leaves open. Dirtying under the folio lock can sleep, which is safe in every path that reaches __rds_ib_teardown_mr(): the registration-reuse path (rds_ib_map_frmr()) runs in syscall context, and the pool flush (rds_ib_unreg_frmr()) runs under pool->flush_lock, a mutex, and already sleeps in rds_ib_post_inv(). The WARN_ON that guarded the old irq-context set_page_dirty() case is dropped along with it. Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260730041629.3512480-5-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/ib_rdma.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/net/rds/ib_rdma.c b/net/rds/ib_rdma.c index 9594ea245f7f..db7e92e7bd29 100644 --- a/net/rds/ib_rdma.c +++ b/net/rds/ib_rdma.c @@ -251,9 +251,7 @@ void __rds_ib_teardown_mr(struct rds_ib_mr *ibmr) /* FIXME we need a way to tell a r/w MR * from a r/o MR */ - WARN_ON(!page->mapping && irqs_disabled()); - set_page_dirty(page); - put_page(page); + unpin_user_pages_dirty_lock(&page, 1, true); } kfree(ibmr->sg);