diff mbox series

[v3,2/3] x86/modules: Increase randomization for modules

Message ID 1534365020-18943-3-git-send-email-rick.p.edgecombe@intel.com (mailing list archive)
State New, archived
Headers show
Series KASLR feature to randomize each loadable module | expand

Commit Message

Edgecombe, Rick P Aug. 15, 2018, 8:30 p.m. UTC
This changes the behavior of the KASLR logic for allocating memory for the text
sections of loadable modules. It randomizes the location of each module text
section with about 17 bits of entropy in typical use. This is enabled on X86_64
only. For 32 bit, the behavior is unchanged. It refactors existing code around
module randomization in order to cleanly implement this along side the unchanged
32 bit behavior.

The algorithm breaks the module space in two, a random area and a backup area.
It first tries to allocate at a number of randomly located starting pages inside
the random section without purging any lazy free vmap areas and triggering the
associated TLB flush. If this fails, it will try again a number of times
allowing for purges if needed.  Finally if those both fail to find a position it
will allocate in the backup area. The backup area base will be offset in the
same way as the current algorithm does for the base area, 1024 possible
locations.

Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
---
 arch/x86/include/asm/pgtable_64_types.h |   7 ++
 arch/x86/kernel/module.c                | 163 +++++++++++++++++++++++++++-----
 2 files changed, 147 insertions(+), 23 deletions(-)

Comments

kernel test robot Aug. 16, 2018, 7:15 a.m. UTC | #1
Hi Rick,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on linus/master]
[also build test ERROR on v4.18 next-20180815]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url:    https://github.com/0day-ci/linux/commits/Rick-Edgecombe/KASLR-feature-to-randomize-each-loadable-module/20180816-120750
config: um-x86_64_defconfig (attached as .config)
compiler: gcc-7 (Debian 7.3.0-16) 7.3.0
reproduce:
        # save the attached .config to linux build tree
        make ARCH=um SUBARCH=x86_64

Note: the linux-review/Rick-Edgecombe/KASLR-feature-to-randomize-each-loadable-module/20180816-120750 HEAD 7eff11788897c8579f53bc7043f854794dbad25f builds fine.
      It only hurts bisectibility.

All error/warnings (new ones prefixed by >>):

   arch/x86/um/../kernel/module.c: In function 'kaslr_randomize_each_module':
>> arch/x86/um/../kernel/module.c:53:9: error: implicit declaration of function 'kaslr_enabled' [-Werror=implicit-function-declaration]
     return kaslr_enabled()
            ^~~~~~~~~~~~~
   arch/x86/um/../kernel/module.c: In function 'get_modules_rand_len':
>> arch/x86/um/../kernel/module.c:68:9: error: 'MODULES_RAND_LEN' undeclared (first use in this function); did you mean 'MODULE_NAME_LEN'?
     return MODULES_RAND_LEN;
            ^~~~~~~~~~~~~~~~
            MODULE_NAME_LEN
   arch/x86/um/../kernel/module.c:68:9: note: each undeclared identifier is reported only once for each function it appears in
>> arch/x86/um/../kernel/module.c:69:1: warning: control reaches end of non-void function [-Wreturn-type]
    }
    ^
   cc1: some warnings being treated as errors

vim +/kaslr_enabled +53 arch/x86/um/../kernel/module.c

    50	
    51	static inline int kaslr_randomize_each_module(void)
    52	{
  > 53		return kaslr_enabled()
    54				&& IS_ENABLED(CONFIG_RANDOMIZE_BASE)
    55				&& IS_ENABLED(CONFIG_X86_64);
    56	}
    57	
    58	static inline int kaslr_randomize_base(void)
    59	{
    60		return kaslr_enabled()
    61				&& IS_ENABLED(CONFIG_RANDOMIZE_BASE)
    62				&& !IS_ENABLED(CONFIG_X86_64);
    63	}
    64	
    65	#ifdef CONFIG_X86_64
    66	static inline const unsigned long get_modules_rand_len(void)
    67	{
  > 68		return MODULES_RAND_LEN;
  > 69	}
    70	#else
    71	static inline const unsigned long get_modules_rand_len(void)
    72	{
    73		BUILD_BUG();
    74		return 0;
    75	}
    76	#endif
    77	

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
diff mbox series

Patch

diff --git a/arch/x86/include/asm/pgtable_64_types.h b/arch/x86/include/asm/pgtable_64_types.h
index 054765a..c320b7f 100644
--- a/arch/x86/include/asm/pgtable_64_types.h
+++ b/arch/x86/include/asm/pgtable_64_types.h
@@ -142,6 +142,13 @@  extern unsigned int ptrs_per_p4d;
 #define MODULES_END		_AC(0xffffffffff000000, UL)
 #define MODULES_LEN		(MODULES_END - MODULES_VADDR)
 
+/*
+ * Dedicate the first part of the module space to a randomized area when KASLR
+ * is in use.  Leave the remaining part for a fallback if we are unable to
+ * allocate in the random area.
+ */
+#define MODULES_RAND_LEN	PAGE_ALIGN((MODULES_LEN/3)*2)
+
 #define ESPFIX_PGD_ENTRY	_AC(-2, UL)
 #define ESPFIX_BASE_ADDR	(ESPFIX_PGD_ENTRY << P4D_SHIFT)
 
diff --git a/arch/x86/kernel/module.c b/arch/x86/kernel/module.c
index f58336a..1cb8efa 100644
--- a/arch/x86/kernel/module.c
+++ b/arch/x86/kernel/module.c
@@ -48,34 +48,149 @@  do {							\
 } while (0)
 #endif
 
-#ifdef CONFIG_RANDOMIZE_BASE
+static inline int kaslr_randomize_each_module(void)
+{
+	return kaslr_enabled()
+			&& IS_ENABLED(CONFIG_RANDOMIZE_BASE)
+			&& IS_ENABLED(CONFIG_X86_64);
+}
+
+static inline int kaslr_randomize_base(void)
+{
+	return kaslr_enabled()
+			&& IS_ENABLED(CONFIG_RANDOMIZE_BASE)
+			&& !IS_ENABLED(CONFIG_X86_64);
+}
+
+#ifdef CONFIG_X86_64
+static inline const unsigned long get_modules_rand_len(void)
+{
+	return MODULES_RAND_LEN;
+}
+#else
+static inline const unsigned long get_modules_rand_len(void)
+{
+	BUILD_BUG();
+	return 0;
+}
+#endif
+
 static unsigned long module_load_offset;
+static const unsigned long NR_NO_PURGE = 5000;
+static const unsigned long NR_TRY_PURGE = 5000;
 
 /* Mutex protects the module_load_offset. */
 static DEFINE_MUTEX(module_kaslr_mutex);
 
 static unsigned long int get_module_load_offset(void)
 {
-	if (kaslr_enabled()) {
-		mutex_lock(&module_kaslr_mutex);
-		/*
-		 * Calculate the module_load_offset the first time this
-		 * code is called. Once calculated it stays the same until
-		 * reboot.
-		 */
-		if (module_load_offset == 0)
-			module_load_offset =
-				(get_random_int() % 1024 + 1) * PAGE_SIZE;
-		mutex_unlock(&module_kaslr_mutex);
-	}
+	mutex_lock(&module_kaslr_mutex);
+	/*
+	 * Calculate the module_load_offset the first time this
+	 * code is called. Once calculated it stays the same until
+	 * reboot.
+	 */
+	if (module_load_offset == 0)
+		module_load_offset = (get_random_int() % 1024 + 1) * PAGE_SIZE;
+	mutex_unlock(&module_kaslr_mutex);
+
 	return module_load_offset;
 }
-#else
-static unsigned long int get_module_load_offset(void)
+
+static unsigned long get_module_vmalloc_start(void)
 {
-	return 0;
+	if (kaslr_randomize_each_module())
+		return MODULES_VADDR + get_modules_rand_len()
+					+ get_module_load_offset();
+	else if (kaslr_randomize_base())
+		return MODULES_VADDR + get_module_load_offset();
+
+	return MODULES_VADDR;
+}
+
+static void *try_module_alloc(unsigned long addr, unsigned long size,
+					int try_purge)
+{
+	const unsigned long vm_flags = 0;
+
+	return __vmalloc_node_try_addr(addr, size, GFP_KERNEL, PAGE_KERNEL_EXEC,
+					vm_flags, NUMA_NO_NODE, try_purge,
+					__builtin_return_address(0));
+}
+
+/*
+ * Find a random address to try that wont obviosly not fit. Random areas are
+ * allowed to overflow into the backup area
+ */
+static unsigned long get_rand_module_addr(unsigned long size)
+{
+	unsigned long nr_max_pos = (MODULES_LEN - size) / MODULE_ALIGN + 1;
+	unsigned long nr_rnd_pos = get_modules_rand_len() / MODULE_ALIGN;
+	unsigned long nr_pos = min(nr_max_pos, nr_rnd_pos);
+
+	unsigned long module_position_nr = get_random_long() % nr_pos;
+	unsigned long offset = module_position_nr * MODULE_ALIGN;
+
+	return MODULES_VADDR + offset;
+}
+
+/*
+ * Try to allocate in the random area. First 5000 times without purging, then
+ * 5000 times with purging. If these fail, return NULL.
+ */
+static void *try_module_randomize_each(unsigned long size)
+{
+	void *p = NULL;
+	unsigned int i;
+	unsigned long last_lazy_free_blocked = 0;
+
+	/* This will have a gaurd page */
+	unsigned long real_size = PAGE_ALIGN(size) + PAGE_SIZE;
+
+	if (!kaslr_randomize_each_module())
+		return NULL;
+
+	/* Make sure there is at least one address that might fit. */
+	if (real_size < PAGE_ALIGN(size) || real_size > MODULES_LEN)
+		return NULL;
+
+	/* Try to find a spot that doesn't need a lazy purge */
+	for (i = 0; i < NR_NO_PURGE; i++) {
+		unsigned long addr = get_rand_module_addr(real_size);
+
+		/* First try to avoid having to purge */
+		p = try_module_alloc(addr, real_size, 0);
+
+		/*
+		 * Save the last value that was blocked by a
+		 * lazy purge area.
+		 */
+		if (IS_ERR(p) && PTR_ERR(p) == -EUCLEAN)
+			last_lazy_free_blocked = addr;
+		else if (!IS_ERR(p))
+			return p;
+	}
+
+	/* Try the most recent spot that could be used after a lazy purge */
+	if (last_lazy_free_blocked) {
+		p = try_module_alloc(last_lazy_free_blocked, real_size, 1);
+
+		if (!IS_ERR(p))
+			return p;
+	}
+
+	/* Look for more spots and allow lazy purges */
+	for (i = 0; i < NR_TRY_PURGE; i++) {
+		unsigned long addr = get_rand_module_addr(real_size);
+
+		/* Give up and allow for purges */
+		p = try_module_alloc(addr, real_size, 1);
+
+		if (!IS_ERR(p))
+			return p;
+	}
+	return NULL;
 }
-#endif
 
 void *module_alloc(unsigned long size)
 {
@@ -84,16 +199,18 @@  void *module_alloc(unsigned long size)
 	if (PAGE_ALIGN(size) > MODULES_LEN)
 		return NULL;
 
-	p = __vmalloc_node_range(size, MODULE_ALIGN,
-				    MODULES_VADDR + get_module_load_offset(),
-				    MODULES_END, GFP_KERNEL,
-				    PAGE_KERNEL_EXEC, 0, NUMA_NO_NODE,
-				    __builtin_return_address(0));
+	p = try_module_randomize_each(size);
+
+	if (!p)
+		p = __vmalloc_node_range(size, MODULE_ALIGN,
+				get_module_vmalloc_start(), MODULES_END,
+				GFP_KERNEL, PAGE_KERNEL_EXEC, 0,
+				NUMA_NO_NODE, __builtin_return_address(0));
+
 	if (p && (kasan_module_alloc(p, size) < 0)) {
 		vfree(p);
 		return NULL;
 	}
-
 	return p;
 }