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

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.org


Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent a9b576f7
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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

lib/test_free_pages.c

0 → 100644
+42 −0
Original line number Diff line number Diff line
// 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");
+3 −0
Original line number Diff line number Diff line
@@ -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);