Commit e320d301 authored by Matthew Wilcox (Oracle)'s avatar Matthew Wilcox (Oracle) Committed by Linus Torvalds

mm/page_alloc.c: fix freeing non-compound pages

Here is a very rare race which leaks memory:

Page P0 is allocated to the page cache.  Page P1 is free.

Thread A                Thread B                Thread C
find_get_entry():
xas_load() returns P0
						Removes P0 from page cache
						P0 finds its buddy P1
			alloc_pages(GFP_KERNEL, 1) returns P0
			P0 has refcount 1
page_cache_get_speculative(P0)
P0 has refcount 2
			__free_pages(P0)
			P0 has refcount 1
put_page(P0)
P1 is not freed

Fix this by freeing all the pages in __free_pages() that won't be freed
by the call to put_page().  It's usually not a good idea to split a page,
but this is a very unlikely scenario.

Fixes: e286781d ("mm: speculative page references")
Signed-off-by: default avatarMatthew Wilcox (Oracle) <willy@infradead.org>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Acked-by: default avatarMike Rapoport <rppt@linux.ibm.com>
Cc: Nick Piggin <npiggin@gmail.com>
Cc: Hugh Dickins <hughd@google.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://lkml.kernel.org/r/20200926213919.26642-1-willy@infradead.orgSigned-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent a9b576f7
...@@ -2367,6 +2367,15 @@ config TEST_HMM ...@@ -2367,6 +2367,15 @@ config TEST_HMM
If unsure, say N. If unsure, say N.
config TEST_FREE_PAGES
tristate "Test freeing pages"
help
Test that a memory leak does not occur due to a race between
freeing a block of pages and a speculative page reference.
Loading this module is safe if your kernel has the bug fixed.
If the bug is not fixed, it will leak gigabytes of memory and
probably OOM your system.
config TEST_FPU config TEST_FPU
tristate "Test floating point operations in kernel space" tristate "Test floating point operations in kernel space"
depends on X86 && !KCOV_INSTRUMENT_ALL depends on X86 && !KCOV_INSTRUMENT_ALL
......
...@@ -101,6 +101,7 @@ obj-$(CONFIG_TEST_BLACKHOLE_DEV) += test_blackhole_dev.o ...@@ -101,6 +101,7 @@ obj-$(CONFIG_TEST_BLACKHOLE_DEV) += test_blackhole_dev.o
obj-$(CONFIG_TEST_MEMINIT) += test_meminit.o obj-$(CONFIG_TEST_MEMINIT) += test_meminit.o
obj-$(CONFIG_TEST_LOCKUP) += test_lockup.o obj-$(CONFIG_TEST_LOCKUP) += test_lockup.o
obj-$(CONFIG_TEST_HMM) += test_hmm.o obj-$(CONFIG_TEST_HMM) += test_hmm.o
obj-$(CONFIG_TEST_FREE_PAGES) += test_free_pages.o
# #
# CFLAGS for compiling floating point code inside the kernel. x86/Makefile turns # CFLAGS for compiling floating point code inside the kernel. x86/Makefile turns
......
// SPDX-License-Identifier: GPL-2.0+
/*
* test_free_pages.c: Check that free_pages() doesn't leak memory
* Copyright (c) 2020 Oracle
* Author: Matthew Wilcox <willy@infradead.org>
*/
#include <linux/gfp.h>
#include <linux/mm.h>
#include <linux/module.h>
static void test_free_pages(gfp_t gfp)
{
unsigned int i;
for (i = 0; i < 1000 * 1000; i++) {
unsigned long addr = __get_free_pages(gfp, 3);
struct page *page = virt_to_page(addr);
/* Simulate page cache getting a speculative reference */
get_page(page);
free_pages(addr, 3);
put_page(page);
}
}
static int m_in(void)
{
test_free_pages(GFP_KERNEL);
test_free_pages(GFP_KERNEL | __GFP_COMP);
return 0;
}
static void m_ex(void)
{
}
module_init(m_in);
module_exit(m_ex);
MODULE_AUTHOR("Matthew Wilcox <willy@infradead.org>");
MODULE_LICENSE("GPL");
...@@ -4952,6 +4952,9 @@ void __free_pages(struct page *page, unsigned int order) ...@@ -4952,6 +4952,9 @@ void __free_pages(struct page *page, unsigned int order)
{ {
if (put_page_testzero(page)) if (put_page_testzero(page))
free_the_page(page, order); free_the_page(page, order);
else if (!PageHead(page))
while (order-- > 0)
free_the_page(page + (1 << order), order);
} }
EXPORT_SYMBOL(__free_pages); EXPORT_SYMBOL(__free_pages);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment