Skip to content

Commit 8c06c77

Browse files
hansendcIngo Molnar
authored andcommitted
x86/pti: Leave kernel text global for !PCID
Global pages are bad for hardening because they potentially let an exploit read the kernel image via a Meltdown-style attack which makes it easier to find gadgets. But, global pages are good for performance because they reduce TLB misses when making user/kernel transitions, especially when PCIDs are not available, such as on older hardware, or where a hypervisor has disabled them for some reason. This patch implements a basic, sane policy: If you have PCIDs, you only map a minimal amount of kernel text global. If you do not have PCIDs, you map all kernel text global. This policy effectively makes PCIDs something that not only adds performance but a little bit of hardening as well. I ran a simple "lseek" microbenchmark[1] to test the benefit on a modern Atom microserver. Most of the benefit comes from applying the series before this patch ("entry only"), but there is still a signifiant benefit from this patch. No Global Lines (baseline ): 6077741 lseeks/sec 88 Global Lines (entry only): 7528609 lseeks/sec (+23.9%) 94 Global Lines (this patch): 8433111 lseeks/sec (+38.8%) [1.] https://github.com/antonblanchard/will-it-scale/blob/master/tests/lseek1.c Signed-off-by: Dave Hansen <[email protected]> Cc: Andrea Arcangeli <[email protected]> Cc: Andy Lutomirski <[email protected]> Cc: Arjan van de Ven <[email protected]> Cc: Borislav Petkov <[email protected]> Cc: Dan Williams <[email protected]> Cc: David Woodhouse <[email protected]> Cc: Greg Kroah-Hartman <[email protected]> Cc: Hugh Dickins <[email protected]> Cc: Josh Poimboeuf <[email protected]> Cc: Juergen Gross <[email protected]> Cc: Kees Cook <[email protected]> Cc: Linus Torvalds <[email protected]> Cc: Nadav Amit <[email protected]> Cc: Peter Zijlstra <[email protected]> Cc: Thomas Gleixner <[email protected]> Cc: [email protected] Link: http://lkml.kernel.org/r/[email protected] Signed-off-by: Ingo Molnar <[email protected]>
1 parent 39114b7 commit 8c06c77

File tree

3 files changed

+82
-4
lines changed

3 files changed

+82
-4
lines changed

arch/x86/include/asm/pti.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
#ifdef CONFIG_PAGE_TABLE_ISOLATION
77
extern void pti_init(void);
88
extern void pti_check_boottime_disable(void);
9+
extern void pti_clone_kernel_text(void);
910
#else
1011
static inline void pti_check_boottime_disable(void) { }
12+
static inline void pti_clone_kernel_text(void) { }
1113
#endif
1214

1315
#endif /* __ASSEMBLY__ */

arch/x86/mm/init_64.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,6 +1290,12 @@ void mark_rodata_ro(void)
12901290
(unsigned long) __va(__pa_symbol(_sdata)));
12911291

12921292
debug_checkwx();
1293+
1294+
/*
1295+
* Do this after all of the manipulation of the
1296+
* kernel text page tables are complete.
1297+
*/
1298+
pti_clone_kernel_text();
12931299
}
12941300

12951301
int kern_addr_valid(unsigned long addr)

arch/x86/mm/pti.c

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,31 +66,46 @@ static void __init pti_print_if_secure(const char *reason)
6666
pr_info("%s\n", reason);
6767
}
6868

69+
enum pti_mode {
70+
PTI_AUTO = 0,
71+
PTI_FORCE_OFF,
72+
PTI_FORCE_ON
73+
} pti_mode;
74+
6975
void __init pti_check_boottime_disable(void)
7076
{
7177
char arg[5];
7278
int ret;
7379

80+
/* Assume mode is auto unless overridden. */
81+
pti_mode = PTI_AUTO;
82+
7483
if (hypervisor_is_type(X86_HYPER_XEN_PV)) {
84+
pti_mode = PTI_FORCE_OFF;
7585
pti_print_if_insecure("disabled on XEN PV.");
7686
return;
7787
}
7888

7989
ret = cmdline_find_option(boot_command_line, "pti", arg, sizeof(arg));
8090
if (ret > 0) {
8191
if (ret == 3 && !strncmp(arg, "off", 3)) {
92+
pti_mode = PTI_FORCE_OFF;
8293
pti_print_if_insecure("disabled on command line.");
8394
return;
8495
}
8596
if (ret == 2 && !strncmp(arg, "on", 2)) {
97+
pti_mode = PTI_FORCE_ON;
8698
pti_print_if_secure("force enabled on command line.");
8799
goto enable;
88100
}
89-
if (ret == 4 && !strncmp(arg, "auto", 4))
101+
if (ret == 4 && !strncmp(arg, "auto", 4)) {
102+
pti_mode = PTI_AUTO;
90103
goto autosel;
104+
}
91105
}
92106

93107
if (cmdline_find_option_bool(boot_command_line, "nopti")) {
108+
pti_mode = PTI_FORCE_OFF;
94109
pti_print_if_insecure("disabled on command line.");
95110
return;
96111
}
@@ -149,7 +164,7 @@ pgd_t __pti_set_user_pgd(pgd_t *pgdp, pgd_t pgd)
149164
*
150165
* Returns a pointer to a P4D on success, or NULL on failure.
151166
*/
152-
static __init p4d_t *pti_user_pagetable_walk_p4d(unsigned long address)
167+
static p4d_t *pti_user_pagetable_walk_p4d(unsigned long address)
153168
{
154169
pgd_t *pgd = kernel_to_user_pgdp(pgd_offset_k(address));
155170
gfp_t gfp = (GFP_KERNEL | __GFP_NOTRACK | __GFP_ZERO);
@@ -177,7 +192,7 @@ static __init p4d_t *pti_user_pagetable_walk_p4d(unsigned long address)
177192
*
178193
* Returns a pointer to a PMD on success, or NULL on failure.
179194
*/
180-
static __init pmd_t *pti_user_pagetable_walk_pmd(unsigned long address)
195+
static pmd_t *pti_user_pagetable_walk_pmd(unsigned long address)
181196
{
182197
gfp_t gfp = (GFP_KERNEL | __GFP_NOTRACK | __GFP_ZERO);
183198
p4d_t *p4d = pti_user_pagetable_walk_p4d(address);
@@ -267,7 +282,7 @@ static void __init pti_setup_vsyscall(void)
267282
static void __init pti_setup_vsyscall(void) { }
268283
#endif
269284

270-
static void __init
285+
static void
271286
pti_clone_pmds(unsigned long start, unsigned long end, pmdval_t clear)
272287
{
273288
unsigned long addr;
@@ -372,6 +387,58 @@ static void __init pti_clone_entry_text(void)
372387
_PAGE_RW);
373388
}
374389

390+
/*
391+
* Global pages and PCIDs are both ways to make kernel TLB entries
392+
* live longer, reduce TLB misses and improve kernel performance.
393+
* But, leaving all kernel text Global makes it potentially accessible
394+
* to Meltdown-style attacks which make it trivial to find gadgets or
395+
* defeat KASLR.
396+
*
397+
* Only use global pages when it is really worth it.
398+
*/
399+
static inline bool pti_kernel_image_global_ok(void)
400+
{
401+
/*
402+
* Systems with PCIDs get litlle benefit from global
403+
* kernel text and are not worth the downsides.
404+
*/
405+
if (cpu_feature_enabled(X86_FEATURE_PCID))
406+
return false;
407+
408+
/*
409+
* Only do global kernel image for pti=auto. Do the most
410+
* secure thing (not global) if pti=on specified.
411+
*/
412+
if (pti_mode != PTI_AUTO)
413+
return false;
414+
415+
/*
416+
* K8 may not tolerate the cleared _PAGE_RW on the userspace
417+
* global kernel image pages. Do the safe thing (disable
418+
* global kernel image). This is unlikely to ever be
419+
* noticed because PTI is disabled by default on AMD CPUs.
420+
*/
421+
if (boot_cpu_has(X86_FEATURE_K8))
422+
return false;
423+
424+
return true;
425+
}
426+
427+
/*
428+
* For some configurations, map all of kernel text into the user page
429+
* tables. This reduces TLB misses, especially on non-PCID systems.
430+
*/
431+
void pti_clone_kernel_text(void)
432+
{
433+
unsigned long start = PFN_ALIGN(_text);
434+
unsigned long end = ALIGN((unsigned long)_end, PMD_PAGE_SIZE);
435+
436+
if (!pti_kernel_image_global_ok())
437+
return;
438+
439+
pti_clone_pmds(start, end, _PAGE_RW);
440+
}
441+
375442
/*
376443
* This is the only user for it and it is not arch-generic like
377444
* the other set_memory.h functions. Just extern it.
@@ -388,6 +455,9 @@ void pti_set_kernel_image_nonglobal(void)
388455
unsigned long start = PFN_ALIGN(_text);
389456
unsigned long end = ALIGN((unsigned long)_end, PMD_PAGE_SIZE);
390457

458+
if (pti_kernel_image_global_ok())
459+
return;
460+
391461
pr_debug("set kernel image non-global\n");
392462

393463
set_memory_nonglobal(start, (end - start) >> PAGE_SHIFT);

0 commit comments

Comments
 (0)