Skip to content

Commit

Permalink
mm/page_alloc.c: fix freeing non-compound pages
Browse files Browse the repository at this point in the history
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: e286781 ("mm: speculative page references")
Signed-off-by: Matthew Wilcox (Oracle) <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Acked-by: Mike Rapoport <[email protected]>
Cc: Nick Piggin <[email protected]>
Cc: Hugh Dickins <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Link: https://lkml.kernel.org/r/[email protected]
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
Matthew Wilcox (Oracle) authored and torvalds committed Oct 14, 2020
1 parent a9b576f commit e320d30
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 0 deletions.
9 changes: 9 additions & 0 deletions lib/Kconfig.debug
Original file line number Diff line number Diff line change
Expand Up @@ -2367,6 +2367,15 @@ config TEST_HMM

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
tristate "Test floating point operations in kernel space"
depends on X86 && !KCOV_INSTRUMENT_ALL
Expand Down
1 change: 1 addition & 0 deletions lib/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ obj-$(CONFIG_TEST_BLACKHOLE_DEV) += test_blackhole_dev.o
obj-$(CONFIG_TEST_MEMINIT) += test_meminit.o
obj-$(CONFIG_TEST_LOCKUP) += test_lockup.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
Expand Down
42 changes: 42 additions & 0 deletions lib/test_free_pages.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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 <[email protected]>
*/

#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 <[email protected]>");
MODULE_LICENSE("GPL");
3 changes: 3 additions & 0 deletions mm/page_alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -4952,6 +4952,9 @@ void __free_pages(struct page *page, unsigned int order)
{
if (put_page_testzero(page))
free_the_page(page, order);
else if (!PageHead(page))
while (order-- > 0)
free_the_page(page + (1 << order), order);
}
EXPORT_SYMBOL(__free_pages);

Expand Down

0 comments on commit e320d30

Please sign in to comment.