diff options
| author | Dmitry Antipov <dmantipov@yandex.ru> | 2026-05-08 14:13:29 +0300 |
|---|---|---|
| committer | Andrew Morton <akpm@linux-foundation.org> | 2026-05-28 21:32:21 -0700 |
| commit | c6b4edd2474051bddbb7604987e99fbfbee3f2ea (patch) | |
| tree | 34ed92b701950f921ea8b54f104bb3f5efba17fd /lib | |
| parent | 8b6270dcbcd3443d4608b81329abb55078ae3ded (diff) | |
| download | linux-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.c | 54 |
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); |
