aboutsummaryrefslogtreecommitdiffstats
path: root/mm
diff options
context:
space:
mode:
authorMike Kravetz <mike.kravetz@oracle.com>2017-01-10 19:58:27 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-01-19 14:17:59 -0500
commit1e26cec60668091201d490dd8709352696336211 (patch)
tree1560d3ff47e70b84ed77a914360d8a7e373df000 /mm
parent8315c22ea879082bba365d46dd2cc7881fbfb49a (diff)
mm/hugetlb.c: fix reservation race when freeing surplus pages
commit e5bbc8a6c992901058bc09e2ce01d16c111ff047 upstream. return_unused_surplus_pages() decrements the global reservation count, and frees any unused surplus pages that were backing the reservation. Commit 7848a4bf51b3 ("mm/hugetlb.c: add cond_resched_lock() in return_unused_surplus_pages()") added a call to cond_resched_lock in the loop freeing the pages. As a result, the hugetlb_lock could be dropped, and someone else could use the pages that will be freed in subsequent iterations of the loop. This could result in inconsistent global hugetlb page state, application api failures (such as mmap) failures or application crashes. When dropping the lock in return_unused_surplus_pages, make sure that the global reservation count (resv_huge_pages) remains sufficiently large to prevent someone else from claiming pages about to be freed. Analyzed by Paul Cassella. Fixes: 7848a4bf51b3 ("mm/hugetlb.c: add cond_resched_lock() in return_unused_surplus_pages()") Link: http://lkml.kernel.org/r/1483991767-6879-1-git-send-email-mike.kravetz@oracle.com Signed-off-by: Mike Kravetz <mike.kravetz@oracle.com> Reported-by: Paul Cassella <cassella@cray.com> Suggested-by: Michal Hocko <mhocko@kernel.org> Cc: Masayoshi Mizuma <m.mizuma@jp.fujitsu.com> Cc: Naoya Horiguchi <n-horiguchi@ah.jp.nec.com> Cc: Aneesh Kumar <aneesh.kumar@linux.vnet.ibm.com> Cc: Hillf Danton <hillf.zj@alibaba-inc.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'mm')
-rw-r--r--mm/hugetlb.c37
1 files changed, 28 insertions, 9 deletions
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 23aec01836aa..b6adedbafaf5 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -1773,23 +1773,32 @@ free:
1773} 1773}
1774 1774
1775/* 1775/*
1776 * When releasing a hugetlb pool reservation, any surplus pages that were 1776 * This routine has two main purposes:
1777 * allocated to satisfy the reservation must be explicitly freed if they were 1777 * 1) Decrement the reservation count (resv_huge_pages) by the value passed
1778 * never used. 1778 * in unused_resv_pages. This corresponds to the prior adjustments made
1779 * Called with hugetlb_lock held. 1779 * to the associated reservation map.
1780 * 2) Free any unused surplus pages that may have been allocated to satisfy
1781 * the reservation. As many as unused_resv_pages may be freed.
1782 *
1783 * Called with hugetlb_lock held. However, the lock could be dropped (and
1784 * reacquired) during calls to cond_resched_lock. Whenever dropping the lock,
1785 * we must make sure nobody else can claim pages we are in the process of
1786 * freeing. Do this by ensuring resv_huge_page always is greater than the
1787 * number of huge pages we plan to free when dropping the lock.
1780 */ 1788 */
1781static void return_unused_surplus_pages(struct hstate *h, 1789static void return_unused_surplus_pages(struct hstate *h,
1782 unsigned long unused_resv_pages) 1790 unsigned long unused_resv_pages)
1783{ 1791{
1784 unsigned long nr_pages; 1792 unsigned long nr_pages;
1785 1793
1786 /* Uncommit the reservation */
1787 h->resv_huge_pages -= unused_resv_pages;
1788
1789 /* Cannot return gigantic pages currently */ 1794 /* Cannot return gigantic pages currently */
1790 if (hstate_is_gigantic(h)) 1795 if (hstate_is_gigantic(h))
1791 return; 1796 goto out;
1792 1797
1798 /*
1799 * Part (or even all) of the reservation could have been backed
1800 * by pre-allocated pages. Only free surplus pages.
1801 */
1793 nr_pages = min(unused_resv_pages, h->surplus_huge_pages); 1802 nr_pages = min(unused_resv_pages, h->surplus_huge_pages);
1794 1803
1795 /* 1804 /*
@@ -1799,12 +1808,22 @@ static void return_unused_surplus_pages(struct hstate *h,
1799 * when the nodes with surplus pages have no free pages. 1808 * when the nodes with surplus pages have no free pages.
1800 * free_pool_huge_page() will balance the the freed pages across the 1809 * free_pool_huge_page() will balance the the freed pages across the
1801 * on-line nodes with memory and will handle the hstate accounting. 1810 * on-line nodes with memory and will handle the hstate accounting.
1811 *
1812 * Note that we decrement resv_huge_pages as we free the pages. If
1813 * we drop the lock, resv_huge_pages will still be sufficiently large
1814 * to cover subsequent pages we may free.
1802 */ 1815 */
1803 while (nr_pages--) { 1816 while (nr_pages--) {
1817 h->resv_huge_pages--;
1818 unused_resv_pages--;
1804 if (!free_pool_huge_page(h, &node_states[N_MEMORY], 1)) 1819 if (!free_pool_huge_page(h, &node_states[N_MEMORY], 1))
1805 break; 1820 goto out;
1806 cond_resched_lock(&hugetlb_lock); 1821 cond_resched_lock(&hugetlb_lock);
1807 } 1822 }
1823
1824out:
1825 /* Fully uncommit the reservation */
1826 h->resv_huge_pages -= unused_resv_pages;
1808} 1827}
1809 1828
1810 1829