Skip to content

Commit

Permalink
mm/page_poisoning.c: allow for zero poisoning
Browse files Browse the repository at this point in the history
By default, page poisoning uses a poison value (0xaa) on free.  If this
is changed to 0, the page is not only sanitized but zeroing on alloc
with __GFP_ZERO can be skipped as well.  The tradeoff is that detecting
corruption from the poisoning is harder to detect.  This feature also
cannot be used with hibernation since pages are not guaranteed to be
zeroed after hibernation.

Credit to Grsecurity/PaX team for inspiring this work

Signed-off-by: Laura Abbott <[email protected]>
Acked-by: Rafael J. Wysocki <[email protected]>
Cc: "Kirill A. Shutemov" <[email protected]>
Cc: Vlastimil Babka <[email protected]>
Cc: Michal Hocko <[email protected]>
Cc: Kees Cook <[email protected]>
Cc: Mathias Krause <[email protected]>
Cc: Dave Hansen <[email protected]>
Cc: Jianyu Zhan <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
labbott authored and torvalds committed Mar 15, 2016
1 parent 8823b1d commit 1414c7f
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 5 deletions.
2 changes: 2 additions & 0 deletions include/linux/mm.h
Original file line number Diff line number Diff line change
Expand Up @@ -2179,10 +2179,12 @@ extern int apply_to_page_range(struct mm_struct *mm, unsigned long address,
#ifdef CONFIG_PAGE_POISONING
extern bool page_poisoning_enabled(void);
extern void kernel_poison_pages(struct page *page, int numpages, int enable);
extern bool page_is_poisoned(struct page *page);
#else
static inline bool page_poisoning_enabled(void) { return false; }
static inline void kernel_poison_pages(struct page *page, int numpages,
int enable) { }
static inline bool page_is_poisoned(struct page *page) { return false; }
#endif

#ifdef CONFIG_DEBUG_PAGEALLOC
Expand Down
4 changes: 4 additions & 0 deletions include/linux/poison.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@
#define TIMER_ENTRY_STATIC ((void *) 0x300 + POISON_POINTER_DELTA)

/********** mm/debug-pagealloc.c **********/
#ifdef CONFIG_PAGE_POISONING_ZERO
#define PAGE_POISON 0x00
#else
#define PAGE_POISON 0xaa
#endif

/********** mm/page_alloc.c ************/

Expand Down
17 changes: 17 additions & 0 deletions kernel/power/hibernate.c
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,22 @@ static int __init kaslr_nohibernate_setup(char *str)
return nohibernate_setup(str);
}

static int __init page_poison_nohibernate_setup(char *str)
{
#ifdef CONFIG_PAGE_POISONING_ZERO
/*
* The zeroing option for page poison skips the checks on alloc.
* since hibernation doesn't save free pages there's no way to
* guarantee the pages will still be zeroed.
*/
if (!strcmp(str, "on")) {
pr_info("Disabling hibernation due to page poisoning\n");
return nohibernate_setup(str);
}
#endif
return 1;
}

__setup("noresume", noresume_setup);
__setup("resume_offset=", resume_offset_setup);
__setup("resume=", resume_setup);
Expand All @@ -1166,3 +1182,4 @@ __setup("resumewait", resumewait_setup);
__setup("resumedelay=", resumedelay_setup);
__setup("nohibernate", nohibernate_setup);
__setup("kaslr", kaslr_nohibernate_setup);
__setup("page_poison=", page_poison_nohibernate_setup);
14 changes: 14 additions & 0 deletions mm/Kconfig.debug
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,17 @@ config PAGE_POISONING_NO_SANITY

If you are only interested in sanitization, say Y. Otherwise
say N.

config PAGE_POISONING_ZERO
bool "Use zero for poisoning instead of random data"
depends on PAGE_POISONING
---help---
Instead of using the existing poison value, fill the pages with
zeros. This makes it harder to detect when errors are occurring
due to sanitization but the zeroing at free means that it is
no longer necessary to write zeros when GFP_ZERO is used on
allocation.

Enabling page poisoning with this option will disable hibernation

If unsure, say N
11 changes: 10 additions & 1 deletion mm/page_alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1405,15 +1405,24 @@ static inline int check_new_page(struct page *page)
return 0;
}

static inline bool free_pages_prezeroed(bool poisoned)
{
return IS_ENABLED(CONFIG_PAGE_POISONING_ZERO) &&
page_poisoning_enabled() && poisoned;
}

static int prep_new_page(struct page *page, unsigned int order, gfp_t gfp_flags,
int alloc_flags)
{
int i;
bool poisoned = true;

for (i = 0; i < (1 << order); i++) {
struct page *p = page + i;
if (unlikely(check_new_page(p)))
return 1;
if (poisoned)
poisoned &= page_is_poisoned(p);
}

set_page_private(page, 0);
Expand All @@ -1424,7 +1433,7 @@ static int prep_new_page(struct page *page, unsigned int order, gfp_t gfp_flags,
kernel_poison_pages(page, 1 << order, 1);
kasan_alloc_pages(page, order);

if (gfp_flags & __GFP_ZERO)
if (!free_pages_prezeroed(poisoned) && (gfp_flags & __GFP_ZERO))
for (i = 0; i < (1 << order); i++)
clear_highpage(page + i);

Expand Down
10 changes: 8 additions & 2 deletions mm/page_ext.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,15 @@ struct page_ext *lookup_page_ext(struct page *page)
struct page_ext *base;

base = NODE_DATA(page_to_nid(page))->node_page_ext;
#ifdef CONFIG_DEBUG_VM
#if defined(CONFIG_DEBUG_VM) || defined(CONFIG_PAGE_POISONING)
/*
* The sanity checks the page allocator does upon freeing a
* page can reach here before the page_ext arrays are
* allocated when feeding a range of pages to the allocator
* for the first time during bootup or memory hotplug.
*
* This check is also necessary for ensuring page poisoning
* works as expected when enabled
*/
if (unlikely(!base))
return NULL;
Expand Down Expand Up @@ -180,12 +183,15 @@ struct page_ext *lookup_page_ext(struct page *page)
{
unsigned long pfn = page_to_pfn(page);
struct mem_section *section = __pfn_to_section(pfn);
#ifdef CONFIG_DEBUG_VM
#if defined(CONFIG_DEBUG_VM) || defined(CONFIG_PAGE_POISONING)
/*
* The sanity checks the page allocator does upon freeing a
* page can reach here before the page_ext arrays are
* allocated when feeding a range of pages to the allocator
* for the first time during bootup or memory hotplug.
*
* This check is also necessary for ensuring page poisoning
* works as expected when enabled
*/
if (!section->page_ext)
return NULL;
Expand Down
7 changes: 5 additions & 2 deletions mm/page_poison.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,14 @@ static inline void clear_page_poison(struct page *page)
__clear_bit(PAGE_EXT_DEBUG_POISON, &page_ext->flags);
}

static inline bool page_poison(struct page *page)
bool page_is_poisoned(struct page *page)
{
struct page_ext *page_ext;

page_ext = lookup_page_ext(page);
if (!page_ext)
return false;

return test_bit(PAGE_EXT_DEBUG_POISON, &page_ext->flags);
}

Expand Down Expand Up @@ -137,7 +140,7 @@ static void unpoison_page(struct page *page)
{
void *addr;

if (!page_poison(page))
if (!page_is_poisoned(page))
return;

addr = kmap_atomic(page);
Expand Down

0 comments on commit 1414c7f

Please sign in to comment.