mm: init_mlocked_on_free_v3
Implements the "init_mlocked_on_free" boot option. When this boot option
is enabled, any mlock'ed pages are zeroed on free. If
the pages are munlock'ed beforehand, no initialization takes place.
This boot option is meant to combat the performance hit of
"init_on_free" as reported in commit 6471384af2
("mm: security:
introduce init_on_alloc=1 and init_on_free=1 boot options"). With
"init_mlocked_on_free=1" only relevant data is freed while everything
else is left untouched by the kernel. Correspondingly, this patch
introduces no performance hit for unmapping non-mlock'ed memory. The
unmapping overhead for purely mlocked memory was measured to be
approximately 13%. Realistically, most systems mlock only a fraction of
the total memory so the real-world system overhead should be close to
zero.
Optimally, userspace programs clear any key material or other
confidential memory before exit and munlock the according memory
regions. If a program crashes, userspace key managers fail to do this
job. Accordingly, no munlock operations are performed so the data is
caught and zeroed by the kernel. Should the program not crash, all
memory will ideally be munlocked so no overhead is caused.
CONFIG_INIT_MLOCKED_ON_FREE_DEFAULT_ON can be set to enable
"init_mlocked_on_free" by default.
Link: https://lkml.kernel.org/r/20240329145605.149917-1-yjnworkstation@gmail.com
Signed-off-by: York Jasper Niebuhr <yjnworkstation@gmail.com>
Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
Cc: York Jasper Niebuhr <yjnworkstation@gmail.com>
Cc: Kees Cook <keescook@chromium.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
parent
6c47de3be3
commit
ba42b524a0
|
@ -2148,6 +2148,12 @@
|
||||||
Format: 0 | 1
|
Format: 0 | 1
|
||||||
Default set by CONFIG_INIT_ON_FREE_DEFAULT_ON.
|
Default set by CONFIG_INIT_ON_FREE_DEFAULT_ON.
|
||||||
|
|
||||||
|
init_mlocked_on_free= [MM] Fill freed userspace memory with zeroes if
|
||||||
|
it was mlock'ed and not explicitly munlock'ed
|
||||||
|
afterwards.
|
||||||
|
Format: 0 | 1
|
||||||
|
Default set by CONFIG_INIT_MLOCKED_ON_FREE_DEFAULT_ON
|
||||||
|
|
||||||
init_pkru= [X86] Specify the default memory protection keys rights
|
init_pkru= [X86] Specify the default memory protection keys rights
|
||||||
register contents for all processes. 0x55555554 by
|
register contents for all processes. 0x55555554 by
|
||||||
default (disallow access to all but pkey 0). Can
|
default (disallow access to all but pkey 0). Can
|
||||||
|
|
|
@ -3762,7 +3762,14 @@ DECLARE_STATIC_KEY_MAYBE(CONFIG_INIT_ON_FREE_DEFAULT_ON, init_on_free);
|
||||||
static inline bool want_init_on_free(void)
|
static inline bool want_init_on_free(void)
|
||||||
{
|
{
|
||||||
return static_branch_maybe(CONFIG_INIT_ON_FREE_DEFAULT_ON,
|
return static_branch_maybe(CONFIG_INIT_ON_FREE_DEFAULT_ON,
|
||||||
&init_on_free);
|
&init_on_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
DECLARE_STATIC_KEY_MAYBE(CONFIG_INIT_MLOCKED_ON_FREE_DEFAULT_ON, init_mlocked_on_free);
|
||||||
|
static inline bool want_init_mlocked_on_free(void)
|
||||||
|
{
|
||||||
|
return static_branch_maybe(CONFIG_INIT_MLOCKED_ON_FREE_DEFAULT_ON,
|
||||||
|
&init_mlocked_on_free);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern bool _debug_pagealloc_enabled_early;
|
extern bool _debug_pagealloc_enabled_early;
|
||||||
|
|
|
@ -506,6 +506,7 @@ extern void __putback_isolated_page(struct page *page, unsigned int order,
|
||||||
extern void memblock_free_pages(struct page *page, unsigned long pfn,
|
extern void memblock_free_pages(struct page *page, unsigned long pfn,
|
||||||
unsigned int order);
|
unsigned int order);
|
||||||
extern void __free_pages_core(struct page *page, unsigned int order);
|
extern void __free_pages_core(struct page *page, unsigned int order);
|
||||||
|
extern void kernel_init_pages(struct page *page, int numpages);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This will have no effect, other than possibly generating a warning, if the
|
* This will have no effect, other than possibly generating a warning, if the
|
||||||
|
|
|
@ -1506,6 +1506,12 @@ static __always_inline void zap_present_folio_ptes(struct mmu_gather *tlb,
|
||||||
if (unlikely(page_mapcount(page) < 0))
|
if (unlikely(page_mapcount(page) < 0))
|
||||||
print_bad_pte(vma, addr, ptent, page);
|
print_bad_pte(vma, addr, ptent, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (want_init_mlocked_on_free() && folio_test_mlocked(folio) &&
|
||||||
|
!delay_rmap && folio_test_anon(folio)) {
|
||||||
|
kernel_init_pages(page, folio_nr_pages(folio));
|
||||||
|
}
|
||||||
|
|
||||||
if (unlikely(__tlb_remove_folio_pages(tlb, page, nr, delay_rmap))) {
|
if (unlikely(__tlb_remove_folio_pages(tlb, page, nr, delay_rmap))) {
|
||||||
*force_flush = true;
|
*force_flush = true;
|
||||||
*force_break = true;
|
*force_break = true;
|
||||||
|
|
43
mm/mm_init.c
43
mm/mm_init.c
|
@ -2522,6 +2522,9 @@ EXPORT_SYMBOL(init_on_alloc);
|
||||||
DEFINE_STATIC_KEY_MAYBE(CONFIG_INIT_ON_FREE_DEFAULT_ON, init_on_free);
|
DEFINE_STATIC_KEY_MAYBE(CONFIG_INIT_ON_FREE_DEFAULT_ON, init_on_free);
|
||||||
EXPORT_SYMBOL(init_on_free);
|
EXPORT_SYMBOL(init_on_free);
|
||||||
|
|
||||||
|
DEFINE_STATIC_KEY_MAYBE(CONFIG_INIT_MLOCKED_ON_FREE_DEFAULT_ON, init_mlocked_on_free);
|
||||||
|
EXPORT_SYMBOL(init_mlocked_on_free);
|
||||||
|
|
||||||
static bool _init_on_alloc_enabled_early __read_mostly
|
static bool _init_on_alloc_enabled_early __read_mostly
|
||||||
= IS_ENABLED(CONFIG_INIT_ON_ALLOC_DEFAULT_ON);
|
= IS_ENABLED(CONFIG_INIT_ON_ALLOC_DEFAULT_ON);
|
||||||
static int __init early_init_on_alloc(char *buf)
|
static int __init early_init_on_alloc(char *buf)
|
||||||
|
@ -2539,6 +2542,14 @@ static int __init early_init_on_free(char *buf)
|
||||||
}
|
}
|
||||||
early_param("init_on_free", early_init_on_free);
|
early_param("init_on_free", early_init_on_free);
|
||||||
|
|
||||||
|
static bool _init_mlocked_on_free_enabled_early __read_mostly
|
||||||
|
= IS_ENABLED(CONFIG_INIT_MLOCKED_ON_FREE_DEFAULT_ON);
|
||||||
|
static int __init early_init_mlocked_on_free(char *buf)
|
||||||
|
{
|
||||||
|
return kstrtobool(buf, &_init_mlocked_on_free_enabled_early);
|
||||||
|
}
|
||||||
|
early_param("init_mlocked_on_free", early_init_mlocked_on_free);
|
||||||
|
|
||||||
DEFINE_STATIC_KEY_MAYBE(CONFIG_DEBUG_VM, check_pages_enabled);
|
DEFINE_STATIC_KEY_MAYBE(CONFIG_DEBUG_VM, check_pages_enabled);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2566,12 +2577,21 @@ static void __init mem_debugging_and_hardening_init(void)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if ((_init_on_alloc_enabled_early || _init_on_free_enabled_early) &&
|
if ((_init_on_alloc_enabled_early || _init_on_free_enabled_early ||
|
||||||
|
_init_mlocked_on_free_enabled_early) &&
|
||||||
page_poisoning_requested) {
|
page_poisoning_requested) {
|
||||||
pr_info("mem auto-init: CONFIG_PAGE_POISONING is on, "
|
pr_info("mem auto-init: CONFIG_PAGE_POISONING is on, "
|
||||||
"will take precedence over init_on_alloc and init_on_free\n");
|
"will take precedence over init_on_alloc, init_on_free "
|
||||||
|
"and init_mlocked_on_free\n");
|
||||||
_init_on_alloc_enabled_early = false;
|
_init_on_alloc_enabled_early = false;
|
||||||
_init_on_free_enabled_early = false;
|
_init_on_free_enabled_early = false;
|
||||||
|
_init_mlocked_on_free_enabled_early = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_init_mlocked_on_free_enabled_early && _init_on_free_enabled_early) {
|
||||||
|
pr_info("mem auto-init: init_on_free is on, "
|
||||||
|
"will take precedence over init_mlocked_on_free\n");
|
||||||
|
_init_mlocked_on_free_enabled_early = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_init_on_alloc_enabled_early) {
|
if (_init_on_alloc_enabled_early) {
|
||||||
|
@ -2588,9 +2608,17 @@ static void __init mem_debugging_and_hardening_init(void)
|
||||||
static_branch_disable(&init_on_free);
|
static_branch_disable(&init_on_free);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_KMSAN) &&
|
if (_init_mlocked_on_free_enabled_early) {
|
||||||
(_init_on_alloc_enabled_early || _init_on_free_enabled_early))
|
want_check_pages = true;
|
||||||
pr_info("mem auto-init: please make sure init_on_alloc and init_on_free are disabled when running KMSAN\n");
|
static_branch_enable(&init_mlocked_on_free);
|
||||||
|
} else {
|
||||||
|
static_branch_disable(&init_mlocked_on_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_KMSAN) && (_init_on_alloc_enabled_early ||
|
||||||
|
_init_on_free_enabled_early || _init_mlocked_on_free_enabled_early))
|
||||||
|
pr_info("mem auto-init: please make sure init_on_alloc, init_on_free and "
|
||||||
|
"init_mlocked_on_free are disabled when running KMSAN\n");
|
||||||
|
|
||||||
#ifdef CONFIG_DEBUG_PAGEALLOC
|
#ifdef CONFIG_DEBUG_PAGEALLOC
|
||||||
if (debug_pagealloc_enabled()) {
|
if (debug_pagealloc_enabled()) {
|
||||||
|
@ -2629,9 +2657,10 @@ static void __init report_meminit(void)
|
||||||
else
|
else
|
||||||
stack = "off";
|
stack = "off";
|
||||||
|
|
||||||
pr_info("mem auto-init: stack:%s, heap alloc:%s, heap free:%s\n",
|
pr_info("mem auto-init: stack:%s, heap alloc:%s, heap free:%s, mlocked free:%s\n",
|
||||||
stack, want_init_on_alloc(GFP_KERNEL) ? "on" : "off",
|
stack, want_init_on_alloc(GFP_KERNEL) ? "on" : "off",
|
||||||
want_init_on_free() ? "on" : "off");
|
want_init_on_free() ? "on" : "off",
|
||||||
|
want_init_mlocked_on_free() ? "on" : "off");
|
||||||
if (want_init_on_free())
|
if (want_init_on_free())
|
||||||
pr_info("mem auto-init: clearing system memory may take some time...\n");
|
pr_info("mem auto-init: clearing system memory may take some time...\n");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1032,7 +1032,7 @@ static inline bool should_skip_kasan_poison(struct page *page)
|
||||||
return page_kasan_tag(page) == KASAN_TAG_KERNEL;
|
return page_kasan_tag(page) == KASAN_TAG_KERNEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void kernel_init_pages(struct page *page, int numpages)
|
void kernel_init_pages(struct page *page, int numpages)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,21 @@ config INIT_ON_FREE_DEFAULT_ON
|
||||||
touching "cold" memory areas. Most cases see 3-5% impact. Some
|
touching "cold" memory areas. Most cases see 3-5% impact. Some
|
||||||
synthetic workloads have measured as high as 8%.
|
synthetic workloads have measured as high as 8%.
|
||||||
|
|
||||||
|
config INIT_MLOCKED_ON_FREE_DEFAULT_ON
|
||||||
|
bool "Enable mlocked memory zeroing on free"
|
||||||
|
depends on !KMSAN
|
||||||
|
help
|
||||||
|
This config has the effect of setting "init_mlocked_on_free=1"
|
||||||
|
on the kernel command line. If it is enabled, all mlocked process
|
||||||
|
memory is zeroed when freed. This restriction to mlocked memory
|
||||||
|
improves performance over "init_on_free" but can still be used to
|
||||||
|
protect confidential data like key material from content exposures
|
||||||
|
to other processes, as well as live forensics and cold boot attacks.
|
||||||
|
Any non-mlocked memory is not cleared before it is reassigned. This
|
||||||
|
configuration can be overwritten by setting "init_mlocked_on_free=0"
|
||||||
|
on the command line. The "init_on_free" boot option takes
|
||||||
|
precedence over "init_mlocked_on_free".
|
||||||
|
|
||||||
config CC_HAS_ZERO_CALL_USED_REGS
|
config CC_HAS_ZERO_CALL_USED_REGS
|
||||||
def_bool $(cc-option,-fzero-call-used-regs=used-gpr)
|
def_bool $(cc-option,-fzero-call-used-regs=used-gpr)
|
||||||
# https://github.com/ClangBuiltLinux/linux/issues/1766
|
# https://github.com/ClangBuiltLinux/linux/issues/1766
|
||||||
|
|
Loading…
Reference in New Issue