Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

mm: fix page reference leak in soft_offline_page()

The conversion to move pfn_to_online_page() internal to
soft_offline_page() missed that the get_user_pages() reference taken by
the madvise() path needs to be dropped when pfn_to_online_page() fails.

Note the direct sysfs-path to soft_offline_page() does not perform a
get_user_pages() lookup.

When soft_offline_page() is handed a pfn_valid() && !pfn_to_online_page()
pfn the kernel hangs at dax-device shutdown due to a leaked reference.

Link: https://lkml.kernel.org/r/161058501210.1840162.8108917599181157327.stgit@dwillia2-desk3.amr.corp.intel.com
Fixes: feec24a6139d ("mm, soft-offline: convert parameter to pfn")
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Reviewed-by: David Hildenbrand <david@redhat.com>
Reviewed-by: Oscar Salvador <osalvador@suse.de>
Reviewed-by: Naoya Horiguchi <naoya.horiguchi@nec.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Qian Cai <cai@lca.pw>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Dan Williams and committed by
Linus Torvalds
dad4e5b3 251b5497

+16 -4
+16 -4
mm/memory-failure.c
··· 1885 1885 return rc; 1886 1886 } 1887 1887 1888 + static void put_ref_page(struct page *page) 1889 + { 1890 + if (page) 1891 + put_page(page); 1892 + } 1893 + 1888 1894 /** 1889 1895 * soft_offline_page - Soft offline a page. 1890 1896 * @pfn: pfn to soft-offline ··· 1916 1910 int soft_offline_page(unsigned long pfn, int flags) 1917 1911 { 1918 1912 int ret; 1919 - struct page *page; 1920 1913 bool try_again = true; 1914 + struct page *page, *ref_page = NULL; 1915 + 1916 + WARN_ON_ONCE(!pfn_valid(pfn) && (flags & MF_COUNT_INCREASED)); 1921 1917 1922 1918 if (!pfn_valid(pfn)) 1923 1919 return -ENXIO; 1920 + if (flags & MF_COUNT_INCREASED) 1921 + ref_page = pfn_to_page(pfn); 1922 + 1924 1923 /* Only online pages can be soft-offlined (esp., not ZONE_DEVICE). */ 1925 1924 page = pfn_to_online_page(pfn); 1926 - if (!page) 1925 + if (!page) { 1926 + put_ref_page(ref_page); 1927 1927 return -EIO; 1928 + } 1928 1929 1929 1930 if (PageHWPoison(page)) { 1930 1931 pr_info("%s: %#lx page already poisoned\n", __func__, pfn); 1931 - if (flags & MF_COUNT_INCREASED) 1932 - put_page(page); 1932 + put_ref_page(ref_page); 1933 1933 return 0; 1934 1934 } 1935 1935