aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
authorDmitry Antipov <dmantipov@yandex.ru>2026-05-08 14:13:29 +0300
committerAndrew Morton <akpm@linux-foundation.org>2026-05-28 21:32:21 -0700
commitc6b4edd2474051bddbb7604987e99fbfbee3f2ea (patch)
tree34ed92b701950f921ea8b54f104bb3f5efba17fd /lib
parent8b6270dcbcd3443d4608b81329abb55078ae3ded (diff)
downloadlinux-next-history-c6b4edd2474051bddbb7604987e99fbfbee3f2ea.tar.gz
lib: free pagelist on error in iov_iter_extract_pages()
Since 'iov_iter_extract_pages()' may allocate new pagelist if the passed one isn't large enough, the worst-case scenario may be: ... struct page *stack_pages[SMALL]; struct page **pages = stack_pages; ... if (iov_iter_extract_pages(i..., &pages, ...) <= 0) { /* Even in case of error, new pagelist may be allocated */ if (pages != stack_pages) kvfree(pages); [1] /* The rest of error handling and return */ } /* Regular flow */ ... if (pages != stack_pages) kvfree(pages); ... return 0; If you're unlucky so SMALL amount of pages wasn't enough and new pagelist was allocated, missing [1] causes the memory leak similar to one I've recently observed and fixed for 6.12 in [2]. So adjust 'iov_iter_extract_pages()' to make such a cleanup itself rather than rely on caller's handling on error paths, thus making [1] not needed. [2] https://lore.kernel.org/stable/20260505094529.406783-1-dmantipov@yandex.ru/T/#u Link: https://lore.kernel.org/20260508111329.329943-1-dmantipov@yandex.ru Signed-off-by: Dmitry Antipov <dmantipov@yandex.ru> Suggested-by: Fedor Pchelkin <pchelkin@ispras.ru> Cc: Christoph Hellwig <hch@lst.de> Cc: Jens Axboe <axboe@kernel.dk> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Diffstat (limited to 'lib')
-rw-r--r--lib/iov_iter.c54
1 files changed, 33 insertions, 21 deletions
diff --git a/lib/iov_iter.c b/lib/iov_iter.c
index 243662af1af73..46dd11913df08 100644
--- a/lib/iov_iter.c
+++ b/lib/iov_iter.c
@@ -1807,7 +1807,8 @@ static ssize_t iov_iter_extract_user_pages(struct iov_iter *i,
* (*) Use with ITER_DISCARD is not supported as that has no content.
*
* On success, the function sets *@pages to the new pagelist, if allocated, and
- * sets *offset0 to the offset into the first page.
+ * sets *offset0 to the offset into the first page. On error, new pagelist
+ * is freed if was allocated, and *@pages sets back to its original value.
*
* It may also return -ENOMEM and -EFAULT.
*/
@@ -1818,31 +1819,42 @@ ssize_t iov_iter_extract_pages(struct iov_iter *i,
iov_iter_extraction_t extraction_flags,
size_t *offset0)
{
+ struct page **oldpages = *pages;
+ int ret;
+
maxsize = min_t(size_t, min_t(size_t, maxsize, i->count), MAX_RW_COUNT);
if (!maxsize)
return 0;
if (likely(user_backed_iter(i)))
- return iov_iter_extract_user_pages(i, pages, maxsize,
- maxpages, extraction_flags,
- offset0);
- if (iov_iter_is_kvec(i))
- return iov_iter_extract_kvec_pages(i, pages, maxsize,
- maxpages, extraction_flags,
- offset0);
- if (iov_iter_is_bvec(i))
- return iov_iter_extract_bvec_pages(i, pages, maxsize,
- maxpages, extraction_flags,
- offset0);
- if (iov_iter_is_folioq(i))
- return iov_iter_extract_folioq_pages(i, pages, maxsize,
- maxpages, extraction_flags,
- offset0);
- if (iov_iter_is_xarray(i))
- return iov_iter_extract_xarray_pages(i, pages, maxsize,
- maxpages, extraction_flags,
- offset0);
- return -EFAULT;
+ ret = iov_iter_extract_user_pages(i, pages, maxsize,
+ maxpages, extraction_flags,
+ offset0);
+ else if (iov_iter_is_kvec(i))
+ ret = iov_iter_extract_kvec_pages(i, pages, maxsize,
+ maxpages, extraction_flags,
+ offset0);
+ else if (iov_iter_is_bvec(i))
+ ret = iov_iter_extract_bvec_pages(i, pages, maxsize,
+ maxpages, extraction_flags,
+ offset0);
+ else if (iov_iter_is_folioq(i))
+ ret = iov_iter_extract_folioq_pages(i, pages, maxsize,
+ maxpages, extraction_flags,
+ offset0);
+ else if (iov_iter_is_xarray(i))
+ ret = iov_iter_extract_xarray_pages(i, pages, maxsize,
+ maxpages, extraction_flags,
+ offset0);
+ else
+ ret = -EFAULT;
+
+ if (unlikely(ret) && *pages && *pages != oldpages) {
+ kvfree(*pages);
+ *pages = oldpages;
+ }
+
+ return ret;
}
EXPORT_SYMBOL_GPL(iov_iter_extract_pages);