diff options
author | Pravin B Shelar <pshelar@nicira.com> | 2012-05-29 18:06:49 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-05-29 19:22:24 -0400 |
commit | 5bf5f03c271907978489868a4c72aeb42b5127d2 (patch) | |
tree | 2d6b283fa5ee83b723fd4b4a8f070ae53c60ebe9 /mm | |
parent | dbda591d920b4c7692725b13e3f68ecb251e9080 (diff) |
mm: fix slab->page flags corruption
Transparent huge pages can change page->flags (PG_compound_lock) without
taking Slab lock. Since THP can not break slab pages we can safely access
compound page without taking compound lock.
Specifically this patch fixes a race between compound_unlock() and slab
functions which perform page-flags updates. This can occur when
get_page()/put_page() is called on a page from slab.
[akpm@linux-foundation.org: tweak comment text, fix comment layout, fix label indenting]
Reported-by: Amey Bhide <abhide@nicira.com>
Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
Reviewed-by: Christoph Lameter <cl@linux.com>
Acked-by: Andrea Arcangeli <aarcange@redhat.com>
Cc: Pekka Enberg <penberg@cs.helsinki.fi>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'mm')
-rw-r--r-- | mm/swap.c | 37 |
1 files changed, 35 insertions, 2 deletions
@@ -82,6 +82,25 @@ static void put_compound_page(struct page *page) | |||
82 | if (likely(page != page_head && | 82 | if (likely(page != page_head && |
83 | get_page_unless_zero(page_head))) { | 83 | get_page_unless_zero(page_head))) { |
84 | unsigned long flags; | 84 | unsigned long flags; |
85 | |||
86 | /* | ||
87 | * THP can not break up slab pages so avoid taking | ||
88 | * compound_lock(). Slab performs non-atomic bit ops | ||
89 | * on page->flags for better performance. In particular | ||
90 | * slab_unlock() in slub used to be a hot path. It is | ||
91 | * still hot on arches that do not support | ||
92 | * this_cpu_cmpxchg_double(). | ||
93 | */ | ||
94 | if (PageSlab(page_head)) { | ||
95 | if (PageTail(page)) { | ||
96 | if (put_page_testzero(page_head)) | ||
97 | VM_BUG_ON(1); | ||
98 | |||
99 | atomic_dec(&page->_mapcount); | ||
100 | goto skip_lock_tail; | ||
101 | } else | ||
102 | goto skip_lock; | ||
103 | } | ||
85 | /* | 104 | /* |
86 | * page_head wasn't a dangling pointer but it | 105 | * page_head wasn't a dangling pointer but it |
87 | * may not be a head page anymore by the time | 106 | * may not be a head page anymore by the time |
@@ -92,10 +111,10 @@ static void put_compound_page(struct page *page) | |||
92 | if (unlikely(!PageTail(page))) { | 111 | if (unlikely(!PageTail(page))) { |
93 | /* __split_huge_page_refcount run before us */ | 112 | /* __split_huge_page_refcount run before us */ |
94 | compound_unlock_irqrestore(page_head, flags); | 113 | compound_unlock_irqrestore(page_head, flags); |
95 | VM_BUG_ON(PageHead(page_head)); | 114 | skip_lock: |
96 | if (put_page_testzero(page_head)) | 115 | if (put_page_testzero(page_head)) |
97 | __put_single_page(page_head); | 116 | __put_single_page(page_head); |
98 | out_put_single: | 117 | out_put_single: |
99 | if (put_page_testzero(page)) | 118 | if (put_page_testzero(page)) |
100 | __put_single_page(page); | 119 | __put_single_page(page); |
101 | return; | 120 | return; |
@@ -115,6 +134,8 @@ static void put_compound_page(struct page *page) | |||
115 | VM_BUG_ON(atomic_read(&page_head->_count) <= 0); | 134 | VM_BUG_ON(atomic_read(&page_head->_count) <= 0); |
116 | VM_BUG_ON(atomic_read(&page->_count) != 0); | 135 | VM_BUG_ON(atomic_read(&page->_count) != 0); |
117 | compound_unlock_irqrestore(page_head, flags); | 136 | compound_unlock_irqrestore(page_head, flags); |
137 | |||
138 | skip_lock_tail: | ||
118 | if (put_page_testzero(page_head)) { | 139 | if (put_page_testzero(page_head)) { |
119 | if (PageHead(page_head)) | 140 | if (PageHead(page_head)) |
120 | __put_compound_page(page_head); | 141 | __put_compound_page(page_head); |
@@ -162,6 +183,18 @@ bool __get_page_tail(struct page *page) | |||
162 | struct page *page_head = compound_trans_head(page); | 183 | struct page *page_head = compound_trans_head(page); |
163 | 184 | ||
164 | if (likely(page != page_head && get_page_unless_zero(page_head))) { | 185 | if (likely(page != page_head && get_page_unless_zero(page_head))) { |
186 | |||
187 | /* Ref to put_compound_page() comment. */ | ||
188 | if (PageSlab(page_head)) { | ||
189 | if (likely(PageTail(page))) { | ||
190 | __get_page_tail_foll(page, false); | ||
191 | return true; | ||
192 | } else { | ||
193 | put_page(page_head); | ||
194 | return false; | ||
195 | } | ||
196 | } | ||
197 | |||
165 | /* | 198 | /* |
166 | * page_head wasn't a dangling pointer but it | 199 | * page_head wasn't a dangling pointer but it |
167 | * may not be a head page anymore by the time | 200 | * may not be a head page anymore by the time |