Skip to contentSkip to conversation

mm/cma: don't release CMA pages still in use

RFC PATCH In review Rik van Riel 2 participants 2 messages Aug 11 – Aug 20

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

RFC PATCH v3 1/1 mm/cma: don't release CMA pages still in use

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. A subsequent allocation can hand 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.

Avoid the corruption by not freeing a CMA region if any of the pages inside are still in use.

Make it explicit in the warning that the driver allowed a leak.

---
v3:
- simplify things by simply leaking the CMA range, drivers should not call cma_release() while the space is still in use (David Hildenbrand)

 mm/cma.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)
mm/cma.c +6 -3
index a13ce4999b39..c1425f25c39b 100644
@@ -1018,7 +1018,7 @@ bool cma_release(struct cma *cma, const struct page *pages,
 		 unsigned long count)
 {
 	struct cma_memrange *cmr;
-	unsigned long ret = 0;
+	unsigned long leaked = 0;
 	unsigned long i, pfn;
 
 	cmr = find_cma_memrange(cma, pages, count);
@@ -1027,9 +1027,12 @@ bool cma_release(struct cma *cma, const struct page *pages,
 
 	pfn = page_to_pfn(pages);
 	for (i = 0; i < count; i++, pfn++)
-		ret += !put_page_testzero(pfn_to_page(pfn));
+		leaked += !put_page_testzero(pfn_to_page(pfn));
 
-	WARN(ret, "%lu pages are still in use!\n", ret);
+	if (leaked) {
+		WARN(1, "%lu pages are still in use, not freeing CMA region!\n", leaked);
+		return true;
+	}
 
 	__cma_release_frozen(cma, cmr, pages, count);
 
Signature · 1 line
2.55.0
#1 lore raw
Quoted 14 lines · Rik van Riel

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. A subsequent allocation can hand 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.

Avoid the corruption by not freeing a CMA region if any of the pages inside are still in use.

Quoted 6 lines

Make it explicit in the warning that the driver allowed a leak.

As discussed, I don't like stable here. Fixes is debatable (code was more robust before 9bda131c6093).

Code that actually triggers this is the problematic code, this here is rather making the code more robust for buggy drivers.

Quoted 27 lines

---
v3:
- simplify things by simply leaking the CMA range, drivers should not call cma_release() while the space is still in use (David Hildenbrand)

 mm/cma.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)
mm/cma.c +1 -2
index a13ce4999b39..c1425f25c39b 100644
@@ -1018,7 +1018,7 @@ bool cma_release(struct cma *cma, const struct page *pages,
 		 unsigned long count)
 {
 	struct cma_memrange *cmr;
-	unsigned long ret = 0;
+	unsigned long leaked = 0;
 	unsigned long i, pfn;
 
 	cmr = find_cma_memrange(cma, pages, count);
@@ -1027,9 +1027,12 @@ bool cma_release(struct cma *cma, const struct page *pages,
 
 	pfn = page_to_pfn(pages);
 	for (i = 0; i < count; i++, pfn++)
-		ret += !put_page_testzero(pfn_to_page(pfn));

mm/cma.c bool cma_release(struct cma *cma, const struct page *pages, · line 1038

Quoted 10 lines
mm/cma.c +5 -1
+		leaked += !put_page_testzero(pfn_to_page(pfn));
 
-	WARN(ret, "%lu pages are still in use!\n", ret);
+	if (leaked) {
+		WARN(1, "%lu pages are still in use, not freeing CMA region!\n", leaked);
+		return true;
+	}
 
 	__cma_release_frozen(cma, cmr, pages, count);
 

Okay, that will leave the frozen and the CMA bits remains set.

It's a bit inconsistent, because pages the buggy driver will release later will end up in the buddy, whereby pages we just freed will not end up in the buddy.

We could consider the following instead:

	leaked += !page_ref_freeze(pfn_to_page(pfn), 1);

(I hope I got that conditional right)

Then we will just consistently leak all pages (keeping them allocated with a refcount of 1).

Oh, and it nicely matches the "__cma_release_frozen" terminology later ;)

(on a related note, the code in general looks a bit shaky regarding speculative references, but we saw no reports so far ... it will all get clearer once more code is converted to use frozen pages so we won't even run into this)

Signature · 3 lines
Cheers,

David