Commit edeed30589f5defe63ce6aaae56f2b7c855e4520

Authored by Arjan van de Ven
Committed by Ingo Molnar
1 parent adafdf6a4e

x86: add testcases for RODATA and NX protections/attributes

Latest update; I now have 4 NX tests, but 2 fail so they're #if 0'd.
I also cleaned up the NX test code quite a bit, and got rid of the ugly
exception table sorting stuff.

From: Arjan van de Ven <arjan@linux.intel.com>

This patch adds testcases for the CONFIG_DEBUG_RODATA configuration option
as well as the NX CPU feature/mappings. Both testcases can move to tests/
once that patch gets merged into mainline.
(I'm half considering moving the rodata test into mm/init.c but I'll
wait with that until init.c is unified)

As part of this I had to fix a not-quite-right alignment in the vmlinux.lds.h
for the RODATA sections, which lead to 1 page less being marked read only.

Signed-off-by: Arjan van de Ven <arjan@linux.intel.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>

Showing 6 changed files with 276 additions and 0 deletions Side-by-side Diff

arch/x86/kernel/test_nx.c
  1 +/*
  2 + * test_nx.c: functional test for NX functionality
  3 + *
  4 + * (C) Copyright 2008 Intel Corporation
  5 + * Author: Arjan van de Ven <arjan@linux.intel.com>
  6 + *
  7 + * This program is free software; you can redistribute it and/or
  8 + * modify it under the terms of the GNU General Public License
  9 + * as published by the Free Software Foundation; version 2
  10 + * of the License.
  11 + */
  12 +#include <linux/module.h>
  13 +#include <linux/sort.h>
  14 +#include <asm/uaccess.h>
  15 +
  16 +extern int rodata_test_data;
  17 +
  18 +/*
  19 + * This file checks 4 things:
  20 + * 1) Check if the stack is not executable
  21 + * 2) Check if kmalloc memory is not executable
  22 + * 3) Check if the .rodata section is not executable
  23 + * 4) Check if the .data section of a module is not executable
  24 + *
  25 + * To do this, the test code tries to execute memory in stack/kmalloc/etc,
  26 + * and then checks if the expected trap happens.
  27 + *
  28 + * Sadly, this implies having a dynamic exception handling table entry.
  29 + * ... which can be done (and will make Rusty cry)... but it can only
  30 + * be done in a stand-alone module with only 1 entry total.
  31 + * (otherwise we'd have to sort and that's just too messy)
  32 + */
  33 +
  34 +
  35 +
  36 +/*
  37 + * We want to set up an exception handling point on our stack,
  38 + * which means a variable value. This function is rather dirty
  39 + * and walks the exception table of the module, looking for a magic
  40 + * marker and replaces it with a specific function.
  41 + */
  42 +static void fudze_exception_table(void *marker, void *new)
  43 +{
  44 + struct module *mod = THIS_MODULE;
  45 + struct exception_table_entry *extable;
  46 +
  47 + /*
  48 + * Note: This module has only 1 exception table entry,
  49 + * so searching and sorting is not needed. If that changes,
  50 + * this would be the place to search and re-sort the exception
  51 + * table.
  52 + */
  53 + if (mod->num_exentries > 1) {
  54 + printk(KERN_ERR "test_nx: too many exception table entries!\n");
  55 + printk(KERN_ERR "test_nx: test results are not reliable.\n");
  56 + return;
  57 + }
  58 + extable = (struct exception_table_entry *)mod->extable;
  59 + extable[0].insn = (unsigned long)new;
  60 +}
  61 +
  62 +
  63 +/*
  64 + * exception tables get their symbols translated so we need
  65 + * to use a fake function to put in there, which we can then
  66 + * replace at runtime.
  67 + */
  68 +void foo_label(void);
  69 +
  70 +/*
  71 + * returns 0 for not-executable, negative for executable
  72 + *
  73 + * Note: we cannot allow this function to be inlined, because
  74 + * that would give us more than 1 exception table entry.
  75 + * This in turn would break the assumptions above.
  76 + */
  77 +static noinline int test_address(void *address)
  78 +{
  79 + unsigned long result;
  80 +
  81 + /* Set up an exception table entry for our address */
  82 + fudze_exception_table(&foo_label, address);
  83 + result = 1;
  84 + asm volatile(
  85 + "foo_label:\n"
  86 + "0: call *%[fake_code]\n"
  87 + "1:\n"
  88 + ".section .fixup,\"ax\"\n"
  89 + "2: mov %[zero], %[rslt]\n"
  90 + " ret\n"
  91 + ".previous\n"
  92 + ".section __ex_table,\"a\"\n"
  93 + " .align 8\n"
  94 + " .quad 0b\n"
  95 + " .quad 2b\n"
  96 + ".previous\n"
  97 + : [rslt] "=r" (result)
  98 + : [fake_code] "r" (address), [zero] "r" (0UL), "0" (result)
  99 + );
  100 + /* change the exception table back for the next round */
  101 + fudze_exception_table(address, &foo_label);
  102 +
  103 + if (result)
  104 + return -ENODEV;
  105 + return 0;
  106 +}
  107 +
  108 +static unsigned char test_data = 0xC3; /* 0xC3 is the opcode for "ret" */
  109 +
  110 +static int test_NX(void)
  111 +{
  112 + int ret = 0;
  113 + /* 0xC3 is the opcode for "ret" */
  114 + char stackcode[] = {0xC3, 0x90, 0 };
  115 + char *heap;
  116 +
  117 + test_data = 0xC3;
  118 +
  119 + printk(KERN_INFO "Testing NX protection\n");
  120 +
  121 + /* Test 1: check if the stack is not executable */
  122 + if (test_address(&stackcode)) {
  123 + printk(KERN_ERR "test_nx: stack was executable\n");
  124 + ret = -ENODEV;
  125 + }
  126 +
  127 +
  128 + /* Test 2: Check if the heap is executable */
  129 + heap = kmalloc(64, GFP_KERNEL);
  130 + if (!heap)
  131 + return -ENOMEM;
  132 + heap[0] = 0xC3; /* opcode for "ret" */
  133 +
  134 + if (test_address(heap)) {
  135 + printk(KERN_ERR "test_nx: heap was executable\n");
  136 + ret = -ENODEV;
  137 + }
  138 + kfree(heap);
  139 +
  140 + /*
  141 + * The following 2 tests currently fail, this needs to get fixed
  142 + * Until then, don't run them to avoid too many people getting scared
  143 + * by the error message
  144 + */
  145 +#if 0
  146 +
  147 +#ifdef CONFIG_DEBUG_RODATA
  148 + /* Test 3: Check if the .rodata section is executable */
  149 + if (rodata_test_data != 0xC3) {
  150 + printk(KERN_ERR "test_nx: .rodata marker has invalid value\n");
  151 + ret = -ENODEV;
  152 + } else if (test_address(&rodata_test_data)) {
  153 + printk(KERN_ERR "test_nx: .rodata section is executable\n");
  154 + ret = -ENODEV;
  155 + }
  156 +#endif
  157 +
  158 + /* Test 4: Check if the .data section of a module is executable */
  159 + if (test_address(&test_data)) {
  160 + printk(KERN_ERR "test_nx: .data section is executable\n");
  161 + ret = -ENODEV;
  162 + }
  163 +
  164 +#endif
  165 + return 0;
  166 +}
  167 +
  168 +static void test_exit(void)
  169 +{
  170 +}
  171 +
  172 +module_init(test_NX);
  173 +module_exit(test_exit);
  174 +MODULE_LICENSE("GPL");
  175 +MODULE_DESCRIPTION("Testcase for the NX infrastructure");
  176 +MODULE_AUTHOR("Arjan van de Ven <arjan@linux.intel.com>");
arch/x86/kernel/test_rodata.c
  1 +/*
  2 + * test_rodata.c: functional test for mark_rodata_ro function
  3 + *
  4 + * (C) Copyright 2008 Intel Corporation
  5 + * Author: Arjan van de Ven <arjan@linux.intel.com>
  6 + *
  7 + * This program is free software; you can redistribute it and/or
  8 + * modify it under the terms of the GNU General Public License
  9 + * as published by the Free Software Foundation; version 2
  10 + * of the License.
  11 + */
  12 +#include <linux/module.h>
  13 +#include <asm/sections.h>
  14 +extern int rodata_test_data;
  15 +
  16 +int rodata_test(void)
  17 +{
  18 + unsigned long result;
  19 + unsigned long start, end;
  20 +
  21 + /* test 1: read the value */
  22 + /* If this test fails, some previous testrun has clobbered the state */
  23 + if (!rodata_test_data) {
  24 + printk(KERN_ERR "rodata_test: test 1 fails (start data)\n");
  25 + return -ENODEV;
  26 + }
  27 +
  28 + /* test 2: write to the variable; this should fault */
  29 + /*
  30 + * If this test fails, we managed to overwrite the data
  31 + *
  32 + * This is written in assembly to be able to catch the
  33 + * exception that is supposed to happen in the correct
  34 + * case
  35 + */
  36 +
  37 + result = 1;
  38 + asm volatile(
  39 + "0: mov %[zero],(%[rodata_test])\n"
  40 + " mov %[zero], %[rslt]\n"
  41 + "1:\n"
  42 + ".section .fixup,\"ax\"\n"
  43 + "2: jmp 1b\n"
  44 + ".previous\n"
  45 + ".section __ex_table,\"a\"\n"
  46 + " .align 16\n"
  47 +#ifdef CONFIG_X86_32
  48 + " .long 0b,2b\n"
  49 +#else
  50 + " .quad 0b,2b\n"
  51 +#endif
  52 + ".previous"
  53 + : [rslt] "=r" (result)
  54 + : [rodata_test] "r" (&rodata_test_data), [zero] "r" (0UL)
  55 + );
  56 +
  57 +
  58 + if (!result) {
  59 + printk(KERN_ERR "rodata_test: test data was not read only\n");
  60 + return -ENODEV;
  61 + }
  62 +
  63 + /* test 3: check the value hasn't changed */
  64 + /* If this test fails, we managed to overwrite the data */
  65 + if (!rodata_test_data) {
  66 + printk(KERN_ERR "rodata_test: Test 3 failes (end data)\n");
  67 + return -ENODEV;
  68 + }
  69 + /* test 4: check if the rodata section is 4Kb aligned */
  70 + start = (unsigned long)__start_rodata;
  71 + end = (unsigned long)__end_rodata;
  72 + if (start & (PAGE_SIZE - 1)) {
  73 + printk(KERN_ERR "rodata_test: .rodata is not 4k aligned\n");
  74 + return -ENODEV;
  75 + }
  76 + if (end & (PAGE_SIZE - 1)) {
  77 + printk(KERN_ERR "rodata_test: .rodata end is not 4k aligned\n");
  78 + return -ENODEV;
  79 + }
  80 +
  81 + return 0;
  82 +}
  83 +
  84 +MODULE_LICENSE("GPL");
  85 +MODULE_DESCRIPTION("Testcase for the DEBUG_RODATA infrastructure");
  86 +MODULE_AUTHOR("Arjan van de Ven <arjan@linux.intel.com>");
arch/x86/mm/init_32.c
... ... @@ -736,6 +736,8 @@
736 736 }
737 737  
738 738 #ifdef CONFIG_DEBUG_RODATA
  739 +const int rodata_test_data = 0xC3;
  740 +EXPORT_SYMBOL_GPL(rodata_test_data);
739 741  
740 742 void mark_rodata_ro(void)
741 743 {
... ... @@ -765,6 +767,7 @@
765 767 set_pages_ro(virt_to_page(start), size >> PAGE_SHIFT);
766 768 printk("Write protecting the kernel read-only data: %luk\n",
767 769 size >> 10);
  770 + rodata_test();
768 771  
769 772 #ifdef CONFIG_CPA_DEBUG
770 773 printk("Testing CPA: undo %lx-%lx\n", start, start + size);
arch/x86/mm/init_64.c
... ... @@ -573,6 +573,7 @@
573 573 set_memory_rw(begin, (end - begin)/PAGE_SIZE);
574 574 set_memory_np(begin, (end - begin)/PAGE_SIZE);
575 575 set_memory_nx(begin, (end - begin)/PAGE_SIZE);
  576 + rodata_test();
576 577 }
577 578 #endif
578 579 }
... ... @@ -585,6 +586,8 @@
585 586 }
586 587  
587 588 #ifdef CONFIG_DEBUG_RODATA
  589 +const int rodata_test_data = 0xC3;
  590 +EXPORT_SYMBOL_GPL(rodata_test_data);
588 591  
589 592 void mark_rodata_ro(void)
590 593 {
include/asm-generic/vmlinux.lds.h
... ... @@ -184,6 +184,7 @@
184 184 VMLINUX_SYMBOL(__start___param) = .; \
185 185 *(__param) \
186 186 VMLINUX_SYMBOL(__stop___param) = .; \
  187 + . = ALIGN((align)); \
187 188 VMLINUX_SYMBOL(__end_rodata) = .; \
188 189 } \
189 190 . = ALIGN((align));
include/asm-x86/cacheflush.h
... ... @@ -47,6 +47,13 @@
47 47 #ifdef CONFIG_DEBUG_RODATA
48 48 void mark_rodata_ro(void);
49 49 #endif
  50 +#ifdef CONFIG_DEBUG_RODATA_TEST
  51 +void rodata_test(void);
  52 +#else
  53 +static inline void rodata_test(void)
  54 +{
  55 +}
  56 +#endif
50 57  
51 58 #endif