diff options
| author | Peter Feiner <pfeiner@google.com> | 2014-10-13 18:55:46 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-10-13 20:18:28 -0400 |
| commit | 64e455079e1bd7787cc47be30b7f601ce682a5f6 (patch) | |
| tree | 05193bd91be3ffc0d33ddd3ffb654ef4c23778f9 /include | |
| parent | 63a12d9d01831208a47f5c0fbbf93f503d1fb162 (diff) | |
mm: softdirty: enable write notifications on VMAs after VM_SOFTDIRTY cleared
For VMAs that don't want write notifications, PTEs created for read faults
have their write bit set. If the read fault happens after VM_SOFTDIRTY is
cleared, then the PTE's softdirty bit will remain clear after subsequent
writes.
Here's a simple code snippet to demonstrate the bug:
char* m = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED, -1, 0);
system("echo 4 > /proc/$PPID/clear_refs"); /* clear VM_SOFTDIRTY */
assert(*m == '\0'); /* new PTE allows write access */
assert(!soft_dirty(x));
*m = 'x'; /* should dirty the page */
assert(soft_dirty(x)); /* fails */
With this patch, write notifications are enabled when VM_SOFTDIRTY is
cleared. Furthermore, to avoid unnecessary faults, write notifications
are disabled when VM_SOFTDIRTY is set.
As a side effect of enabling and disabling write notifications with
care, this patch fixes a bug in mprotect where vm_page_prot bits set by
drivers were zapped on mprotect. An analogous bug was fixed in mmap by
commit c9d0bf241451 ("mm: uncached vma support with writenotify").
Signed-off-by: Peter Feiner <pfeiner@google.com>
Reported-by: Peter Feiner <pfeiner@google.com>
Suggested-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Pavel Emelyanov <xemul@parallels.com>
Cc: Jamie Liu <jamieliu@google.com>
Cc: Hugh Dickins <hughd@google.com>
Cc: Naoya Horiguchi <n-horiguchi@ah.jp.nec.com>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'include')
| -rw-r--r-- | include/asm-generic/pgtable.h | 14 | ||||
| -rw-r--r-- | include/linux/mm.h | 5 |
2 files changed, 19 insertions, 0 deletions
diff --git a/include/asm-generic/pgtable.h b/include/asm-generic/pgtable.h index 081ff8826bf6..752e30d63904 100644 --- a/include/asm-generic/pgtable.h +++ b/include/asm-generic/pgtable.h | |||
| @@ -253,6 +253,20 @@ static inline int pmd_same(pmd_t pmd_a, pmd_t pmd_b) | |||
| 253 | #define pgprot_device pgprot_noncached | 253 | #define pgprot_device pgprot_noncached |
| 254 | #endif | 254 | #endif |
| 255 | 255 | ||
| 256 | #ifndef pgprot_modify | ||
| 257 | #define pgprot_modify pgprot_modify | ||
| 258 | static inline pgprot_t pgprot_modify(pgprot_t oldprot, pgprot_t newprot) | ||
| 259 | { | ||
| 260 | if (pgprot_val(oldprot) == pgprot_val(pgprot_noncached(oldprot))) | ||
| 261 | newprot = pgprot_noncached(newprot); | ||
| 262 | if (pgprot_val(oldprot) == pgprot_val(pgprot_writecombine(oldprot))) | ||
| 263 | newprot = pgprot_writecombine(newprot); | ||
| 264 | if (pgprot_val(oldprot) == pgprot_val(pgprot_device(oldprot))) | ||
| 265 | newprot = pgprot_device(newprot); | ||
| 266 | return newprot; | ||
| 267 | } | ||
| 268 | #endif | ||
| 269 | |||
| 256 | /* | 270 | /* |
| 257 | * When walking page tables, get the address of the next boundary, | 271 | * When walking page tables, get the address of the next boundary, |
| 258 | * or the end address of the range if that comes earlier. Although no | 272 | * or the end address of the range if that comes earlier. Although no |
diff --git a/include/linux/mm.h b/include/linux/mm.h index 4cd45cb95e6d..02d11ee7f19d 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h | |||
| @@ -1974,11 +1974,16 @@ static inline struct vm_area_struct *find_exact_vma(struct mm_struct *mm, | |||
| 1974 | 1974 | ||
| 1975 | #ifdef CONFIG_MMU | 1975 | #ifdef CONFIG_MMU |
| 1976 | pgprot_t vm_get_page_prot(unsigned long vm_flags); | 1976 | pgprot_t vm_get_page_prot(unsigned long vm_flags); |
| 1977 | void vma_set_page_prot(struct vm_area_struct *vma); | ||
| 1977 | #else | 1978 | #else |
| 1978 | static inline pgprot_t vm_get_page_prot(unsigned long vm_flags) | 1979 | static inline pgprot_t vm_get_page_prot(unsigned long vm_flags) |
| 1979 | { | 1980 | { |
| 1980 | return __pgprot(0); | 1981 | return __pgprot(0); |
| 1981 | } | 1982 | } |
| 1983 | static inline void vma_set_page_prot(struct vm_area_struct *vma) | ||
| 1984 | { | ||
| 1985 | vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); | ||
| 1986 | } | ||
| 1982 | #endif | 1987 | #endif |
| 1983 | 1988 | ||
| 1984 | #ifdef CONFIG_NUMA_BALANCING | 1989 | #ifdef CONFIG_NUMA_BALANCING |
