| From dgibson@ozlabs.org Thu Jul 17 22:56:01 2008 |
| From: David Gibson <david@gibson.dropbear.id.au> |
| Date: Fri, 18 Jul 2008 15:55:49 +1000 |
| Subject: Correct hash flushing from huge_ptep_set_wrprotect() |
| To: Benjamin Herrenschmidt <benh@kernel.crashing.org> |
| Cc: Greg KH <greg@kroah.com>, Linus Torvalds <torvalds@linux-foundation.org>, stable <stable@kernel.org>, Andy Whitcroft <apw@shadowen.org> |
| Message-ID: <20080718055549.GL18748@yookeroo.seuss> |
| Content-Disposition: inline |
| |
| From: David Gibson <david@gibson.dropbear.id.au> |
| |
| Correct hash flushing from huge_ptep_set_wrprotect() [stable tree version] |
| |
| A fix for incorrect flushing of the hash page table at fork() for |
| hugepages was recently committed as |
| 86df86424939d316b1f6cfac1b6204f0c7dee317. Without this fix, a process |
| can make a MAP_PRIVATE hugepage mapping, then fork() and have writes |
| to the mapping after the fork() pollute the child's version. |
| |
| Unfortunately this bug also exists in the stable branch. In fact in |
| that case copy_hugetlb_page_range() from mm/hugetlb.c calls |
| ptep_set_wrprotect() directly, the hugepage variant hook |
| huge_ptep_set_wrprotect() doesn't even exist. |
| |
| The patch below is a port of the fix to the stable25/master branch. |
| It introduces a huge_ptep_set_wrprotect() call, but this is #defined |
| to be equal to ptep_set_wrprotect() unless the arch defines its own |
| version and sets __HAVE_ARCH_HUGE_PTEP_SET_WRPROTECT. |
| |
| This arch preprocessor flag is kind of nasty, but it seems the sanest |
| way to introduce this fix with minimum risk of breaking other archs |
| for whom prep_set_wprotect() is suitable for hugepages. |
| |
| Signed-off-by: Andy Whitcroft <apw@shadowen.org> |
| Signed-off-by: David Gibson <david@gibson.dropbear.id.au> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> |
| |
| --- |
| include/asm-powerpc/pgtable-ppc64.h | 11 +++++++++++ |
| mm/hugetlb.c | 6 +++++- |
| 2 files changed, 16 insertions(+), 1 deletion(-) |
| |
| --- a/include/asm-powerpc/pgtable-ppc64.h |
| +++ b/include/asm-powerpc/pgtable-ppc64.h |
| @@ -311,6 +311,17 @@ static inline void ptep_set_wrprotect(st |
| old = pte_update(mm, addr, ptep, _PAGE_RW, 0); |
| } |
| |
| +#define __HAVE_ARCH_HUGE_PTEP_SET_WRPROTECT |
| +static inline void huge_ptep_set_wrprotect(struct mm_struct *mm, |
| + unsigned long addr, pte_t *ptep) |
| +{ |
| + unsigned long old; |
| + |
| + if ((pte_val(*ptep) & _PAGE_RW) == 0) |
| + return; |
| + old = pte_update(mm, addr, ptep, _PAGE_RW, 1); |
| +} |
| + |
| /* |
| * We currently remove entries from the hashtable regardless of whether |
| * the entry was young or dirty. The generic routines only flush if the |
| --- a/mm/hugetlb.c |
| +++ b/mm/hugetlb.c |
| @@ -738,6 +738,10 @@ static void set_huge_ptep_writable(struc |
| } |
| |
| |
| +#ifndef __HAVE_ARCH_HUGE_PTEP_SET_WRPROTECT |
| +#define huge_ptep_set_wrprotect ptep_set_wrprotect |
| +#endif |
| + |
| int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src, |
| struct vm_area_struct *vma) |
| { |
| @@ -764,7 +768,7 @@ int copy_hugetlb_page_range(struct mm_st |
| spin_lock(&src->page_table_lock); |
| if (!pte_none(*src_pte)) { |
| if (cow) |
| - ptep_set_wrprotect(src, addr, src_pte); |
| + huge_ptep_set_wrprotect(src, addr, src_pte); |
| entry = *src_pte; |
| ptepage = pte_page(entry); |
| get_page(ptepage); |