Skip to content
This repository was archived by the owner on May 17, 2022. It is now read-only.

Commit

Permalink
mm: close PageTail race
Browse files Browse the repository at this point in the history
Commit bf6bddf ("mm: introduce compaction and migration for
ballooned pages") introduces page_count(page) into memory compaction
which dereferences page->first_page if PageTail(page).

This results in a very rare NULL pointer dereference on the
aforementioned page_count(page).  Indeed, anything that does
compound_head(), including page_count() is susceptible to racing with
prep_compound_page() and seeing a NULL or dangling page->first_page
pointer.

This patch uses Andrea's implementation of compound_trans_head() that
deals with such a race and makes it the default compound_head()
implementation.  This includes a read memory barrier that ensures that
if PageTail(head) is true that we return a head page that is neither
NULL nor dangling.  The patch then adds a store memory barrier to
prep_compound_page() to ensure page->first_page is set.

This is the safest way to ensure we see the head page that we are
expecting, PageTail(page) is already in the unlikely() path and the
memory barriers are unfortunately required.

Hugetlbfs is the exception, we don't enforce a store memory barrier
during init since no race is possible.

Signed-off-by: David Rientjes <[email protected]>
Cc: Holger Kiehl <[email protected]>
Cc: Christoph Lameter <[email protected]>
Cc: Rafael Aquini <[email protected]>
Cc: Vlastimil Babka <[email protected]>
Cc: Michal Hocko <[email protected]>
Cc: Mel Gorman <[email protected]>
Cc: Andrea Arcangeli <[email protected]>
Cc: Rik van Riel <[email protected]>
Cc: "Kirill A. Shutemov" <[email protected]>
Cc: <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
Change-Id: Icc6c679a4467d30676097f57fe93c8e38dac99a1
  • Loading branch information
rientjes authored and pavlaras committed Aug 21, 2020
1 parent 3656988 commit ec18cb7
Show file tree
Hide file tree
Showing 4 changed files with 5 additions and 23 deletions.
4 changes: 2 additions & 2 deletions drivers/block/aoe/aoecmd.c
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ bio_pageinc(struct bio *bio)
/* Non-zero page count for non-head members of
* compound pages is no longer allowed by the kernel.
*/
page = compound_trans_head(bv->bv_page);
page = compound_head(bv.bv_page);
atomic_inc(&page->_count);
}
}
Expand All @@ -905,7 +905,7 @@ bio_pagedec(struct bio *bio)
int i;

bio_for_each_segment(bv, bio, i) {
page = compound_trans_head(bv->bv_page);
page = compound_head(bv.bv_page);
atomic_dec(&page->_count);
}
}
Expand Down
18 changes: 0 additions & 18 deletions include/linux/huge_mm.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,23 +163,6 @@ static inline int hpage_nr_pages(struct page *page)
extern int do_huge_pmd_numa_page(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long addr, pmd_t pmd, pmd_t *pmdp);

static inline struct page *compound_trans_head(struct page *page)
{
if (PageTail(page)) {
struct page *head;
head = page->first_page;
smp_rmb();
/*
* head may be a dangling pointer.
* __split_huge_page_refcount clears PageTail before
* overwriting first_page, so if PageTail is still
* there it means the head pointer isn't dangling.
*/
if (PageTail(page))
return head;
}
return page;
}
#else /* CONFIG_TRANSPARENT_HUGEPAGE */
#define HPAGE_PMD_SHIFT ({ BUILD_BUG(); 0; })
#define HPAGE_PMD_MASK ({ BUILD_BUG(); 0; })
Expand All @@ -205,7 +188,6 @@ static inline int split_huge_page(struct page *page)
do { } while (0)
#define split_huge_page_pmd_mm(__mm, __address, __pmd) \
do { } while (0)
#define compound_trans_head(page) compound_head(page)
static inline int hugepage_madvise(struct vm_area_struct *vma,
unsigned long *vm_flags, int advice)
{
Expand Down
2 changes: 1 addition & 1 deletion mm/ksm.c
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ static void break_cow(struct rmap_item *rmap_item)
static struct page *page_trans_compound_anon(struct page *page)
{
if (PageTransCompound(page)) {
struct page *head = compound_trans_head(page);
struct page *head = compound_head(page);
/*
* head may actually be splitted and freed from under
* us but it's ok here.
Expand Down
4 changes: 2 additions & 2 deletions mm/swap.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ static void put_compound_page(struct page *page)
}

/* __split_huge_page_refcount can run under us */
page_head = compound_trans_head(page);
page_head = compound_head(page);

/*
* THP can not break up slab pages so avoid taking
Expand Down Expand Up @@ -250,7 +250,7 @@ bool __get_page_tail(struct page *page)
*/
unsigned long flags;
bool got;
struct page *page_head = compound_trans_head(page);
struct page *page_head = compound_head(page);

/* Ref to put_compound_page() comment. */
if (!__compound_tail_refcounted(page_head)) {
Expand Down

0 comments on commit ec18cb7

Please sign in to comment.