| From 114de38225d9b300f027e2aec9afbb6e0def154b Mon Sep 17 00:00:00 2001 |
| From: Trond Myklebust <trondmy@gmail.com> |
| Date: Sun, 2 Feb 2020 17:53:54 -0500 |
| Subject: NFS: Directory page cache pages need to be locked when read |
| |
| From: Trond Myklebust <trondmy@gmail.com> |
| |
| commit 114de38225d9b300f027e2aec9afbb6e0def154b upstream. |
| |
| When a NFS directory page cache page is removed from the page cache, |
| its contents are freed through a call to nfs_readdir_clear_array(). |
| To prevent the removal of the page cache entry until after we've |
| finished reading it, we must take the page lock. |
| |
| Fixes: 11de3b11e08c ("NFS: Fix a memory leak in nfs_readdir") |
| Cc: stable@vger.kernel.org # v2.6.37+ |
| Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com> |
| Reviewed-by: Benjamin Coddington <bcodding@redhat.com> |
| Signed-off-by: Anna Schumaker <Anna.Schumaker@Netapp.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/nfs/dir.c | 30 +++++++++++++++++++----------- |
| 1 file changed, 19 insertions(+), 11 deletions(-) |
| |
| --- a/fs/nfs/dir.c |
| +++ b/fs/nfs/dir.c |
| @@ -701,8 +701,6 @@ int nfs_readdir_filler(nfs_readdir_descr |
| static |
| void cache_page_release(nfs_readdir_descriptor_t *desc) |
| { |
| - if (!desc->page->mapping) |
| - nfs_readdir_clear_array(desc->page); |
| put_page(desc->page); |
| desc->page = NULL; |
| } |
| @@ -716,19 +714,28 @@ struct page *get_cache_page(nfs_readdir_ |
| |
| /* |
| * Returns 0 if desc->dir_cookie was found on page desc->page_index |
| + * and locks the page to prevent removal from the page cache. |
| */ |
| static |
| -int find_cache_page(nfs_readdir_descriptor_t *desc) |
| +int find_and_lock_cache_page(nfs_readdir_descriptor_t *desc) |
| { |
| int res; |
| |
| desc->page = get_cache_page(desc); |
| if (IS_ERR(desc->page)) |
| return PTR_ERR(desc->page); |
| - |
| - res = nfs_readdir_search_array(desc); |
| + res = lock_page_killable(desc->page); |
| if (res != 0) |
| - cache_page_release(desc); |
| + goto error; |
| + res = -EAGAIN; |
| + if (desc->page->mapping != NULL) { |
| + res = nfs_readdir_search_array(desc); |
| + if (res == 0) |
| + return 0; |
| + } |
| + unlock_page(desc->page); |
| +error: |
| + cache_page_release(desc); |
| return res; |
| } |
| |
| @@ -743,7 +750,7 @@ int readdir_search_pagecache(nfs_readdir |
| desc->last_cookie = 0; |
| } |
| do { |
| - res = find_cache_page(desc); |
| + res = find_and_lock_cache_page(desc); |
| } while (res == -EAGAIN); |
| return res; |
| } |
| @@ -782,7 +789,6 @@ int nfs_do_filldir(nfs_readdir_descripto |
| desc->eof = true; |
| |
| kunmap(desc->page); |
| - cache_page_release(desc); |
| dfprintk(DIRCACHE, "NFS: nfs_do_filldir() filling ended @ cookie %Lu; returning = %d\n", |
| (unsigned long long)*desc->dir_cookie, res); |
| return res; |
| @@ -828,13 +834,13 @@ int uncached_readdir(nfs_readdir_descrip |
| |
| status = nfs_do_filldir(desc); |
| |
| + out_release: |
| + nfs_readdir_clear_array(desc->page); |
| + cache_page_release(desc); |
| out: |
| dfprintk(DIRCACHE, "NFS: %s: returns %d\n", |
| __func__, status); |
| return status; |
| - out_release: |
| - cache_page_release(desc); |
| - goto out; |
| } |
| |
| /* The file offset position represents the dirent entry number. A |
| @@ -899,6 +905,8 @@ static int nfs_readdir(struct file *file |
| break; |
| |
| res = nfs_do_filldir(desc); |
| + unlock_page(desc->page); |
| + cache_page_release(desc); |
| if (res < 0) |
| break; |
| } while (!desc->eof); |