Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [RFC v2 PATCH] mm/cma: don't release CMA pages still in use
@ 2026-08-10 16:27 Rik van Riel
  2026-08-10 18:53 ` David Hildenbrand (Arm)
  0 siblings, 1 reply; 2+ messages in thread
From: Rik van Riel @ 2026-08-10 16:27 UTC (permalink / raw)
  To: Andrew Morton
  Cc: David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
	Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
	Chris Mason, linux-mm, linux-kernel

When a driver calls dma_free_contiguous() before quiescing DMA, the
page still has a reference from the device. put_page_testzero() there
returns false, WARN fires, but the code proceeds to
free_contig_frozen_range() putting a live page onto buddy and clearing
the bitmap. Later cma_alloc() hands the same PFN to a new owner while
the original holder still references it.

A concurrent put_page() that drops the last reference between the
testzero loop and free_contig_frozen_range() can double-queue the page
via page->lru, corrupting buddy lists.

Fix by freeing already-frozen pages in contiguous runs, while keeping
still-referenced pages. When any live page exists, a future cma_alloc()
on this address range will fail until whoever holds the extra references
frees those pages.

This change should be safe because nothing can get reallocated while it
is still in use.

Fixes: 9bda131c6093 ("mm: cma: add cma_alloc_frozen{_compound}()")
Cc: stable@vger.kernel.org
Assisted-by: Hermes:muse-spark-1.2 syzkaller
Reported-by: Chris Mason <clm@meta.com>
Signed-off-by: Rik van Riel <riel@surriel.com>
---
v2:
 - don't play clever games with the CMA bitmap, the page allocator
   alone will prevent re-use of not-free-yet pages
 - v1: https://lore.kernel.org/all/20260809210608.06b5ccb9@fangorn/

 mm/cma.c | 39 +++++++++++++++++++++++++++++++++------
 1 file changed, 33 insertions(+), 6 deletions(-)

diff --git a/mm/cma.c b/mm/cma.c
index a13ce4999b39..cbf8dba8f077 100644
--- a/mm/cma.c
+++ b/mm/cma.c
@@ -998,7 +998,6 @@ static void __cma_release_frozen(struct cma *cma, struct cma_memrange *cmr,
 
 	pr_debug("%s(page %p, count %lu)\n", __func__, (void *)pages, count);
 
-	free_contig_frozen_range(pfn, count);
 	cma_clear_bitmap(cma, cmr, pfn, count);
 	cma_sysfs_account_release_pages(cma, count);
 	trace_cma_release(cma->name, pfn, pages, count);
@@ -1018,18 +1017,45 @@ bool cma_release(struct cma *cma, const struct page *pages,
 		 unsigned long count)
 {
 	struct cma_memrange *cmr;
-	unsigned long ret = 0;
+	unsigned long skipped = 0;
 	unsigned long i, pfn;
+	unsigned long base_pfn;
+	unsigned long run_start = 0;
+	unsigned long run_len = 0;
 
 	cmr = find_cma_memrange(cma, pages, count);
 	if (!cmr)
 		return false;
 
-	pfn = page_to_pfn(pages);
-	for (i = 0; i < count; i++, pfn++)
-		ret += !put_page_testzero(pfn_to_page(pfn));
+	base_pfn = page_to_pfn(pages);
+	pfn = base_pfn;
+	for (i = 0; i < count; i++, pfn++) {
+		if (put_page_testzero(pfn_to_page(pfn))) {
+			/* Add it to the batch. */
+			if (run_len == 0)
+				run_start = pfn;
+			run_len++;
+		} else {
+			/*
+			 * This page is still in use! Free the freeable
+			 * pages encountered so far, but skip this page.
+			 */
+			if (run_len) {
+				free_contig_frozen_range(run_start, run_len);
+				run_len = 0;
+			}
+			skipped++;
+		}
+	}
+	if (run_len)
+		free_contig_frozen_range(run_start, run_len);
 
-	WARN(ret, "%lu pages are still in use!\n", ret);
+	/*
+	 * Some pages were still in use! This should not happen.
+	 * Subsequent cma_alloc() calls to the same range will fail
+	 * until whoever grabbed the extra refcounts frees the pages.
+	 */
+	WARN(skipped, "%lu pages are still in use!\n", skipped);
 
 	__cma_release_frozen(cma, cmr, pages, count);
 
@@ -1046,6 +1072,7 @@ bool cma_release_frozen(struct cma *cma, const struct page *pages,
 	if (!cmr)
 		return false;
 
+	free_contig_frozen_range(page_to_pfn(pages), count);
 	__cma_release_frozen(cma, cmr, pages, count);
 
 	return true;
-- 
2.55.0




^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [RFC v2 PATCH] mm/cma: don't release CMA pages still in use
  2026-08-10 16:27 [RFC v2 PATCH] mm/cma: don't release CMA pages still in use Rik van Riel
@ 2026-08-10 18:53 ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 2+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-10 18:53 UTC (permalink / raw)
  To: Rik van Riel, Andrew Morton
  Cc: Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Mason, linux-mm,
	linux-kernel

On 8/10/26 18:27, Rik van Riel wrote:
> When a driver calls dma_free_contiguous() before quiescing DMA, the
> page still has a reference from the device. put_page_testzero() there
> returns false, WARN fires, but the code proceeds to
> free_contig_frozen_range() putting a live page onto buddy and clearing
> the bitmap. Later cma_alloc() hands the same PFN to a new owner while
> the original holder still references it.

Hm, it rather sounds like it's really the drivers job to quiescing DMA. Are you
aware of an in-tree driver that can trigger this, and if so, how?

Freeing memory through CMA, while the memory is still in use is problematic for
CMA as you discovered :)

> 
> A concurrent put_page() that drops the last reference between the
> testzero loop and free_contig_frozen_range() can double-queue the page
> via page->lru, corrupting buddy lists.
> 
> Fix by freeing already-frozen pages in contiguous runs, while keeping
> still-referenced pages. When any live page exists, a future cma_alloc()
> on this address range will fail until whoever holds the extra references
> frees those pages.
> 
> This change should be safe because nothing can get reallocated while it
> is still in use.
> 
> Fixes: 9bda131c6093 ("mm: cma: add cma_alloc_frozen{_compound}()")
> Cc: stable@vger.kernel.org
> Assisted-by: Hermes:muse-spark-1.2 syzkaller
> Reported-by: Chris Mason <clm@meta.com>
> Signed-off-by: Rik van Riel <riel@surriel.com>
> ---
> v2:
>  - don't play clever games with the CMA bitmap, the page allocator
>    alone will prevent re-use of not-free-yet pages
>  - v1: https://lore.kernel.org/all/20260809210608.06b5ccb9@fangorn/
> 
>  mm/cma.c | 39 +++++++++++++++++++++++++++++++++------
>  1 file changed, 33 insertions(+), 6 deletions(-)
> 
> diff --git a/mm/cma.c b/mm/cma.c
> index a13ce4999b39..cbf8dba8f077 100644
> --- a/mm/cma.c
> +++ b/mm/cma.c
> @@ -998,7 +998,6 @@ static void __cma_release_frozen(struct cma *cma, struct cma_memrange *cmr,
>  
>  	pr_debug("%s(page %p, count %lu)\n", __func__, (void *)pages, count);
>  
> -	free_contig_frozen_range(pfn, count);
>  	cma_clear_bitmap(cma, cmr, pfn, count);
>  	cma_sysfs_account_release_pages(cma, count);
>  	trace_cma_release(cma->name, pfn, pages, count);
> @@ -1018,18 +1017,45 @@ bool cma_release(struct cma *cma, const struct page *pages,
>  		 unsigned long count)
>  {
>  	struct cma_memrange *cmr;
> -	unsigned long ret = 0;
> +	unsigned long skipped = 0;
>  	unsigned long i, pfn;
> +	unsigned long base_pfn;
> +	unsigned long run_start = 0;
> +	unsigned long run_len = 0;
>  
>  	cmr = find_cma_memrange(cma, pages, count);
>  	if (!cmr)
>  		return false;
>  
> -	pfn = page_to_pfn(pages);
> -	for (i = 0; i < count; i++, pfn++)
> -		ret += !put_page_testzero(pfn_to_page(pfn));
> +	base_pfn = page_to_pfn(pages);
> +	pfn = base_pfn;
> +	for (i = 0; i < count; i++, pfn++) {
> +		if (put_page_testzero(pfn_to_page(pfn))) {
> +			/* Add it to the batch. */
> +			if (run_len == 0)
> +				run_start = pfn;
> +			run_len++;
> +		} else {
> +			/*
> +			 * This page is still in use! Free the freeable
> +			 * pages encountered so far, but skip this page.
> +			 */
> +			if (run_len) {
> +				free_contig_frozen_range(run_start, run_len);
> +				run_len = 0;
> +			}
> +			skipped++;
> +		}
> +	}
> +	if (run_len)
> +		free_contig_frozen_range(run_start, run_len);
>  
> -	WARN(ret, "%lu pages are still in use!\n", ret);
> +	/*
> +	 * Some pages were still in use! This should not happen.
> +	 * Subsequent cma_alloc() calls to the same range will fail
> +	 * until whoever grabbed the extra refcounts frees the pages.
> +	 */
> +	WARN(skipped, "%lu pages are still in use!\n", skipped);


We still issue a WARN, which itself is problematic. See "Do not WARN lightly" in
Documentation/process/coding-style.rst.

So it's either

(a) This scenario is valid to be triggered. In that case, WARN is not appropriate.

(b) This scenario is not valid to be triggered. In that case, this is not a fix.

-- 
Cheers,

David


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-08-10 18:53 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-10 16:27 [RFC v2 PATCH] mm/cma: don't release CMA pages still in use Rik van Riel
2026-08-10 18:53 ` David Hildenbrand (Arm)

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox