| From: Axel Rasmussen <axelrasmussen@google.com> |
| Subject: mm: userfaultfd: fix UFFDIO_CONTINUE on fallocated shmem pages |
| Date: Fri, 10 Jun 2022 10:38:12 -0700 |
| |
| When fallocate() is used on a shmem file, the pages we allocate can end up |
| with !PageUptodate. |
| |
| Since UFFDIO_CONTINUE tries to find the existing page the user wants to |
| map with SGP_READ, we would fail to find such a page, since |
| shmem_getpage_gfp returns with a "NULL" pagep for SGP_READ if it discovers |
| !PageUptodate. As a result, UFFDIO_CONTINUE returns -EFAULT, as it would |
| do if the page wasn't found in the page cache at all. |
| |
| This isn't the intended behavior. UFFDIO_CONTINUE is just trying to find |
| if a page exists, and doesn't care whether it still needs to be cleared or |
| not. So, instead of SGP_READ, pass in SGP_NOALLOC. This is the same, |
| except for one critical difference: in the !PageUptodate case, SGP_NOALLOC |
| will clear the page and then return it. With this change, UFFDIO_CONTINUE |
| works properly (succeeds) on a shmem file which has been fallocated, but |
| otherwise not modified. |
| |
| Link: https://lkml.kernel.org/r/20220610173812.1768919-1-axelrasmussen@google.com |
| Fixes: 153132571f02 ("userfaultfd/shmem: support UFFDIO_CONTINUE for shmem") |
| Signed-off-by: Axel Rasmussen <axelrasmussen@google.com> |
| Acked-by: Peter Xu <peterx@redhat.com> |
| Cc: Hugh Dickins <hughd@google.com> |
| Cc: <stable@vger.kernel.org> |
| Signed-off-by: Andrew Morton <akpm@linux-foundation.org> |
| --- |
| |
| mm/userfaultfd.c | 5 ++++- |
| 1 file changed, 4 insertions(+), 1 deletion(-) |
| |
| --- a/mm/userfaultfd.c~mm-userfaultfd-fix-uffdio_continue-on-fallocated-shmem-pages |
| +++ a/mm/userfaultfd.c |
| @@ -246,7 +246,10 @@ static int mcontinue_atomic_pte(struct m |
| struct page *page; |
| int ret; |
| |
| - ret = shmem_getpage(inode, pgoff, &page, SGP_READ); |
| + ret = shmem_getpage(inode, pgoff, &page, SGP_NOALLOC); |
| + /* Our caller expects us to return -EFAULT if we failed to find page. */ |
| + if (ret == -ENOENT) |
| + ret = -EFAULT; |
| if (ret) |
| goto out; |
| if (!page) { |
| _ |