diff mbox series

[1/3] mm, page_alloc: do not rely on the order of page_poison and init_on_alloc/free parameters

Message ID 20201026173358.14704-2-vbabka@suse.cz (mailing list archive)
State New, archived
Headers show
Series optimize handling of memory debugging parameters | expand

Commit Message

Vlastimil Babka Oct. 26, 2020, 5:33 p.m. UTC
Enabling page_poison=1 together with init_on_alloc=1 or init_on_free=1 produces
a warning in dmesg that page_poison takes precendence. However, as these
warnings are printed in early_param handlers for init_on_alloc/free, they are
not printed if page_poison is enabled later on the command line (handlers are
called in the order of their parameters), or when init_on_alloc/free is always
enabled by the respective config option - before the page_poison early param
handler is called, it is not considered to be enabled. This is inconsistent.

We can remove the dependency on order by making the init_on_* parameters only
set a boolean variable, and postponing the evaluation after all early params
have been processed. Introduce a new init_mem_debugging() function for that,
and move the related debug_pagealloc processing there as well.

As a result init_mem_debugging() knows always accurately if init_on_* and/or
page_poison options were enabled. Thus we can also optimize want_init_on_alloc()
and want_init_on_free(). We don't need to check page_poisoning_enabled() there,
we can instead not enable the init_on_* tracepoint at all, if page poisoning is
enabled. This results in a simpler and more effective code.

Signed-off-by: Vlastimil Babka <vbabka@suse.cz>
---
 include/linux/mm.h | 20 ++--------
 init/main.c        |  2 +-
 mm/page_alloc.c    | 94 +++++++++++++++++++++++-----------------------
 3 files changed, 50 insertions(+), 66 deletions(-)

Comments

David Hildenbrand Oct. 27, 2020, 9:03 a.m. UTC | #1
On 26.10.20 18:33, Vlastimil Babka wrote:
> Enabling page_poison=1 together with init_on_alloc=1 or init_on_free=1 produces
> a warning in dmesg that page_poison takes precendence. However, as these
> warnings are printed in early_param handlers for init_on_alloc/free, they are
> not printed if page_poison is enabled later on the command line (handlers are
> called in the order of their parameters), or when init_on_alloc/free is always
> enabled by the respective config option - before the page_poison early param
> handler is called, it is not considered to be enabled. This is inconsistent.
> 
> We can remove the dependency on order by making the init_on_* parameters only
> set a boolean variable, and postponing the evaluation after all early params
> have been processed. Introduce a new init_mem_debugging() function for that,
> and move the related debug_pagealloc processing there as well.

init_mem_debugging() is somewhat sub-optimal - init_on_alloc=1 or 
init_on_free=1 are rather security hardening mechanisms.

... I wondered if this could be the place to initialize any kind of mm 
parameters in the future. Like init_mem_params() or so.

> 
> As a result init_mem_debugging() knows always accurately if init_on_* and/or
> page_poison options were enabled. Thus we can also optimize want_init_on_alloc()
> and want_init_on_free(). We don't need to check page_poisoning_enabled() there,
> we can instead not enable the init_on_* tracepoint at all, if page poisoning is
> enabled. This results in a simpler and more effective code.

LGTM

Reviewed-by: David Hildenbrand <david@redhat.com>
Vlastimil Babka Oct. 27, 2020, 9:58 a.m. UTC | #2
On 10/27/20 10:03 AM, David Hildenbrand wrote:
> On 26.10.20 18:33, Vlastimil Babka wrote:
>> Enabling page_poison=1 together with init_on_alloc=1 or init_on_free=1 produces
>> a warning in dmesg that page_poison takes precendence. However, as these
>> warnings are printed in early_param handlers for init_on_alloc/free, they are
>> not printed if page_poison is enabled later on the command line (handlers are
>> called in the order of their parameters), or when init_on_alloc/free is always
>> enabled by the respective config option - before the page_poison early param
>> handler is called, it is not considered to be enabled. This is inconsistent.
>> 
>> We can remove the dependency on order by making the init_on_* parameters only
>> set a boolean variable, and postponing the evaluation after all early params
>> have been processed. Introduce a new init_mem_debugging() function for that,
>> and move the related debug_pagealloc processing there as well.
> 
> init_mem_debugging() is somewhat sub-optimal - init_on_alloc=1 or
> init_on_free=1 are rather security hardening mechanisms.

Well yeah, init_mem_debugging_and_hardening()?

> ... I wondered if this could be the place to initialize any kind of mm
> parameters in the future. Like init_mem_params() or so.

Maybe. In practice you often find out that different things have to be hooked in 
different points of the init process, and a single function might not be enough. 
I tried to group stuff that's really inter-related and can be initialized at the 
same time.

>> 
>> As a result init_mem_debugging() knows always accurately if init_on_* and/or
>> page_poison options were enabled. Thus we can also optimize want_init_on_alloc()
>> and want_init_on_free(). We don't need to check page_poisoning_enabled() there,
>> we can instead not enable the init_on_* tracepoint at all, if page poisoning is
>> enabled. This results in a simpler and more effective code.
> 
> LGTM
> 
> Reviewed-by: David Hildenbrand <david@redhat.com>

Thanks!
David Hildenbrand Oct. 27, 2020, 9:58 a.m. UTC | #3
On 27.10.20 10:58, Vlastimil Babka wrote:
> On 10/27/20 10:03 AM, David Hildenbrand wrote:
>> On 26.10.20 18:33, Vlastimil Babka wrote:
>>> Enabling page_poison=1 together with init_on_alloc=1 or init_on_free=1 produces
>>> a warning in dmesg that page_poison takes precendence. However, as these
>>> warnings are printed in early_param handlers for init_on_alloc/free, they are
>>> not printed if page_poison is enabled later on the command line (handlers are
>>> called in the order of their parameters), or when init_on_alloc/free is always
>>> enabled by the respective config option - before the page_poison early param
>>> handler is called, it is not considered to be enabled. This is inconsistent.
>>>
>>> We can remove the dependency on order by making the init_on_* parameters only
>>> set a boolean variable, and postponing the evaluation after all early params
>>> have been processed. Introduce a new init_mem_debugging() function for that,
>>> and move the related debug_pagealloc processing there as well.
>>
>> init_mem_debugging() is somewhat sub-optimal - init_on_alloc=1 or
>> init_on_free=1 are rather security hardening mechanisms.
> 
> Well yeah, init_mem_debugging_and_hardening()?

Would work for me.
Mike Rapoport Oct. 28, 2020, 8:31 a.m. UTC | #4
On Mon, Oct 26, 2020 at 06:33:56PM +0100, Vlastimil Babka wrote:
> Enabling page_poison=1 together with init_on_alloc=1 or init_on_free=1 produces
> a warning in dmesg that page_poison takes precendence. However, as these

                                          ^ precedence

> warnings are printed in early_param handlers for init_on_alloc/free, they are
> not printed if page_poison is enabled later on the command line (handlers are
> called in the order of their parameters), or when init_on_alloc/free is always
> enabled by the respective config option - before the page_poison early param
> handler is called, it is not considered to be enabled. This is inconsistent.
> 
> We can remove the dependency on order by making the init_on_* parameters only
> set a boolean variable, and postponing the evaluation after all early params
> have been processed. Introduce a new init_mem_debugging() function for that,
> and move the related debug_pagealloc processing there as well.
> 
> As a result init_mem_debugging() knows always accurately if init_on_* and/or
> page_poison options were enabled. Thus we can also optimize want_init_on_alloc()
> and want_init_on_free(). We don't need to check page_poisoning_enabled() there,
> we can instead not enable the init_on_* tracepoint at all, if page poisoning is
> enabled. This results in a simpler and more effective code.
> 
> Signed-off-by: Vlastimil Babka <vbabka@suse.cz>

With two more nits below fixed

Reviewed-by: Mike Rapoport <rppt@linux.ibm.com>

> ---
>  include/linux/mm.h | 20 ++--------
>  init/main.c        |  2 +-
>  mm/page_alloc.c    | 94 +++++++++++++++++++++++-----------------------
>  3 files changed, 50 insertions(+), 66 deletions(-)
> 

...

> @@ -792,6 +752,44 @@ static inline void clear_page_guard(struct zone *zone, struct page *page,
>  				unsigned int order, int migratetype) {}
>  #endif
>  
> +/*
> + * Enable static keys related to various memory debugging and hardening options.
> + * Some override others, and depend on early params that are evaluated in the
> + * order of appearance. So we need to first gather the full picture of what was
> + * enabled, and then make decisions.
> + */
> +void init_mem_debugging()

Shouldn't it be init_mem_debug(void)?
Or whatever a new name would be :)

> +{
> +	if (_init_on_alloc_enabled_early) {
> +		if (page_poisoning_enabled()) {
> +			pr_info("mem auto-init: CONFIG_PAGE_POISONING is on, "
> +				"will take precedence over init_on_alloc\n");
> +		} else {
> +			static_branch_enable(&init_on_alloc);
> +		}
> +	}
> +	if (_init_on_free_enabled_early) {
> +		if (page_poisoning_enabled()) {
> +			pr_info("mem auto-init: CONFIG_PAGE_POISONING is on, "
> +				"will take precedence over init_on_free\n");
> +		} else {
> +			static_branch_enable(&init_on_free);
> +		}
> +	}

I think the braces for the inner ifs are not required.

> +
> +#ifdef CONFIG_DEBUG_PAGEALLOC
> +	if (!debug_pagealloc_enabled())
> +		return;
> +
> +	static_branch_enable(&_debug_pagealloc_enabled);
> +
> +	if (!debug_guardpage_minorder())
> +		return;
> +
> +	static_branch_enable(&_debug_guardpage_enabled);
> +#endif
> +}
> +
>  static inline void set_buddy_order(struct page *page, unsigned int order)
>  {
>  	set_page_private(page, order);
> -- 
> 2.29.0
> 
>
diff mbox series

Patch

diff --git a/include/linux/mm.h b/include/linux/mm.h
index ef360fe70aaf..c6a0adccf2fe 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2865,6 +2865,7 @@  extern int apply_to_existing_page_range(struct mm_struct *mm,
 				   unsigned long address, unsigned long size,
 				   pte_fn_t fn, void *data);
 
+extern void init_mem_debugging(void);
 #ifdef CONFIG_PAGE_POISONING
 extern bool page_poisoning_enabled(void);
 extern void kernel_poison_pages(struct page *page, int numpages, int enable);
@@ -2874,35 +2875,20 @@  static inline void kernel_poison_pages(struct page *page, int numpages,
 					int enable) { }
 #endif
 
-#ifdef CONFIG_INIT_ON_ALLOC_DEFAULT_ON
-DECLARE_STATIC_KEY_TRUE(init_on_alloc);
-#else
 DECLARE_STATIC_KEY_FALSE(init_on_alloc);
-#endif
 static inline bool want_init_on_alloc(gfp_t flags)
 {
-	if (static_branch_unlikely(&init_on_alloc) &&
-	    !page_poisoning_enabled())
+	if (static_branch_unlikely(&init_on_alloc))
 		return true;
 	return flags & __GFP_ZERO;
 }
 
-#ifdef CONFIG_INIT_ON_FREE_DEFAULT_ON
-DECLARE_STATIC_KEY_TRUE(init_on_free);
-#else
 DECLARE_STATIC_KEY_FALSE(init_on_free);
-#endif
 static inline bool want_init_on_free(void)
 {
-	return static_branch_unlikely(&init_on_free) &&
-	       !page_poisoning_enabled();
+	return static_branch_unlikely(&init_on_free);
 }
 
-#ifdef CONFIG_DEBUG_PAGEALLOC
-extern void init_debug_pagealloc(void);
-#else
-static inline void init_debug_pagealloc(void) {}
-#endif
 extern bool _debug_pagealloc_enabled_early;
 DECLARE_STATIC_KEY_FALSE(_debug_pagealloc_enabled);
 
diff --git a/init/main.c b/init/main.c
index 130376ec10ba..dca5b9093742 100644
--- a/init/main.c
+++ b/init/main.c
@@ -815,7 +815,7 @@  static void __init mm_init(void)
 	 * bigger than MAX_ORDER unless SPARSEMEM.
 	 */
 	page_ext_init_flatmem();
-	init_debug_pagealloc();
+	init_mem_debugging();
 	report_meminit();
 	mem_init();
 	kmem_cache_init();
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 23f5066bd4a5..b168c58ef337 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -165,53 +165,26 @@  unsigned long totalcma_pages __read_mostly;
 
 int percpu_pagelist_fraction;
 gfp_t gfp_allowed_mask __read_mostly = GFP_BOOT_MASK;
-#ifdef CONFIG_INIT_ON_ALLOC_DEFAULT_ON
-DEFINE_STATIC_KEY_TRUE(init_on_alloc);
-#else
-DEFINE_STATIC_KEY_FALSE(init_on_alloc);
-#endif
+DEFINE_STATIC_KEY_FALSE_RO(init_on_alloc);
 EXPORT_SYMBOL(init_on_alloc);
 
-#ifdef CONFIG_INIT_ON_FREE_DEFAULT_ON
-DEFINE_STATIC_KEY_TRUE(init_on_free);
-#else
-DEFINE_STATIC_KEY_FALSE(init_on_free);
-#endif
+DEFINE_STATIC_KEY_FALSE_RO(init_on_free);
 EXPORT_SYMBOL(init_on_free);
 
+static bool _init_on_alloc_enabled_early __read_mostly
+				= IS_ENABLED(CONFIG_INIT_ON_ALLOC_DEFAULT_ON);
 static int __init early_init_on_alloc(char *buf)
 {
-	int ret;
-	bool bool_result;
 
-	ret = kstrtobool(buf, &bool_result);
-	if (ret)
-		return ret;
-	if (bool_result && page_poisoning_enabled())
-		pr_info("mem auto-init: CONFIG_PAGE_POISONING is on, will take precedence over init_on_alloc\n");
-	if (bool_result)
-		static_branch_enable(&init_on_alloc);
-	else
-		static_branch_disable(&init_on_alloc);
-	return 0;
+	return kstrtobool(buf, &_init_on_alloc_enabled_early);
 }
 early_param("init_on_alloc", early_init_on_alloc);
 
+static bool _init_on_free_enabled_early __read_mostly
+				= IS_ENABLED(CONFIG_INIT_ON_FREE_DEFAULT_ON);
 static int __init early_init_on_free(char *buf)
 {
-	int ret;
-	bool bool_result;
-
-	ret = kstrtobool(buf, &bool_result);
-	if (ret)
-		return ret;
-	if (bool_result && page_poisoning_enabled())
-		pr_info("mem auto-init: CONFIG_PAGE_POISONING is on, will take precedence over init_on_free\n");
-	if (bool_result)
-		static_branch_enable(&init_on_free);
-	else
-		static_branch_disable(&init_on_free);
-	return 0;
+	return kstrtobool(buf, &_init_on_free_enabled_early);
 }
 early_param("init_on_free", early_init_on_free);
 
@@ -728,19 +701,6 @@  static int __init early_debug_pagealloc(char *buf)
 }
 early_param("debug_pagealloc", early_debug_pagealloc);
 
-void init_debug_pagealloc(void)
-{
-	if (!debug_pagealloc_enabled())
-		return;
-
-	static_branch_enable(&_debug_pagealloc_enabled);
-
-	if (!debug_guardpage_minorder())
-		return;
-
-	static_branch_enable(&_debug_guardpage_enabled);
-}
-
 static int __init debug_guardpage_minorder_setup(char *buf)
 {
 	unsigned long res;
@@ -792,6 +752,44 @@  static inline void clear_page_guard(struct zone *zone, struct page *page,
 				unsigned int order, int migratetype) {}
 #endif
 
+/*
+ * Enable static keys related to various memory debugging and hardening options.
+ * Some override others, and depend on early params that are evaluated in the
+ * order of appearance. So we need to first gather the full picture of what was
+ * enabled, and then make decisions.
+ */
+void init_mem_debugging()
+{
+	if (_init_on_alloc_enabled_early) {
+		if (page_poisoning_enabled()) {
+			pr_info("mem auto-init: CONFIG_PAGE_POISONING is on, "
+				"will take precedence over init_on_alloc\n");
+		} else {
+			static_branch_enable(&init_on_alloc);
+		}
+	}
+	if (_init_on_free_enabled_early) {
+		if (page_poisoning_enabled()) {
+			pr_info("mem auto-init: CONFIG_PAGE_POISONING is on, "
+				"will take precedence over init_on_free\n");
+		} else {
+			static_branch_enable(&init_on_free);
+		}
+	}
+
+#ifdef CONFIG_DEBUG_PAGEALLOC
+	if (!debug_pagealloc_enabled())
+		return;
+
+	static_branch_enable(&_debug_pagealloc_enabled);
+
+	if (!debug_guardpage_minorder())
+		return;
+
+	static_branch_enable(&_debug_guardpage_enabled);
+#endif
+}
+
 static inline void set_buddy_order(struct page *page, unsigned int order)
 {
 	set_page_private(page, order);