diff mbox

mm: add arch-independent testcases for RODATA

Message ID 20170119145114.GA19772@pjb1027-Latitude-E5410 (mailing list archive)
State New, archived
Headers show

Commit Message

Jinbum Park Jan. 19, 2017, 2:51 p.m. UTC
This patch adds arch-independent testcases for RODATA.
Both x86 and x86_64 already have testcases for RODATA,
But they are arch-specific because using inline assembly directly.

and cacheflush.h is not suitable location for rodata-test related things.
Since they were in cacheflush.h,
If someone change the state of CONFIG_DEBUG_RODATA_TEST,
It cause overhead of kernel build.

To solve above issue,
write arch-independent testcases and move it to shared location. (main.c)

Signed-off-by: Jinbum Park <jinb.park7@gmail.com>
---
 arch/x86/Kconfig.debug            |  8 -----
 arch/x86/include/asm/cacheflush.h | 10 ------
 arch/x86/kernel/Makefile          |  1 -
 arch/x86/kernel/test_rodata.c     | 75 ---------------------------------------
 arch/x86/mm/init_32.c             |  4 ---
 arch/x86/mm/init_64.c             |  5 ---
 init/main.c                       | 10 +++++-
 mm/Kconfig.debug                  |  6 ++++
 mm/Makefile                       |  1 +
 mm/rodata_test.c                  | 63 ++++++++++++++++++++++++++++++++
 10 files changed, 79 insertions(+), 104 deletions(-)
 delete mode 100644 arch/x86/kernel/test_rodata.c
 create mode 100644 mm/rodata_test.c

Comments

Mark Rutland Jan. 19, 2017, 3:57 p.m. UTC | #1
On Thu, Jan 19, 2017 at 11:51:14PM +0900, Jinbum Park wrote:
> This patch adds arch-independent testcases for RODATA.
> Both x86 and x86_64 already have testcases for RODATA,
> But they are arch-specific because using inline assembly directly.
> 
> and cacheflush.h is not suitable location for rodata-test related things.
> Since they were in cacheflush.h,
> If someone change the state of CONFIG_DEBUG_RODATA_TEST,
> It cause overhead of kernel build.
> 
> To solve above issue,
> write arch-independent testcases and move it to shared location. (main.c)

This is clearly a rework and move of the existing x86 test, and not the
addition of a completely new test (see Arjan's comment about his credit
being removed...).

I would recommend that you turn this into a series that makes the x86
code generic, then moves it out into a common location where it can be
used by others. e.g.

1) make the test use put_user()
2) move the rodata_test() call and the prototype to a common location
3) move the test out to mm/ (with no changes to the file itself)

Otherwise, comments below.

> diff --git a/mm/rodata_test.c b/mm/rodata_test.c
> new file mode 100644
> index 0000000..d5b0504
> --- /dev/null
> +++ b/mm/rodata_test.c
> @@ -0,0 +1,63 @@
> +/*
> + * rodata_test.c: functional test for mark_rodata_ro function
> + *
> + * (C) Copyright 2017 Jinbum Park <jinb.park7@gmail.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * as published by the Free Software Foundation; version 2
> + * of the License.
> + */
> +#include <asm/uaccess.h>
> +#include <asm/sections.h>
> +
> +const int rodata_test_data = 0xC3;
> +EXPORT_SYMBOL_GPL(rodata_test_data);
> +
> +void rodata_test(void)
> +{
> +	unsigned long start, end, rodata_addr;
> +	int zero = 0;
> +
> +	/* prepare test */
> +	rodata_addr = ((unsigned long)&rodata_test_data);
> +
> +	/* test 1: read the value */
> +	/* If this test fails, some previous testrun has clobbered the state */
> +	if (!rodata_test_data) {
> +		pr_err("rodata_test: test 1 fails (start data)\n");
> +		return;
> +	}
> +
> +	/* test 2: write to the variable; this should fault */
> +	/*
> +	 * This must be written in assembly to be able to catch the
> +	 * exception that is supposed to happen in the correct case.
> +	 *
> +	 * So that put_user macro is used to write arch-independent assembly.
> +	 */
> +	if (!put_user(zero, (int *)rodata_addr)) {
> +		pr_err("rodata_test: test data was not read only\n");
> +		return;
> +	}

As I mentioned in the original posting, you need to change to KERNEL_DS
for the put_user.

Russell's suggestion to use probe_kernel_write() is strictly better;
please do that instead.

Thanks,
Mark.
Jinbum Park Jan. 20, 2017, 12:16 a.m. UTC | #2
1) make the test use put_user()
2) move the rodata_test() call and the prototype to a common location
3) move the test out to mm/ (with no changes to file itself)


Where is the best place for common test file in general??

 kernel/rodata_test.c
 include/rodata_test.h => Is it fine??

I can't see common file about rodata.
So I'm confused where the best place is.
Jinbum Park Jan. 20, 2017, 6:49 a.m. UTC | #3
Where is the best place for common test file in general??

 kernel/rodata_test.c
 include/rodata_test.h => Is it fine??

I can't see common file about rodata.
So I'm confused where the best place is.

2017. 1. 20. 오전 12:58에 "Mark Rutland" <mark.rutland@arm.com>님이 작성:

> On Thu, Jan 19, 2017 at 11:51:14PM +0900, Jinbum Park wrote:
> > This patch adds arch-independent testcases for RODATA.
> > Both x86 and x86_64 already have testcases for RODATA,
> > But they are arch-specific because using inline assembly directly.
> >
> > and cacheflush.h is not suitable location for rodata-test related things.
> > Since they were in cacheflush.h,
> > If someone change the state of CONFIG_DEBUG_RODATA_TEST,
> > It cause overhead of kernel build.
> >
> > To solve above issue,
> > write arch-independent testcases and move it to shared location. (main.c)
>
> This is clearly a rework and move of the existing x86 test, and not the
> addition of a completely new test (see Arjan's comment about his credit
> being removed...).
>
> I would recommend that you turn this into a series that makes the x86
> code generic, then moves it out into a common location where it can be
> used by others. e.g.
>
> 1) make the test use put_user()
> 2) move the rodata_test() call and the prototype to a common location
> 3) move the test out to mm/ (with no changes to the file itself)
>
> Otherwise, comments below.
>
> > diff --git a/mm/rodata_test.c b/mm/rodata_test.c
> > new file mode 100644
> > index 0000000..d5b0504
> > --- /dev/null
> > +++ b/mm/rodata_test.c
> > @@ -0,0 +1,63 @@
> > +/*
> > + * rodata_test.c: functional test for mark_rodata_ro function
> > + *
> > + * (C) Copyright 2017 Jinbum Park <jinb.park7@gmail.com>
> > + *
> > + * This program is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU General Public License
> > + * as published by the Free Software Foundation; version 2
> > + * of the License.
> > + */
> > +#include <asm/uaccess.h>
> > +#include <asm/sections.h>
> > +
> > +const int rodata_test_data = 0xC3;
> > +EXPORT_SYMBOL_GPL(rodata_test_data);
> > +
> > +void rodata_test(void)
> > +{
> > +     unsigned long start, end, rodata_addr;
> > +     int zero = 0;
> > +
> > +     /* prepare test */
> > +     rodata_addr = ((unsigned long)&rodata_test_data);
> > +
> > +     /* test 1: read the value */
> > +     /* If this test fails, some previous testrun has clobbered the
> state */
> > +     if (!rodata_test_data) {
> > +             pr_err("rodata_test: test 1 fails (start data)\n");
> > +             return;
> > +     }
> > +
> > +     /* test 2: write to the variable; this should fault */
> > +     /*
> > +      * This must be written in assembly to be able to catch the
> > +      * exception that is supposed to happen in the correct case.
> > +      *
> > +      * So that put_user macro is used to write arch-independent
> assembly.
> > +      */
> > +     if (!put_user(zero, (int *)rodata_addr)) {
> > +             pr_err("rodata_test: test data was not read only\n");
> > +             return;
> > +     }
>
> As I mentioned in the original posting, you need to change to KERNEL_DS
> for the put_user.
>
> Russell's suggestion to use probe_kernel_write() is strictly better;
> please do that instead.
>
> Thanks,
> Mark.
>
Mark Rutland Jan. 20, 2017, 11:17 a.m. UTC | #4
On Fri, Jan 20, 2017 at 03:49:46PM +0900, park jinbum wrote:
> Where is the best place for common test file in general??
> 
>  kernel/rodata_test.c
>  include/rodata_test.h => Is it fine??

I had assumed you would use mm/rodata_test.c, as you do in this patch
(i.e. a *new* common file). That seems fine to me.

Thanks,
Mark.
diff mbox

Patch

diff --git a/arch/x86/Kconfig.debug b/arch/x86/Kconfig.debug
index 67eec55..5df54a8 100644
--- a/arch/x86/Kconfig.debug
+++ b/arch/x86/Kconfig.debug
@@ -74,14 +74,6 @@  config EFI_PGT_DUMP
 	  issues with the mapping of the EFI runtime regions into that
 	  table.
 
-config DEBUG_RODATA_TEST
-	bool "Testcase for the marking rodata read-only"
-	default y
-	---help---
-	  This option enables a testcase for the setting rodata read-only
-	  as well as for the change_page_attr() infrastructure.
-	  If in doubt, say "N"
-
 config DEBUG_WX
 	bool "Warn on W+X mappings at boot"
 	select X86_PTDUMP_CORE
diff --git a/arch/x86/include/asm/cacheflush.h b/arch/x86/include/asm/cacheflush.h
index 872877d..e7e1942e 100644
--- a/arch/x86/include/asm/cacheflush.h
+++ b/arch/x86/include/asm/cacheflush.h
@@ -90,18 +90,8 @@ 
 
 #define mmio_flush_range(addr, size) clflush_cache_range(addr, size)
 
-extern const int rodata_test_data;
 extern int kernel_set_to_readonly;
 void set_kernel_text_rw(void);
 void set_kernel_text_ro(void);
 
-#ifdef CONFIG_DEBUG_RODATA_TEST
-int rodata_test(void);
-#else
-static inline int rodata_test(void)
-{
-	return 0;
-}
-#endif
-
 #endif /* _ASM_X86_CACHEFLUSH_H */
diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile
index 581386c..f6caf82 100644
--- a/arch/x86/kernel/Makefile
+++ b/arch/x86/kernel/Makefile
@@ -100,7 +100,6 @@  obj-$(CONFIG_HPET_TIMER) 	+= hpet.o
 obj-$(CONFIG_APB_TIMER)		+= apb_timer.o
 
 obj-$(CONFIG_AMD_NB)		+= amd_nb.o
-obj-$(CONFIG_DEBUG_RODATA_TEST)	+= test_rodata.o
 obj-$(CONFIG_DEBUG_NX_TEST)	+= test_nx.o
 obj-$(CONFIG_DEBUG_NMI_SELFTEST) += nmi_selftest.o
 
diff --git a/arch/x86/kernel/test_rodata.c b/arch/x86/kernel/test_rodata.c
deleted file mode 100644
index 222e84e..0000000
--- a/arch/x86/kernel/test_rodata.c
+++ /dev/null
@@ -1,75 +0,0 @@ 
-/*
- * test_rodata.c: functional test for mark_rodata_ro function
- *
- * (C) Copyright 2008 Intel Corporation
- * Author: Arjan van de Ven <arjan@linux.intel.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; version 2
- * of the License.
- */
-#include <asm/cacheflush.h>
-#include <asm/sections.h>
-#include <asm/asm.h>
-
-int rodata_test(void)
-{
-	unsigned long result;
-	unsigned long start, end;
-
-	/* test 1: read the value */
-	/* If this test fails, some previous testrun has clobbered the state */
-	if (!rodata_test_data) {
-		printk(KERN_ERR "rodata_test: test 1 fails (start data)\n");
-		return -ENODEV;
-	}
-
-	/* test 2: write to the variable; this should fault */
-	/*
-	 * If this test fails, we managed to overwrite the data
-	 *
-	 * This is written in assembly to be able to catch the
-	 * exception that is supposed to happen in the correct
-	 * case
-	 */
-
-	result = 1;
-	asm volatile(
-		"0:	mov %[zero],(%[rodata_test])\n"
-		"	mov %[zero], %[rslt]\n"
-		"1:\n"
-		".section .fixup,\"ax\"\n"
-		"2:	jmp 1b\n"
-		".previous\n"
-		_ASM_EXTABLE(0b,2b)
-		: [rslt] "=r" (result)
-		: [rodata_test] "r" (&rodata_test_data), [zero] "r" (0UL)
-	);
-
-
-	if (!result) {
-		printk(KERN_ERR "rodata_test: test data was not read only\n");
-		return -ENODEV;
-	}
-
-	/* test 3: check the value hasn't changed */
-	/* If this test fails, we managed to overwrite the data */
-	if (!rodata_test_data) {
-		printk(KERN_ERR "rodata_test: Test 3 fails (end data)\n");
-		return -ENODEV;
-	}
-	/* test 4: check if the rodata section is 4Kb aligned */
-	start = (unsigned long)__start_rodata;
-	end = (unsigned long)__end_rodata;
-	if (start & (PAGE_SIZE - 1)) {
-		printk(KERN_ERR "rodata_test: .rodata is not 4k aligned\n");
-		return -ENODEV;
-	}
-	if (end & (PAGE_SIZE - 1)) {
-		printk(KERN_ERR "rodata_test: .rodata end is not 4k aligned\n");
-		return -ENODEV;
-	}
-
-	return 0;
-}
diff --git a/arch/x86/mm/init_32.c b/arch/x86/mm/init_32.c
index 928d657..2b4b53e 100644
--- a/arch/x86/mm/init_32.c
+++ b/arch/x86/mm/init_32.c
@@ -864,9 +864,6 @@  static noinline int do_test_wp_bit(void)
 	return flag;
 }
 
-const int rodata_test_data = 0xC3;
-EXPORT_SYMBOL_GPL(rodata_test_data);
-
 int kernel_set_to_readonly __read_mostly;
 
 void set_kernel_text_rw(void)
@@ -939,7 +936,6 @@  void mark_rodata_ro(void)
 	set_pages_ro(virt_to_page(start), size >> PAGE_SHIFT);
 	printk(KERN_INFO "Write protecting the kernel read-only data: %luk\n",
 		size >> 10);
-	rodata_test();
 
 #ifdef CONFIG_CPA_DEBUG
 	printk(KERN_INFO "Testing CPA: undo %lx-%lx\n", start, start + size);
diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c
index 97346f9..15173d3 100644
--- a/arch/x86/mm/init_64.c
+++ b/arch/x86/mm/init_64.c
@@ -1000,9 +1000,6 @@  void __init mem_init(void)
 	mem_init_print_info(NULL);
 }
 
-const int rodata_test_data = 0xC3;
-EXPORT_SYMBOL_GPL(rodata_test_data);
-
 int kernel_set_to_readonly;
 
 void set_kernel_text_rw(void)
@@ -1071,8 +1068,6 @@  void mark_rodata_ro(void)
 	all_end = roundup((unsigned long)_brk_end, PMD_SIZE);
 	set_memory_nx(text_end, (all_end - text_end) >> PAGE_SHIFT);
 
-	rodata_test();
-
 #ifdef CONFIG_CPA_DEBUG
 	printk(KERN_INFO "Testing CPA: undo %lx-%lx\n", start, end);
 	set_memory_rw(start, (end-start) >> PAGE_SHIFT);
diff --git a/init/main.c b/init/main.c
index e47373d..15b42bf 100644
--- a/init/main.c
+++ b/init/main.c
@@ -932,11 +932,19 @@  static int __init set_debug_rodata(char *str)
 __setup("rodata=", set_debug_rodata);
 #endif
 
+#ifdef CONFIG_DEBUG_RODATA_TEST
+void rodata_test(void);
+#else
+static inline void rodata_test(void) {}
+#endif
+
 #ifdef CONFIG_DEBUG_RODATA
 static void mark_readonly(void)
 {
-	if (rodata_enabled)
+	if (rodata_enabled) {
 		mark_rodata_ro();
+		rodata_test();
+	}
 	else
 		pr_info("Kernel memory protection disabled.\n");
 }
diff --git a/mm/Kconfig.debug b/mm/Kconfig.debug
index afcc550..e4f22ce 100644
--- a/mm/Kconfig.debug
+++ b/mm/Kconfig.debug
@@ -90,3 +90,9 @@  config DEBUG_PAGE_REF
 	  careful when enabling this feature because it adds about 30 KB to the
 	  kernel code.  However the runtime performance overhead is virtually
 	  nil until the tracepoints are actually enabled.
+
+config DEBUG_RODATA_TEST
+	bool "Testcase for the marking rodata read-only"
+	depends on DEBUG_RODATA
+	---help---
+	  This option enables a testcase for the setting rodata read-only.
\ No newline at end of file
diff --git a/mm/Makefile b/mm/Makefile
index 433eaf9..d6199d4 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -83,6 +83,7 @@  obj-$(CONFIG_MEMORY_FAILURE) += memory-failure.o
 obj-$(CONFIG_HWPOISON_INJECT) += hwpoison-inject.o
 obj-$(CONFIG_DEBUG_KMEMLEAK) += kmemleak.o
 obj-$(CONFIG_DEBUG_KMEMLEAK_TEST) += kmemleak-test.o
+obj-$(CONFIG_DEBUG_RODATA_TEST) += rodata_test.o
 obj-$(CONFIG_PAGE_OWNER) += page_owner.o
 obj-$(CONFIG_CLEANCACHE) += cleancache.o
 obj-$(CONFIG_MEMORY_ISOLATION) += page_isolation.o
diff --git a/mm/rodata_test.c b/mm/rodata_test.c
new file mode 100644
index 0000000..d5b0504
--- /dev/null
+++ b/mm/rodata_test.c
@@ -0,0 +1,63 @@ 
+/*
+ * rodata_test.c: functional test for mark_rodata_ro function
+ *
+ * (C) Copyright 2017 Jinbum Park <jinb.park7@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2
+ * of the License.
+ */
+#include <asm/uaccess.h>
+#include <asm/sections.h>
+
+const int rodata_test_data = 0xC3;
+EXPORT_SYMBOL_GPL(rodata_test_data);
+
+void rodata_test(void)
+{
+	unsigned long start, end, rodata_addr;
+	int zero = 0;
+
+	/* prepare test */
+	rodata_addr = ((unsigned long)&rodata_test_data);
+
+	/* test 1: read the value */
+	/* If this test fails, some previous testrun has clobbered the state */
+	if (!rodata_test_data) {
+		pr_err("rodata_test: test 1 fails (start data)\n");
+		return;
+	}
+
+	/* test 2: write to the variable; this should fault */
+	/*
+	 * This must be written in assembly to be able to catch the
+	 * exception that is supposed to happen in the correct case.
+	 *
+	 * So that put_user macro is used to write arch-independent assembly.
+	 */
+	if (!put_user(zero, (int *)rodata_addr)) {
+		pr_err("rodata_test: test data was not read only\n");
+		return;
+	}
+
+	/* test 3: check the value hasn't changed */
+	if (rodata_test_data == zero) {
+		pr_err("rodata_test: test data was changed\n");
+		return;
+	}
+
+	/* test 4: check if the rodata section is PAGE_SIZE aligned */
+	start = (unsigned long)__start_rodata;
+	end = (unsigned long)__end_rodata;
+	if (start & (PAGE_SIZE - 1)) {
+		pr_err("rodata_test: start of .rodata is not page size aligned\n");
+		return;
+	}
+	if (end & (PAGE_SIZE - 1)) {
+		pr_err("rodata_test: end of .rodata is not page size aligned\n");
+		return;
+	}
+
+	pr_info("rodata_test: all tests were successful\n");
+}