| From 18a2f371f5edf41810f6469cb9be39931ef9deb9 Mon Sep 17 00:00:00 2001 |
| From: Mel Gorman <mgorman@suse.de> |
| Date: Wed, 5 Dec 2012 14:01:41 -0800 |
| Subject: tmpfs: fix shared mempolicy leak |
| |
| From: Mel Gorman <mgorman@suse.de> |
| |
| commit 18a2f371f5edf41810f6469cb9be39931ef9deb9 upstream. |
| |
| This fixes a regression in 3.7-rc, which has since gone into stable. |
| |
| Commit 00442ad04a5e ("mempolicy: fix a memory corruption by refcount |
| imbalance in alloc_pages_vma()") changed get_vma_policy() to raise the |
| refcount on a shmem shared mempolicy; whereas shmem_alloc_page() went |
| on expecting alloc_page_vma() to drop the refcount it had acquired. |
| This deserves a rework: but for now fix the leak in shmem_alloc_page(). |
| |
| Hugh: shmem_swapin() did not need a fix, but surely it's clearer to use |
| the same refcounting there as in shmem_alloc_page(), delete its onstack |
| mempolicy, and the strange mpol_cond_copy() and __mpol_cond_copy() - |
| those were invented to let swapin_readahead() make an unknown number of |
| calls to alloc_pages_vma() with one mempolicy; but since 00442ad04a5e, |
| alloc_pages_vma() has kept refcount in balance, so now no problem. |
| |
| Reported-and-tested-by: Tommi Rantala <tt.rantala@gmail.com> |
| Signed-off-by: Mel Gorman <mgorman@suse.de> |
| Signed-off-by: Hugh Dickins <hughd@google.com> |
| Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| include/linux/mempolicy.h | 16 ---------------- |
| mm/mempolicy.c | 22 ---------------------- |
| mm/shmem.c | 26 ++++++++++++++++---------- |
| 3 files changed, 16 insertions(+), 48 deletions(-) |
| |
| --- a/include/linux/mempolicy.h |
| +++ b/include/linux/mempolicy.h |
| @@ -137,16 +137,6 @@ static inline void mpol_cond_put(struct |
| __mpol_put(pol); |
| } |
| |
| -extern struct mempolicy *__mpol_cond_copy(struct mempolicy *tompol, |
| - struct mempolicy *frompol); |
| -static inline struct mempolicy *mpol_cond_copy(struct mempolicy *tompol, |
| - struct mempolicy *frompol) |
| -{ |
| - if (!frompol) |
| - return frompol; |
| - return __mpol_cond_copy(tompol, frompol); |
| -} |
| - |
| extern struct mempolicy *__mpol_dup(struct mempolicy *pol); |
| static inline struct mempolicy *mpol_dup(struct mempolicy *pol) |
| { |
| @@ -270,12 +260,6 @@ static inline void mpol_cond_put(struct |
| { |
| } |
| |
| -static inline struct mempolicy *mpol_cond_copy(struct mempolicy *to, |
| - struct mempolicy *from) |
| -{ |
| - return from; |
| -} |
| - |
| static inline void mpol_get(struct mempolicy *pol) |
| { |
| } |
| --- a/mm/mempolicy.c |
| +++ b/mm/mempolicy.c |
| @@ -2009,28 +2009,6 @@ struct mempolicy *__mpol_dup(struct memp |
| return new; |
| } |
| |
| -/* |
| - * If *frompol needs [has] an extra ref, copy *frompol to *tompol , |
| - * eliminate the * MPOL_F_* flags that require conditional ref and |
| - * [NOTE!!!] drop the extra ref. Not safe to reference *frompol directly |
| - * after return. Use the returned value. |
| - * |
| - * Allows use of a mempolicy for, e.g., multiple allocations with a single |
| - * policy lookup, even if the policy needs/has extra ref on lookup. |
| - * shmem_readahead needs this. |
| - */ |
| -struct mempolicy *__mpol_cond_copy(struct mempolicy *tompol, |
| - struct mempolicy *frompol) |
| -{ |
| - if (!mpol_needs_cond_ref(frompol)) |
| - return frompol; |
| - |
| - *tompol = *frompol; |
| - tompol->flags &= ~MPOL_F_SHARED; /* copy doesn't need unref */ |
| - __mpol_put(frompol); |
| - return tompol; |
| -} |
| - |
| /* Slow path of a mempolicy comparison */ |
| bool __mpol_equal(struct mempolicy *a, struct mempolicy *b) |
| { |
| --- a/mm/shmem.c |
| +++ b/mm/shmem.c |
| @@ -798,24 +798,28 @@ static struct mempolicy *shmem_get_sbmpo |
| static struct page *shmem_swapin(swp_entry_t swap, gfp_t gfp, |
| struct shmem_inode_info *info, pgoff_t index) |
| { |
| - struct mempolicy mpol, *spol; |
| struct vm_area_struct pvma; |
| - |
| - spol = mpol_cond_copy(&mpol, |
| - mpol_shared_policy_lookup(&info->policy, index)); |
| + struct page *page; |
| |
| /* Create a pseudo vma that just contains the policy */ |
| pvma.vm_start = 0; |
| pvma.vm_pgoff = index; |
| pvma.vm_ops = NULL; |
| - pvma.vm_policy = spol; |
| - return swapin_readahead(swap, gfp, &pvma, 0); |
| + pvma.vm_policy = mpol_shared_policy_lookup(&info->policy, index); |
| + |
| + page = swapin_readahead(swap, gfp, &pvma, 0); |
| + |
| + /* Drop reference taken by mpol_shared_policy_lookup() */ |
| + mpol_cond_put(pvma.vm_policy); |
| + |
| + return page; |
| } |
| |
| static struct page *shmem_alloc_page(gfp_t gfp, |
| struct shmem_inode_info *info, pgoff_t index) |
| { |
| struct vm_area_struct pvma; |
| + struct page *page; |
| |
| /* Create a pseudo vma that just contains the policy */ |
| pvma.vm_start = 0; |
| @@ -823,10 +827,12 @@ static struct page *shmem_alloc_page(gfp |
| pvma.vm_ops = NULL; |
| pvma.vm_policy = mpol_shared_policy_lookup(&info->policy, index); |
| |
| - /* |
| - * alloc_page_vma() will drop the shared policy reference |
| - */ |
| - return alloc_page_vma(gfp, &pvma, 0); |
| + page = alloc_page_vma(gfp, &pvma, 0); |
| + |
| + /* Drop reference taken by mpol_shared_policy_lookup() */ |
| + mpol_cond_put(pvma.vm_policy); |
| + |
| + return page; |
| } |
| #else /* !CONFIG_NUMA */ |
| #ifdef CONFIG_TMPFS |