mm/cma: don't release CMA pages still in use
Subsystems (2)
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
@@ -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
David Hildenbrand (Arm)
maintainerQuoted 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 tofree_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 andfree_contig_frozen_range()can double-queue the page viapage->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.
Fixes 9bda131c6093 ("mm: cma: add cma_alloc_frozen{_compound}()")
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
Signed-off-by Rik van Riel <[email protected]>---
v3:
- simplify things by simply leaking the CMA range, drivers should not callcma_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@@ -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