Merge tag 'gfs2-v5.15-rc5-mmap-fault' of git://git.kernel.org/pub/scm/linux/kernel...
authorLinus Torvalds <torvalds@linux-foundation.org>
Tue, 2 Nov 2021 19:25:03 +0000 (12:25 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 2 Nov 2021 19:25:03 +0000 (12:25 -0700)
Pull gfs2 mmap + page fault deadlocks fixes from Andreas Gruenbacher:
 "Functions gfs2_file_read_iter and gfs2_file_write_iter are both
  accessing the user buffer to write to or read from while holding the
  inode glock.

  In the most basic deadlock scenario, that buffer will not be resident
  and it will be mapped to the same file. Accessing the buffer will
  trigger a page fault, and gfs2 will deadlock trying to take the same
  inode glock again while trying to handle that fault.

  Fix that and similar, more complex scenarios by disabling page faults
  while accessing user buffers. To make this work, introduce a small
  amount of new infrastructure and fix some bugs that didn't trigger so
  far, with page faults enabled"

* tag 'gfs2-v5.15-rc5-mmap-fault' of git://git.kernel.org/pub/scm/linux/kernel/git/gfs2/linux-gfs2:
  gfs2: Fix mmap + page fault deadlocks for direct I/O
  iov_iter: Introduce nofault flag to disable page faults
  gup: Introduce FOLL_NOFAULT flag to disable page faults
  iomap: Add done_before argument to iomap_dio_rw
  iomap: Support partial direct I/O on user copy failures
  iomap: Fix iomap_dio_rw return value for user copies
  gfs2: Fix mmap + page fault deadlocks for buffered I/O
  gfs2: Eliminate ip->i_gh
  gfs2: Move the inode glock locking to gfs2_file_buffered_write
  gfs2: Introduce flag for glock holder auto-demotion
  gfs2: Clean up function may_grant
  gfs2: Add wrapper for iomap_file_buffered_write
  iov_iter: Introduce fault_in_iov_iter_writeable
  iov_iter: Turn iov_iter_fault_in_readable into fault_in_iov_iter_readable
  gup: Turn fault_in_pages_{readable,writeable} into fault_in_{readable,writeable}
  powerpc/kvm: Fix kvm_use_magic_page
  iov_iter: Fix iov_iter_get_pages{,_alloc} page fault return value

16 files changed:
1  2 
arch/x86/kernel/fpu/signal.c
fs/btrfs/file.c
fs/btrfs/ioctl.c
fs/erofs/data.c
fs/ext4/file.c
fs/fuse/file.c
fs/gfs2/file.c
fs/iomap/direct-io.c
fs/ntfs/file.c
fs/ntfs3/file.c
fs/xfs/xfs_file.c
fs/zonefs/super.c
include/linux/iomap.h
include/linux/mm.h
include/linux/pagemap.h
mm/filemap.c

@@@ -306,12 -275,12 +306,12 @@@ retry
                fpregs_unlock();
  
                /* Try to handle #PF, but anything else is fatal. */
 -              if (ret != -EFAULT)
 -                      return -EINVAL;
 +              if (ret != X86_TRAP_PF)
 +                      return false;
  
-               if (!fault_in_pages_readable(buf, size))
+               if (!fault_in_readable(buf, size))
                        goto retry;
 -              return -EFAULT;
 +              return false;
        }
  
        /*
diff --cc fs/btrfs/file.c
Simple merge
Simple merge
diff --cc fs/erofs/data.c
Simple merge
diff --cc fs/ext4/file.c
Simple merge
diff --cc fs/fuse/file.c
Simple merge
diff --cc fs/gfs2/file.c
Simple merge
Simple merge
diff --cc fs/ntfs/file.c
Simple merge
diff --cc fs/ntfs3/file.c
Simple merge
Simple merge
Simple merge
@@@ -330,13 -330,21 +330,20 @@@ struct iomap_dio_ops 
    */
  #define IOMAP_DIO_OVERWRITE_ONLY      (1 << 1)
  
+ /*
+  * When a page fault occurs, return a partial synchronous result and allow
+  * the caller to retry the rest of the operation after dealing with the page
+  * fault.
+  */
+ #define IOMAP_DIO_PARTIAL             (1 << 2)
  ssize_t iomap_dio_rw(struct kiocb *iocb, struct iov_iter *iter,
                const struct iomap_ops *ops, const struct iomap_dio_ops *dops,
-               unsigned int dio_flags);
+               unsigned int dio_flags, size_t done_before);
  struct iomap_dio *__iomap_dio_rw(struct kiocb *iocb, struct iov_iter *iter,
                const struct iomap_ops *ops, const struct iomap_dio_ops *dops,
-               unsigned int dio_flags);
+               unsigned int dio_flags, size_t done_before);
  ssize_t iomap_dio_complete(struct iomap_dio *dio);
 -int iomap_dio_iopoll(struct kiocb *kiocb, bool spin);
  
  #ifdef CONFIG_SWAP
  struct file;
Simple merge
@@@ -821,71 -730,19 +821,21 @@@ int folio_wait_private_2_killable(struc
  /*
   * Add an arbitrary waiter to a page's wait queue
   */
 -extern void add_page_wait_queue(struct page *page, wait_queue_entry_t *waiter);
 +void folio_add_wait_queue(struct folio *folio, wait_queue_entry_t *waiter);
  
  /*
-  * Fault everything in given userspace address range in.
+  * Fault in userspace address range.
   */
- static inline int fault_in_pages_writeable(char __user *uaddr, size_t size)
- {
-       char __user *end = uaddr + size - 1;
-       if (unlikely(size == 0))
-               return 0;
-       if (unlikely(uaddr > end))
-               return -EFAULT;
-       /*
-        * Writing zeroes into userspace here is OK, because we know that if
-        * the zero gets there, we'll be overwriting it.
-        */
-       do {
-               if (unlikely(__put_user(0, uaddr) != 0))
-                       return -EFAULT;
-               uaddr += PAGE_SIZE;
-       } while (uaddr <= end);
-       /* Check whether the range spilled into the next page. */
-       if (((unsigned long)uaddr & PAGE_MASK) ==
-                       ((unsigned long)end & PAGE_MASK))
-               return __put_user(0, end);
-       return 0;
- }
- static inline int fault_in_pages_readable(const char __user *uaddr, size_t size)
- {
-       volatile char c;
-       const char __user *end = uaddr + size - 1;
-       if (unlikely(size == 0))
-               return 0;
-       if (unlikely(uaddr > end))
-               return -EFAULT;
-       do {
-               if (unlikely(__get_user(c, uaddr) != 0))
-                       return -EFAULT;
-               uaddr += PAGE_SIZE;
-       } while (uaddr <= end);
-       /* Check whether the range spilled into the next page. */
-       if (((unsigned long)uaddr & PAGE_MASK) ==
-                       ((unsigned long)end & PAGE_MASK)) {
-               return __get_user(c, end);
-       }
-       (void)c;
-       return 0;
- }
+ size_t fault_in_writeable(char __user *uaddr, size_t size);
+ size_t fault_in_safe_writeable(const char __user *uaddr, size_t size);
+ size_t fault_in_readable(const char __user *uaddr, size_t size);
  
  int add_to_page_cache_locked(struct page *page, struct address_space *mapping,
 -                              pgoff_t index, gfp_t gfp_mask);
 +              pgoff_t index, gfp_t gfp);
  int add_to_page_cache_lru(struct page *page, struct address_space *mapping,
 -                              pgoff_t index, gfp_t gfp_mask);
 +              pgoff_t index, gfp_t gfp);
 +int filemap_add_folio(struct address_space *mapping, struct folio *folio,
 +              pgoff_t index, gfp_t gfp);
  extern void delete_from_page_cache(struct page *page);
  extern void __delete_from_page_cache(struct page *page, void *shadow);
  void replace_page_cache_page(struct page *old, struct page *new);
diff --cc mm/filemap.c
Simple merge