| From 9fa248c65bdbf5af0a2f74dd38575acfc8dfd2bf Mon Sep 17 00:00:00 2001 |
| From: Miklos Szeredi <mszeredi@redhat.com> |
| Date: Thu, 20 Oct 2022 17:18:58 +0200 |
| Subject: fuse: fix readdir cache race |
| |
| From: Miklos Szeredi <mszeredi@redhat.com> |
| |
| commit 9fa248c65bdbf5af0a2f74dd38575acfc8dfd2bf upstream. |
| |
| There's a race in fuse's readdir cache that can result in an uninitilized |
| page being read. The page lock is supposed to prevent this from happening |
| but in the following case it doesn't: |
| |
| Two fuse_add_dirent_to_cache() start out and get the same parameters |
| (size=0,offset=0). One of them wins the race to create and lock the page, |
| after which it fills in data, sets rdc.size and unlocks the page. |
| |
| In the meantime the page gets evicted from the cache before the other |
| instance gets to run. That one also creates the page, but finds the |
| size to be mismatched, bails out and leaves the uninitialized page in the |
| cache. |
| |
| Fix by marking a filled page uptodate and ignoring non-uptodate pages. |
| |
| Reported-by: Frank Sorenson <fsorenso@redhat.com> |
| Fixes: 5d7bc7e8680c ("fuse: allow using readdir cache") |
| Cc: <stable@vger.kernel.org> # v4.20 |
| Signed-off-by: Miklos Szeredi <mszeredi@redhat.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| fs/fuse/readdir.c | 10 +++++++++- |
| 1 file changed, 9 insertions(+), 1 deletion(-) |
| |
| --- a/fs/fuse/readdir.c |
| +++ b/fs/fuse/readdir.c |
| @@ -77,8 +77,10 @@ static void fuse_add_dirent_to_cache(str |
| goto unlock; |
| |
| addr = kmap_local_page(page); |
| - if (!offset) |
| + if (!offset) { |
| clear_page(addr); |
| + SetPageUptodate(page); |
| + } |
| memcpy(addr + offset, dirent, reclen); |
| kunmap_local(addr); |
| fi->rdc.size = (index << PAGE_SHIFT) + offset + reclen; |
| @@ -516,6 +518,12 @@ retry_locked: |
| |
| page = find_get_page_flags(file->f_mapping, index, |
| FGP_ACCESSED | FGP_LOCK); |
| + /* Page gone missing, then re-added to cache, but not initialized? */ |
| + if (page && !PageUptodate(page)) { |
| + unlock_page(page); |
| + put_page(page); |
| + page = NULL; |
| + } |
| spin_lock(&fi->rdc.lock); |
| if (!page) { |
| /* |