Skip to content

Commit df4e817

Browse files
soleentorvalds
authored andcommitted
mm: page table check
Check user page table entries at the time they are added and removed. Allows to synchronously catch memory corruption issues related to double mapping. When a pte for an anonymous page is added into page table, we verify that this pte does not already point to a file backed page, and vice versa if this is a file backed page that is being added we verify that this page does not have an anonymous mapping We also enforce that read-only sharing for anonymous pages is allowed (i.e. cow after fork). All other sharing must be for file pages. Page table check allows to protect and debug cases where "struct page" metadata became corrupted for some reason. For example, when refcnt or mapcount become invalid. Link: https://lkml.kernel.org/r/[email protected] Signed-off-by: Pasha Tatashin <[email protected]> Cc: Aneesh Kumar K.V <[email protected]> Cc: Dave Hansen <[email protected]> Cc: David Rientjes <[email protected]> Cc: Frederic Weisbecker <[email protected]> Cc: Greg Thelen <[email protected]> Cc: "H. Peter Anvin" <[email protected]> Cc: Hugh Dickins <[email protected]> Cc: Ingo Molnar <[email protected]> Cc: Jiri Slaby <[email protected]> Cc: Jonathan Corbet <[email protected]> Cc: Kees Cook <[email protected]> Cc: Masahiro Yamada <[email protected]> Cc: Mike Rapoport <[email protected]> Cc: Muchun Song <[email protected]> Cc: Paul Turner <[email protected]> Cc: Peter Zijlstra <[email protected]> Cc: Sami Tolvanen <[email protected]> Cc: Thomas Gleixner <[email protected]> Cc: Wei Xu <[email protected]> Cc: Will Deacon <[email protected]> Signed-off-by: Andrew Morton <[email protected]> Signed-off-by: Linus Torvalds <[email protected]>
1 parent 08d5b29 commit df4e817

File tree

10 files changed

+519
-0
lines changed

10 files changed

+519
-0
lines changed

Documentation/vm/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ algorithms. If you are looking for advice on simply allocating memory, see the
3131
page_migration
3232
page_frags
3333
page_owner
34+
page_table_check
3435
remap_file_pages
3536
slub
3637
split_page_table_lock

Documentation/vm/page_table_check.rst

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
.. SPDX-License-Identifier: GPL-2.0
2+
3+
.. _page_table_check:
4+
5+
================
6+
Page Table Check
7+
================
8+
9+
Introduction
10+
============
11+
12+
Page table check allows to hardern the kernel by ensuring that some types of
13+
the memory corruptions are prevented.
14+
15+
Page table check performs extra verifications at the time when new pages become
16+
accessible from the userspace by getting their page table entries (PTEs PMDs
17+
etc.) added into the table.
18+
19+
In case of detected corruption, the kernel is crashed. There is a small
20+
performance and memory overhead associated with the page table check. Therefore,
21+
it is disabled by default, but can be optionally enabled on systems where the
22+
extra hardening outweighs the performance costs. Also, because page table check
23+
is synchronous, it can help with debugging double map memory corruption issues,
24+
by crashing kernel at the time wrong mapping occurs instead of later which is
25+
often the case with memory corruptions bugs.
26+
27+
Double mapping detection logic
28+
==============================
29+
30+
+-------------------+-------------------+-------------------+------------------+
31+
| Current Mapping | New mapping | Permissions | Rule |
32+
+===================+===================+===================+==================+
33+
| Anonymous | Anonymous | Read | Allow |
34+
+-------------------+-------------------+-------------------+------------------+
35+
| Anonymous | Anonymous | Read / Write | Prohibit |
36+
+-------------------+-------------------+-------------------+------------------+
37+
| Anonymous | Named | Any | Prohibit |
38+
+-------------------+-------------------+-------------------+------------------+
39+
| Named | Anonymous | Any | Prohibit |
40+
+-------------------+-------------------+-------------------+------------------+
41+
| Named | Named | Any | Allow |
42+
+-------------------+-------------------+-------------------+------------------+
43+
44+
Enabling Page Table Check
45+
=========================
46+
47+
Build kernel with:
48+
49+
- PAGE_TABLE_CHECK=y
50+
Note, it can only be enabled on platforms where ARCH_SUPPORTS_PAGE_TABLE_CHECK
51+
is available.
52+
53+
- Boot with 'page_table_check=on' kernel parameter.
54+
55+
Optionally, build kernel with PAGE_TABLE_CHECK_ENFORCED in order to have page
56+
table support without extra kernel parameter.

MAINTAINERS

+9
Original file line numberDiff line numberDiff line change
@@ -14387,6 +14387,15 @@ F: include/net/page_pool.h
1438714387
F: include/trace/events/page_pool.h
1438814388
F: net/core/page_pool.c
1438914389

14390+
PAGE TABLE CHECK
14391+
M: Pasha Tatashin <[email protected]>
14392+
M: Andrew Morton <[email protected]>
14393+
14394+
S: Maintained
14395+
F: Documentation/vm/page_table_check.rst
14396+
F: include/linux/page_table_check.h
14397+
F: mm/page_table_check.c
14398+
1439014399
PANASONIC LAPTOP ACPI EXTRAS DRIVER
1439114400
M: Kenneth Chan <[email protected]>
1439214401

arch/Kconfig

+3
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,9 @@ config HAVE_ARCH_PFN_VALID
12971297
config ARCH_SUPPORTS_DEBUG_PAGEALLOC
12981298
bool
12991299

1300+
config ARCH_SUPPORTS_PAGE_TABLE_CHECK
1301+
bool
1302+
13001303
config ARCH_SPLIT_ARG64
13011304
bool
13021305
help

include/linux/page_table_check.h

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
3+
/*
4+
* Copyright (c) 2021, Google LLC.
5+
* Pasha Tatashin <[email protected]>
6+
*/
7+
#ifndef __LINUX_PAGE_TABLE_CHECK_H
8+
#define __LINUX_PAGE_TABLE_CHECK_H
9+
10+
#ifdef CONFIG_PAGE_TABLE_CHECK
11+
#include <linux/jump_label.h>
12+
13+
extern struct static_key_true page_table_check_disabled;
14+
extern struct page_ext_operations page_table_check_ops;
15+
16+
void __page_table_check_zero(struct page *page, unsigned int order);
17+
void __page_table_check_pte_clear(struct mm_struct *mm, unsigned long addr,
18+
pte_t pte);
19+
void __page_table_check_pmd_clear(struct mm_struct *mm, unsigned long addr,
20+
pmd_t pmd);
21+
void __page_table_check_pud_clear(struct mm_struct *mm, unsigned long addr,
22+
pud_t pud);
23+
void __page_table_check_pte_set(struct mm_struct *mm, unsigned long addr,
24+
pte_t *ptep, pte_t pte);
25+
void __page_table_check_pmd_set(struct mm_struct *mm, unsigned long addr,
26+
pmd_t *pmdp, pmd_t pmd);
27+
void __page_table_check_pud_set(struct mm_struct *mm, unsigned long addr,
28+
pud_t *pudp, pud_t pud);
29+
30+
static inline void page_table_check_alloc(struct page *page, unsigned int order)
31+
{
32+
if (static_branch_likely(&page_table_check_disabled))
33+
return;
34+
35+
__page_table_check_zero(page, order);
36+
}
37+
38+
static inline void page_table_check_free(struct page *page, unsigned int order)
39+
{
40+
if (static_branch_likely(&page_table_check_disabled))
41+
return;
42+
43+
__page_table_check_zero(page, order);
44+
}
45+
46+
static inline void page_table_check_pte_clear(struct mm_struct *mm,
47+
unsigned long addr, pte_t pte)
48+
{
49+
if (static_branch_likely(&page_table_check_disabled))
50+
return;
51+
52+
__page_table_check_pte_clear(mm, addr, pte);
53+
}
54+
55+
static inline void page_table_check_pmd_clear(struct mm_struct *mm,
56+
unsigned long addr, pmd_t pmd)
57+
{
58+
if (static_branch_likely(&page_table_check_disabled))
59+
return;
60+
61+
__page_table_check_pmd_clear(mm, addr, pmd);
62+
}
63+
64+
static inline void page_table_check_pud_clear(struct mm_struct *mm,
65+
unsigned long addr, pud_t pud)
66+
{
67+
if (static_branch_likely(&page_table_check_disabled))
68+
return;
69+
70+
__page_table_check_pud_clear(mm, addr, pud);
71+
}
72+
73+
static inline void page_table_check_pte_set(struct mm_struct *mm,
74+
unsigned long addr, pte_t *ptep,
75+
pte_t pte)
76+
{
77+
if (static_branch_likely(&page_table_check_disabled))
78+
return;
79+
80+
__page_table_check_pte_set(mm, addr, ptep, pte);
81+
}
82+
83+
static inline void page_table_check_pmd_set(struct mm_struct *mm,
84+
unsigned long addr, pmd_t *pmdp,
85+
pmd_t pmd)
86+
{
87+
if (static_branch_likely(&page_table_check_disabled))
88+
return;
89+
90+
__page_table_check_pmd_set(mm, addr, pmdp, pmd);
91+
}
92+
93+
static inline void page_table_check_pud_set(struct mm_struct *mm,
94+
unsigned long addr, pud_t *pudp,
95+
pud_t pud)
96+
{
97+
if (static_branch_likely(&page_table_check_disabled))
98+
return;
99+
100+
__page_table_check_pud_set(mm, addr, pudp, pud);
101+
}
102+
103+
#else
104+
105+
static inline void page_table_check_alloc(struct page *page, unsigned int order)
106+
{
107+
}
108+
109+
static inline void page_table_check_free(struct page *page, unsigned int order)
110+
{
111+
}
112+
113+
static inline void page_table_check_pte_clear(struct mm_struct *mm,
114+
unsigned long addr, pte_t pte)
115+
{
116+
}
117+
118+
static inline void page_table_check_pmd_clear(struct mm_struct *mm,
119+
unsigned long addr, pmd_t pmd)
120+
{
121+
}
122+
123+
static inline void page_table_check_pud_clear(struct mm_struct *mm,
124+
unsigned long addr, pud_t pud)
125+
{
126+
}
127+
128+
static inline void page_table_check_pte_set(struct mm_struct *mm,
129+
unsigned long addr, pte_t *ptep,
130+
pte_t pte)
131+
{
132+
}
133+
134+
static inline void page_table_check_pmd_set(struct mm_struct *mm,
135+
unsigned long addr, pmd_t *pmdp,
136+
pmd_t pmd)
137+
{
138+
}
139+
140+
static inline void page_table_check_pud_set(struct mm_struct *mm,
141+
unsigned long addr, pud_t *pudp,
142+
pud_t pud)
143+
{
144+
}
145+
146+
#endif /* CONFIG_PAGE_TABLE_CHECK */
147+
#endif /* __LINUX_PAGE_TABLE_CHECK_H */

mm/Kconfig.debug

+24
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,30 @@ config PAGE_OWNER
6262

6363
If unsure, say N.
6464

65+
config PAGE_TABLE_CHECK
66+
bool "Check for invalid mappings in user page tables"
67+
depends on ARCH_SUPPORTS_PAGE_TABLE_CHECK
68+
select PAGE_EXTENSION
69+
help
70+
Check that anonymous page is not being mapped twice with read write
71+
permissions. Check that anonymous and file pages are not being
72+
erroneously shared. Since the checking is performed at the time
73+
entries are added and removed to user page tables, leaking, corruption
74+
and double mapping problems are detected synchronously.
75+
76+
If unsure say "n".
77+
78+
config PAGE_TABLE_CHECK_ENFORCED
79+
bool "Enforce the page table checking by default"
80+
depends on PAGE_TABLE_CHECK
81+
help
82+
Always enable page table checking. By default the page table checking
83+
is disabled, and can be optionally enabled via page_table_check=on
84+
kernel parameter. This config enforces that page table check is always
85+
enabled.
86+
87+
If unsure say "n".
88+
6589
config PAGE_POISONING
6690
bool "Poison pages after freeing"
6791
help

mm/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ obj-$(CONFIG_GENERIC_EARLY_IOREMAP) += early_ioremap.o
112112
obj-$(CONFIG_CMA) += cma.o
113113
obj-$(CONFIG_MEMORY_BALLOON) += balloon_compaction.o
114114
obj-$(CONFIG_PAGE_EXTENSION) += page_ext.o
115+
obj-$(CONFIG_PAGE_TABLE_CHECK) += page_table_check.o
115116
obj-$(CONFIG_CMA_DEBUGFS) += cma_debug.o
116117
obj-$(CONFIG_SECRETMEM) += secretmem.o
117118
obj-$(CONFIG_CMA_SYSFS) += cma_sysfs.o

mm/page_alloc.c

+4
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
#include <linux/sched/rt.h>
6464
#include <linux/sched/mm.h>
6565
#include <linux/page_owner.h>
66+
#include <linux/page_table_check.h>
6667
#include <linux/kthread.h>
6768
#include <linux/memcontrol.h>
6869
#include <linux/ftrace.h>
@@ -1307,6 +1308,7 @@ static __always_inline bool free_pages_prepare(struct page *page,
13071308
if (memcg_kmem_enabled() && PageMemcgKmem(page))
13081309
__memcg_kmem_uncharge_page(page, order);
13091310
reset_page_owner(page, order);
1311+
page_table_check_free(page, order);
13101312
return false;
13111313
}
13121314

@@ -1346,6 +1348,7 @@ static __always_inline bool free_pages_prepare(struct page *page,
13461348
page_cpupid_reset_last(page);
13471349
page->flags &= ~PAGE_FLAGS_CHECK_AT_PREP;
13481350
reset_page_owner(page, order);
1351+
page_table_check_free(page, order);
13491352

13501353
if (!PageHighMem(page)) {
13511354
debug_check_no_locks_freed(page_address(page),
@@ -2420,6 +2423,7 @@ inline void post_alloc_hook(struct page *page, unsigned int order,
24202423
}
24212424

24222425
set_page_owner(page, order, gfp_flags);
2426+
page_table_check_alloc(page, order);
24232427
}
24242428

24252429
static void prep_new_page(struct page *page, unsigned int order, gfp_t gfp_flags,

mm/page_ext.c

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <linux/kmemleak.h>
99
#include <linux/page_owner.h>
1010
#include <linux/page_idle.h>
11+
#include <linux/page_table_check.h>
1112

1213
/*
1314
* struct page extension
@@ -75,6 +76,9 @@ static struct page_ext_operations *page_ext_ops[] = {
7576
#if defined(CONFIG_PAGE_IDLE_FLAG) && !defined(CONFIG_64BIT)
7677
&page_idle_ops,
7778
#endif
79+
#ifdef CONFIG_PAGE_TABLE_CHECK
80+
&page_table_check_ops,
81+
#endif
7882
};
7983

8084
unsigned long page_ext_size = sizeof(struct page_ext);

0 commit comments

Comments
 (0)