x86/alternative: Introduce text_poke_set
authorSong Liu <song@kernel.org>
Fri, 20 May 2022 23:57:52 +0000 (16:57 -0700)
committerDaniel Borkmann <daniel@iogearbox.net>
Mon, 23 May 2022 21:07:38 +0000 (23:07 +0200)
Introduce a memset like API for text_poke. This will be used to fill the
unused RX memory with illegal instructions.

Suggested-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Signed-off-by: Song Liu <song@kernel.org>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://lore.kernel.org/bpf/20220520235758.1858153-3-song@kernel.org
arch/x86/include/asm/text-patching.h
arch/x86/kernel/alternative.c

index d20ab0921480a3005314c8a1c20ce429c04b0464..1cc15528ce29bab81582b4d23c02ba46c9309e49 100644 (file)
@@ -45,6 +45,7 @@ extern void *text_poke(void *addr, const void *opcode, size_t len);
 extern void text_poke_sync(void);
 extern void *text_poke_kgdb(void *addr, const void *opcode, size_t len);
 extern void *text_poke_copy(void *addr, const void *opcode, size_t len);
+extern void *text_poke_set(void *addr, int c, size_t len);
 extern int poke_int3_handler(struct pt_regs *regs);
 extern void text_poke_bp(void *addr, const void *opcode, size_t len, const void *emulate);
 
index d374cb3cf024c2bae7cba1ac42cce15d119dcda3..7563b5bc832830437a803f29ef7bb996321276b1 100644 (file)
@@ -994,7 +994,21 @@ static inline void unuse_temporary_mm(temp_mm_state_t prev_state)
 __ro_after_init struct mm_struct *poking_mm;
 __ro_after_init unsigned long poking_addr;
 
-static void *__text_poke(void *addr, const void *opcode, size_t len)
+static void text_poke_memcpy(void *dst, const void *src, size_t len)
+{
+       memcpy(dst, src, len);
+}
+
+static void text_poke_memset(void *dst, const void *src, size_t len)
+{
+       int c = *(const int *)src;
+
+       memset(dst, c, len);
+}
+
+typedef void text_poke_f(void *dst, const void *src, size_t len);
+
+static void *__text_poke(text_poke_f func, void *addr, const void *src, size_t len)
 {
        bool cross_page_boundary = offset_in_page(addr) + len > PAGE_SIZE;
        struct page *pages[2] = {NULL};
@@ -1059,7 +1073,7 @@ static void *__text_poke(void *addr, const void *opcode, size_t len)
        prev = use_temporary_mm(poking_mm);
 
        kasan_disable_current();
-       memcpy((u8 *)poking_addr + offset_in_page(addr), opcode, len);
+       func((u8 *)poking_addr + offset_in_page(addr), src, len);
        kasan_enable_current();
 
        /*
@@ -1087,11 +1101,13 @@ static void *__text_poke(void *addr, const void *opcode, size_t len)
                           (cross_page_boundary ? 2 : 1) * PAGE_SIZE,
                           PAGE_SHIFT, false);
 
-       /*
-        * If the text does not match what we just wrote then something is
-        * fundamentally screwy; there's nothing we can really do about that.
-        */
-       BUG_ON(memcmp(addr, opcode, len));
+       if (func == text_poke_memcpy) {
+               /*
+                * If the text does not match what we just wrote then something is
+                * fundamentally screwy; there's nothing we can really do about that.
+                */
+               BUG_ON(memcmp(addr, src, len));
+       }
 
        local_irq_restore(flags);
        pte_unmap_unlock(ptep, ptl);
@@ -1118,7 +1134,7 @@ void *text_poke(void *addr, const void *opcode, size_t len)
 {
        lockdep_assert_held(&text_mutex);
 
-       return __text_poke(addr, opcode, len);
+       return __text_poke(text_poke_memcpy, addr, opcode, len);
 }
 
 /**
@@ -1137,7 +1153,7 @@ void *text_poke(void *addr, const void *opcode, size_t len)
  */
 void *text_poke_kgdb(void *addr, const void *opcode, size_t len)
 {
-       return __text_poke(addr, opcode, len);
+       return __text_poke(text_poke_memcpy, addr, opcode, len);
 }
 
 /**
@@ -1167,7 +1183,38 @@ void *text_poke_copy(void *addr, const void *opcode, size_t len)
 
                s = min_t(size_t, PAGE_SIZE * 2 - offset_in_page(ptr), len - patched);
 
-               __text_poke((void *)ptr, opcode + patched, s);
+               __text_poke(text_poke_memcpy, (void *)ptr, opcode + patched, s);
+               patched += s;
+       }
+       mutex_unlock(&text_mutex);
+       return addr;
+}
+
+/**
+ * text_poke_set - memset into (an unused part of) RX memory
+ * @addr: address to modify
+ * @c: the byte to fill the area with
+ * @len: length to copy, could be more than 2x PAGE_SIZE
+ *
+ * This is useful to overwrite unused regions of RX memory with illegal
+ * instructions.
+ */
+void *text_poke_set(void *addr, int c, size_t len)
+{
+       unsigned long start = (unsigned long)addr;
+       size_t patched = 0;
+
+       if (WARN_ON_ONCE(core_kernel_text(start)))
+               return NULL;
+
+       mutex_lock(&text_mutex);
+       while (patched < len) {
+               unsigned long ptr = start + patched;
+               size_t s;
+
+               s = min_t(size_t, PAGE_SIZE * 2 - offset_in_page(ptr), len - patched);
+
+               __text_poke(text_poke_memset, (void *)ptr, (void *)&c, s);
                patched += s;
        }
        mutex_unlock(&text_mutex);