Skip to content

Commit c0a32fc

Browse files
Stanislaw Gruszkatorvalds
Stanislaw Gruszka
authored andcommitted
mm: more intensive memory corruption debugging
With CONFIG_DEBUG_PAGEALLOC configured, the CPU will generate an exception on access (read,write) to an unallocated page, which permits us to catch code which corrupts memory. However the kernel is trying to maximise memory usage, hence there are usually few free pages in the system and buggy code usually corrupts some crucial data. This patch changes the buddy allocator to keep more free/protected pages and to interlace free/protected and allocated pages to increase the probability of catching corruption. When the kernel is compiled with CONFIG_DEBUG_PAGEALLOC, debug_guardpage_minorder defines the minimum order used by the page allocator to grant a request. The requested size will be returned with the remaining pages used as guard pages. The default value of debug_guardpage_minorder is zero: no change from current behaviour. [akpm@linux-foundation.org: tweak documentation, s/flg/flag/] Signed-off-by: Stanislaw Gruszka <sgruszka@redhat.com> Cc: Mel Gorman <mgorman@suse.de> Cc: Andrea Arcangeli <aarcange@redhat.com> Cc: "Rafael J. Wysocki" <rjw@sisk.pl> Cc: Christoph Lameter <cl@linux-foundation.org> Cc: Pekka Enberg <penberg@cs.helsinki.fi> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1 parent 1399ff8 commit c0a32fc

File tree

5 files changed

+113
-7
lines changed

5 files changed

+113
-7
lines changed

Documentation/kernel-parameters.txt

+19
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,25 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
623623
no_debug_objects
624624
[KNL] Disable object debugging
625625

626+
debug_guardpage_minorder=
627+
[KNL] When CONFIG_DEBUG_PAGEALLOC is set, this
628+
parameter allows control of the order of pages that will
629+
be intentionally kept free (and hence protected) by the
630+
buddy allocator. Bigger value increase the probability
631+
of catching random memory corruption, but reduce the
632+
amount of memory for normal system use. The maximum
633+
possible value is MAX_ORDER/2. Setting this parameter
634+
to 1 or 2 should be enough to identify most random
635+
memory corruption problems caused by bugs in kernel or
636+
driver code when a CPU writes to (or reads from) a
637+
random memory location. Note that there exists a class
638+
of memory corruptions problems caused by buggy H/W or
639+
F/W or by drivers badly programing DMA (basically when
640+
memory is written at bus level and the CPU MMU is
641+
bypassed) which are not detectable by
642+
CONFIG_DEBUG_PAGEALLOC, hence this option will not help
643+
tracking down these problems.
644+
626645
debugpat [X86] Enable PAT debugging
627646

628647
decnet.addr= [HW,NET]

include/linux/mm.h

+17
Original file line numberDiff line numberDiff line change
@@ -1618,5 +1618,22 @@ extern void copy_user_huge_page(struct page *dst, struct page *src,
16181618
unsigned int pages_per_huge_page);
16191619
#endif /* CONFIG_TRANSPARENT_HUGEPAGE || CONFIG_HUGETLBFS */
16201620

1621+
#ifdef CONFIG_DEBUG_PAGEALLOC
1622+
extern unsigned int _debug_guardpage_minorder;
1623+
1624+
static inline unsigned int debug_guardpage_minorder(void)
1625+
{
1626+
return _debug_guardpage_minorder;
1627+
}
1628+
1629+
static inline bool page_is_guard(struct page *page)
1630+
{
1631+
return test_bit(PAGE_DEBUG_FLAG_GUARD, &page->debug_flags);
1632+
}
1633+
#else
1634+
static inline unsigned int debug_guardpage_minorder(void) { return 0; }
1635+
static inline bool page_is_guard(struct page *page) { return false; }
1636+
#endif /* CONFIG_DEBUG_PAGEALLOC */
1637+
16211638
#endif /* __KERNEL__ */
16221639
#endif /* _LINUX_MM_H */

include/linux/page-debug-flags.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
enum page_debug_flags {
1515
PAGE_DEBUG_FLAG_POISON, /* Page is poisoned */
16+
PAGE_DEBUG_FLAG_GUARD,
1617
};
1718

1819
/*
@@ -21,7 +22,8 @@ enum page_debug_flags {
2122
*/
2223

2324
#ifdef CONFIG_WANT_PAGE_DEBUG_FLAGS
24-
#if !defined(CONFIG_PAGE_POISONING) \
25+
#if !defined(CONFIG_PAGE_POISONING) && \
26+
!defined(CONFIG_PAGE_GUARD) \
2527
/* && !defined(CONFIG_PAGE_DEBUG_SOMETHING_ELSE) && ... */
2628
#error WANT_PAGE_DEBUG_FLAGS is turned on with no debug features!
2729
#endif

mm/Kconfig.debug

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ config DEBUG_PAGEALLOC
44
depends on !HIBERNATION || ARCH_SUPPORTS_DEBUG_PAGEALLOC && !PPC && !SPARC
55
depends on !KMEMCHECK
66
select PAGE_POISONING if !ARCH_SUPPORTS_DEBUG_PAGEALLOC
7+
select PAGE_GUARD if ARCH_SUPPORTS_DEBUG_PAGEALLOC
78
---help---
89
Unmap pages from the kernel linear mapping after free_pages().
910
This results in a large slowdown, but helps to find certain types
@@ -22,3 +23,7 @@ config WANT_PAGE_DEBUG_FLAGS
2223
config PAGE_POISONING
2324
bool
2425
select WANT_PAGE_DEBUG_FLAGS
26+
27+
config PAGE_GUARD
28+
bool
29+
select WANT_PAGE_DEBUG_FLAGS

mm/page_alloc.c

+69-6
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
#include <linux/ftrace_event.h>
5858
#include <linux/memcontrol.h>
5959
#include <linux/prefetch.h>
60+
#include <linux/page-debug-flags.h>
6061

6162
#include <asm/tlbflush.h>
6263
#include <asm/div64.h>
@@ -388,6 +389,37 @@ static inline void prep_zero_page(struct page *page, int order, gfp_t gfp_flags)
388389
clear_highpage(page + i);
389390
}
390391

392+
#ifdef CONFIG_DEBUG_PAGEALLOC
393+
unsigned int _debug_guardpage_minorder;
394+
395+
static int __init debug_guardpage_minorder_setup(char *buf)
396+
{
397+
unsigned long res;
398+
399+
if (kstrtoul(buf, 10, &res) < 0 || res > MAX_ORDER / 2) {
400+
printk(KERN_ERR "Bad debug_guardpage_minorder value\n");
401+
return 0;
402+
}
403+
_debug_guardpage_minorder = res;
404+
printk(KERN_INFO "Setting debug_guardpage_minorder to %lu\n", res);
405+
return 0;
406+
}
407+
__setup("debug_guardpage_minorder=", debug_guardpage_minorder_setup);
408+
409+
static inline void set_page_guard_flag(struct page *page)
410+
{
411+
__set_bit(PAGE_DEBUG_FLAG_GUARD, &page->debug_flags);
412+
}
413+
414+
static inline void clear_page_guard_flag(struct page *page)
415+
{
416+
__clear_bit(PAGE_DEBUG_FLAG_GUARD, &page->debug_flags);
417+
}
418+
#else
419+
static inline void set_page_guard_flag(struct page *page) { }
420+
static inline void clear_page_guard_flag(struct page *page) { }
421+
#endif
422+
391423
static inline void set_page_order(struct page *page, int order)
392424
{
393425
set_page_private(page, order);
@@ -445,6 +477,11 @@ static inline int page_is_buddy(struct page *page, struct page *buddy,
445477
if (page_zone_id(page) != page_zone_id(buddy))
446478
return 0;
447479

480+
if (page_is_guard(buddy) && page_order(buddy) == order) {
481+
VM_BUG_ON(page_count(buddy) != 0);
482+
return 1;
483+
}
484+
448485
if (PageBuddy(buddy) && page_order(buddy) == order) {
449486
VM_BUG_ON(page_count(buddy) != 0);
450487
return 1;
@@ -501,11 +538,19 @@ static inline void __free_one_page(struct page *page,
501538
buddy = page + (buddy_idx - page_idx);
502539
if (!page_is_buddy(page, buddy, order))
503540
break;
504-
505-
/* Our buddy is free, merge with it and move up one order. */
506-
list_del(&buddy->lru);
507-
zone->free_area[order].nr_free--;
508-
rmv_page_order(buddy);
541+
/*
542+
* Our buddy is free or it is CONFIG_DEBUG_PAGEALLOC guard page,
543+
* merge with it and move up one order.
544+
*/
545+
if (page_is_guard(buddy)) {
546+
clear_page_guard_flag(buddy);
547+
set_page_private(page, 0);
548+
__mod_zone_page_state(zone, NR_FREE_PAGES, 1 << order);
549+
} else {
550+
list_del(&buddy->lru);
551+
zone->free_area[order].nr_free--;
552+
rmv_page_order(buddy);
553+
}
509554
combined_idx = buddy_idx & page_idx;
510555
page = page + (combined_idx - page_idx);
511556
page_idx = combined_idx;
@@ -731,6 +776,23 @@ static inline void expand(struct zone *zone, struct page *page,
731776
high--;
732777
size >>= 1;
733778
VM_BUG_ON(bad_range(zone, &page[size]));
779+
780+
#ifdef CONFIG_DEBUG_PAGEALLOC
781+
if (high < debug_guardpage_minorder()) {
782+
/*
783+
* Mark as guard pages (or page), that will allow to
784+
* merge back to allocator when buddy will be freed.
785+
* Corresponding page table entries will not be touched,
786+
* pages will stay not present in virtual address space
787+
*/
788+
INIT_LIST_HEAD(&page[size].lru);
789+
set_page_guard_flag(&page[size]);
790+
set_page_private(&page[size], high);
791+
/* Guard pages are not available for any usage */
792+
__mod_zone_page_state(zone, NR_FREE_PAGES, -(1 << high));
793+
continue;
794+
}
795+
#endif
734796
list_add(&page[size].lru, &area->free_list[migratetype]);
735797
area->nr_free++;
736798
set_page_order(&page[size], high);
@@ -1754,7 +1816,8 @@ void warn_alloc_failed(gfp_t gfp_mask, int order, const char *fmt, ...)
17541816
{
17551817
unsigned int filter = SHOW_MEM_FILTER_NODES;
17561818

1757-
if ((gfp_mask & __GFP_NOWARN) || !__ratelimit(&nopage_rs))
1819+
if ((gfp_mask & __GFP_NOWARN) || !__ratelimit(&nopage_rs) ||
1820+
debug_guardpage_minorder() > 0)
17581821
return;
17591822

17601823
/*

0 commit comments

Comments
 (0)