Commit 878b63ac889df706d01048f2c110e322ad2f996d

Authored by Hugh Dickins
Committed by Linus Torvalds
1 parent 2da02997e0

mm: gup persist for write permission

do_wp_page()'s VM_FAULT_WRITE return value tells __get_user_pages() that
COW has been done if necessary, though it may be leaving the pte without
write permission - for the odd case of forced writing to a readonly vma
for ptrace.  At present GUP then retries the follow_page() without asking
for write permission, to escape an endless loop when forced.

But an application may be relying on GUP to guarantee a writable page
which won't be COWed again when written from userspace, whereas a race
here might leave a readonly pte in place?  Change the VM_FAULT_WRITE
handling to ask follow_page() for write permission again, except in that
odd case of forced writing to a readonly vma.

Signed-off-by: Hugh Dickins <hugh@veritas.com>
Cc: Lee Schermerhorn <lee.schermerhorn@hp.com>
Cc: Rik van Riel <riel@redhat.com>
Cc: Nick Piggin <nickpiggin@yahoo.com.au>
Cc: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
Cc: Robin Holt <holt@sgi.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

Showing 1 changed file with 8 additions and 2 deletions Side-by-side Diff

... ... @@ -1264,9 +1264,15 @@
1264 1264 * do_wp_page has broken COW when necessary,
1265 1265 * even if maybe_mkwrite decided not to set
1266 1266 * pte_write. We can thus safely do subsequent
1267   - * page lookups as if they were reads.
  1267 + * page lookups as if they were reads. But only
  1268 + * do so when looping for pte_write is futile:
  1269 + * in some cases userspace may also be wanting
  1270 + * to write to the gotten user page, which a
  1271 + * read fault here might prevent (a readonly
  1272 + * page might get reCOWed by userspace write).
1268 1273 */
1269   - if (ret & VM_FAULT_WRITE)
  1274 + if ((ret & VM_FAULT_WRITE) &&
  1275 + !(vma->vm_flags & VM_WRITE))
1270 1276 foll_flags &= ~FOLL_WRITE;
1271 1277  
1272 1278 cond_resched();