Skip to content

Commit 8959204

Browse files
Michal Hockogregkh
authored andcommitted
mm: hugetlbfs: correctly populate shared pmd
commit eb48c07 upstream. Each page mapped in a process's address space must be correctly accounted for in _mapcount. Normally the rules for this are straightforward but hugetlbfs page table sharing is different. The page table pages at the PMD level are reference counted while the mapcount remains the same. If this accounting is wrong, it causes bugs like this one reported by Larry Woodman: kernel BUG at mm/filemap.c:135! invalid opcode: 0000 [#1] SMP CPU 22 Modules linked in: bridge stp llc sunrpc binfmt_misc dcdbas microcode pcspkr acpi_pad acpi] Pid: 18001, comm: mpitest Tainted: G W 3.3.0+ #4 Dell Inc. PowerEdge R620/07NDJ2 RIP: 0010:[<ffffffff8112cfed>] [<ffffffff8112cfed>] __delete_from_page_cache+0x15d/0x170 Process mpitest (pid: 18001, threadinfo ffff880428972000, task ffff880428b5cc20) Call Trace: delete_from_page_cache+0x40/0x80 truncate_hugepages+0x115/0x1f0 hugetlbfs_evict_inode+0x18/0x30 evict+0x9f/0x1b0 iput_final+0xe3/0x1e0 iput+0x3e/0x50 d_kill+0xf8/0x110 dput+0xe2/0x1b0 __fput+0x162/0x240 During fork(), copy_hugetlb_page_range() detects if huge_pte_alloc() shared page tables with the check dst_pte == src_pte. The logic is if the PMD page is the same, they must be shared. This assumes that the sharing is between the parent and child. However, if the sharing is with a different process entirely then this check fails as in this diagram: parent | ------------>pmd src_pte----------> data page ^ other--------->pmd--------------------| ^ child-----------| dst_pte For this situation to occur, it must be possible for Parent and Other to have faulted and failed to share page tables with each other. This is possible due to the following style of race. PROC A PROC B copy_hugetlb_page_range copy_hugetlb_page_range src_pte == huge_pte_offset src_pte == huge_pte_offset !src_pte so no sharing !src_pte so no sharing (time passes) hugetlb_fault hugetlb_fault huge_pte_alloc huge_pte_alloc huge_pmd_share huge_pmd_share LOCK(i_mmap_mutex) find nothing, no sharing UNLOCK(i_mmap_mutex) LOCK(i_mmap_mutex) find nothing, no sharing UNLOCK(i_mmap_mutex) pmd_alloc pmd_alloc LOCK(instantiation_mutex) fault UNLOCK(instantiation_mutex) LOCK(instantiation_mutex) fault UNLOCK(instantiation_mutex) These two processes are not poing to the same data page but are not sharing page tables because the opportunity was missed. When either process later forks, the src_pte == dst pte is potentially insufficient. As the check falls through, the wrong PTE information is copied in (harmless but wrong) and the mapcount is bumped for a page mapped by a shared page table leading to the BUG_ON. This patch addresses the issue by moving pmd_alloc into huge_pmd_share which guarantees that the shared pud is populated in the same critical section as pmd. This also means that huge_pte_offset test in huge_pmd_share is serialized correctly now which in turn means that the success of the sharing will be higher as the racing tasks see the pud and pmd populated together. Race identified and changelog written mostly by Mel Gorman. {[email protected]: attempt to make the huge_pmd_share() comment comprehensible, clean up coding style] Reported-by: Larry Woodman <[email protected]> Tested-by: Larry Woodman <[email protected]> Reviewed-by: Mel Gorman <[email protected]> Signed-off-by: Michal Hocko <[email protected]> Reviewed-by: Rik van Riel <[email protected]> Cc: David Gibson <[email protected]> Cc: Ken Chen <[email protected]> Cc: Cong Wang <[email protected]> Cc: Hillf Danton <[email protected]> Signed-off-by: Andrew Morton <[email protected]> Signed-off-by: Linus Torvalds <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent eb2f4fb commit 8959204

File tree

1 file changed

+16
-5
lines changed

1 file changed

+16
-5
lines changed

arch/x86/mm/hugetlbpage.c

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,16 @@ static int vma_shareable(struct vm_area_struct *vma, unsigned long addr)
5656
}
5757

5858
/*
59-
* search for a shareable pmd page for hugetlb.
59+
* Search for a shareable pmd page for hugetlb. In any case calls pmd_alloc()
60+
* and returns the corresponding pte. While this is not necessary for the
61+
* !shared pmd case because we can allocate the pmd later as well, it makes the
62+
* code much cleaner. pmd allocation is essential for the shared case because
63+
* pud has to be populated inside the same i_mmap_mutex section - otherwise
64+
* racing tasks could either miss the sharing (see huge_pte_offset) or select a
65+
* bad pmd for sharing.
6066
*/
61-
static void huge_pmd_share(struct mm_struct *mm, unsigned long addr, pud_t *pud)
67+
static pte_t *
68+
huge_pmd_share(struct mm_struct *mm, unsigned long addr, pud_t *pud)
6269
{
6370
struct vm_area_struct *vma = find_vma(mm, addr);
6471
struct address_space *mapping = vma->vm_file->f_mapping;
@@ -68,9 +75,10 @@ static void huge_pmd_share(struct mm_struct *mm, unsigned long addr, pud_t *pud)
6875
struct vm_area_struct *svma;
6976
unsigned long saddr;
7077
pte_t *spte = NULL;
78+
pte_t *pte;
7179

7280
if (!vma_shareable(vma, addr))
73-
return;
81+
return (pte_t *)pmd_alloc(mm, pud, addr);
7482

7583
mutex_lock(&mapping->i_mmap_mutex);
7684
vma_prio_tree_foreach(svma, &iter, &mapping->i_mmap, idx, idx) {
@@ -97,7 +105,9 @@ static void huge_pmd_share(struct mm_struct *mm, unsigned long addr, pud_t *pud)
97105
put_page(virt_to_page(spte));
98106
spin_unlock(&mm->page_table_lock);
99107
out:
108+
pte = (pte_t *)pmd_alloc(mm, pud, addr);
100109
mutex_unlock(&mapping->i_mmap_mutex);
110+
return pte;
101111
}
102112

103113
/*
@@ -142,8 +152,9 @@ pte_t *huge_pte_alloc(struct mm_struct *mm,
142152
} else {
143153
BUG_ON(sz != PMD_SIZE);
144154
if (pud_none(*pud))
145-
huge_pmd_share(mm, addr, pud);
146-
pte = (pte_t *) pmd_alloc(mm, pud, addr);
155+
pte = huge_pmd_share(mm, addr, pud);
156+
else
157+
pte = (pte_t *)pmd_alloc(mm, pud, addr);
147158
}
148159
}
149160
BUG_ON(pte && !pte_none(*pte) && !pte_huge(*pte));

0 commit comments

Comments
 (0)