diff mbox series

[mm,v3,30/38] kasan, vmalloc: don't tag executable vmalloc allocations

Message ID c77f819e87b9fefcb26c6448a027b25c939f079e.1639432170.git.andreyknvl@google.com (mailing list archive)
State New, archived
Headers show
Series kasan, vmalloc, arm64: add vmalloc tagging support for SW/HW_TAGS | expand

Commit Message

andrey.konovalov@linux.dev Dec. 13, 2021, 9:54 p.m. UTC
From: Andrey Konovalov <andreyknvl@google.com>

The kernel can use to allocate executable memory. The only supported way
to do that is via __vmalloc_node_range() with the executable bit set in
the prot argument. (vmap() resets the bit via pgprot_nx()).

Once tag-based KASAN modes start tagging vmalloc allocations, executing
code from such allocations will lead to the PC register getting a tag,
which is not tolerated by the kernel.

Don't tag the allocation if page protections allow execution.

Signed-off-by: Andrey Konovalov <andreyknvl@google.com>

---

Changes v2->v3:
- Add this patch.
---
 include/linux/kasan.h |  1 +
 mm/kasan/hw_tags.c    |  7 +++++++
 mm/kasan/shadow.c     |  7 +++++++
 mm/vmalloc.c          | 10 +++++++---
 4 files changed, 22 insertions(+), 3 deletions(-)

Comments

Andrey Konovalov Dec. 14, 2021, 7:42 p.m. UTC | #1
On Mon, Dec 13, 2021 at 10:54 PM <andrey.konovalov@linux.dev> wrote:
>

[...]

> @@ -3133,10 +3133,14 @@ void *__vmalloc_node_range(unsigned long size, unsigned long align,
>          * (except for the should_skip_init() check) to make sure that memory
>          * is initialized under the same conditions regardless of the enabled
>          * KASAN mode.
> +        * Tag-based KASAN modes only assign tags to non-executable
> +        * allocations, see __kasan_unpoison_vmalloc().
>          */
>         kasan_flags = KASAN_VMALLOC_VM_ALLOC;
>         if (!want_init_on_free() && want_init_on_alloc(gfp_mask))
>                 kasan_flags |= KASAN_VMALLOC_INIT;
> +       if (pgprot_val(prot) == pgprot_val(pgprot_nx(prot)))

Can simply compare with PAGE_KERNEL here to match the check in
arch_vmalloc_pgprot_modify(). Will do in v4.

> +               kasan_flags |= KASAN_VMALLOC_NOEXEC;
>         addr = kasan_unpoison_vmalloc(addr, real_size, kasan_flags);
>
>         /*
> @@ -3844,7 +3848,7 @@ struct vm_struct **pcpu_get_vm_areas(const unsigned long *offsets,
>         for (area = 0; area < nr_vms; area++)
>                 vms[area]->addr = kasan_unpoison_vmalloc(vms[area]->addr,
>                                                          vms[area]->size,
> -                                                        KASAN_VMALLOC_NONE);
> +                                                        KASAN_VMALLOC_NOEXEC);
>
>         kfree(vas);
>         return vms;
> --
> 2.25.1
>
diff mbox series

Patch

diff --git a/include/linux/kasan.h b/include/linux/kasan.h
index 499f1573dba4..11f29c121bee 100644
--- a/include/linux/kasan.h
+++ b/include/linux/kasan.h
@@ -30,6 +30,7 @@  typedef unsigned int __bitwise kasan_vmalloc_flags_t;
 #define KASAN_VMALLOC_NONE	0x00u
 #define KASAN_VMALLOC_INIT	0x01u
 #define KASAN_VMALLOC_VM_ALLOC	0x02u
+#define KASAN_VMALLOC_NOEXEC	0x04u
 
 #if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
 
diff --git a/mm/kasan/hw_tags.c b/mm/kasan/hw_tags.c
index de564a6187e1..bbcf6f914490 100644
--- a/mm/kasan/hw_tags.c
+++ b/mm/kasan/hw_tags.c
@@ -246,6 +246,13 @@  void *__kasan_unpoison_vmalloc(const void *start, unsigned long size,
 	if (!(flags & KASAN_VMALLOC_VM_ALLOC))
 		return (void *)start;
 
+	/*
+	 * Don't tag executable memory.
+	 * The kernel doesn't tolerate having the PC register tagged.
+	 */
+	if (!(flags & KASAN_VMALLOC_NOEXEC))
+		return (void *)start;
+
 	tag = kasan_random_tag();
 	start = set_tag(start, tag);
 
diff --git a/mm/kasan/shadow.c b/mm/kasan/shadow.c
index b958babc8fed..d86ab0a9dcc3 100644
--- a/mm/kasan/shadow.c
+++ b/mm/kasan/shadow.c
@@ -488,6 +488,13 @@  void *__kasan_unpoison_vmalloc(const void *start, unsigned long size,
 	if (!is_vmalloc_or_module_addr(start))
 		return (void *)start;
 
+	/*
+	 * Don't tag executable memory.
+	 * The kernel doesn't tolerate having the PC register tagged.
+	 */
+	if (!(flags & KASAN_VMALLOC_NOEXEC))
+		return (void *)start;
+
 	start = set_tag(start, kasan_random_tag());
 	kasan_unpoison(start, size, false);
 	return (void *)start;
diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index 4171778922cc..75afd6c9bc3d 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -2219,7 +2219,7 @@  void *vm_map_ram(struct page **pages, unsigned int count, int node)
 	 * With hardware tag-based KASAN, marking is skipped for
 	 * non-VM_ALLOC mappings, see __kasan_unpoison_vmalloc().
 	 */
-	mem = kasan_unpoison_vmalloc(mem, size, KASAN_VMALLOC_NONE);
+	mem = kasan_unpoison_vmalloc(mem, size, KASAN_VMALLOC_NOEXEC);
 
 	return mem;
 }
@@ -2458,7 +2458,7 @@  static struct vm_struct *__get_vm_area_node(unsigned long size,
 	 */
 	if (!(flags & VM_ALLOC))
 		area->addr = kasan_unpoison_vmalloc(area->addr, requested_size,
-							KASAN_VMALLOC_NONE);
+							KASAN_VMALLOC_NOEXEC);
 
 	return area;
 }
@@ -3133,10 +3133,14 @@  void *__vmalloc_node_range(unsigned long size, unsigned long align,
 	 * (except for the should_skip_init() check) to make sure that memory
 	 * is initialized under the same conditions regardless of the enabled
 	 * KASAN mode.
+	 * Tag-based KASAN modes only assign tags to non-executable
+	 * allocations, see __kasan_unpoison_vmalloc().
 	 */
 	kasan_flags = KASAN_VMALLOC_VM_ALLOC;
 	if (!want_init_on_free() && want_init_on_alloc(gfp_mask))
 		kasan_flags |= KASAN_VMALLOC_INIT;
+	if (pgprot_val(prot) == pgprot_val(pgprot_nx(prot)))
+		kasan_flags |= KASAN_VMALLOC_NOEXEC;
 	addr = kasan_unpoison_vmalloc(addr, real_size, kasan_flags);
 
 	/*
@@ -3844,7 +3848,7 @@  struct vm_struct **pcpu_get_vm_areas(const unsigned long *offsets,
 	for (area = 0; area < nr_vms; area++)
 		vms[area]->addr = kasan_unpoison_vmalloc(vms[area]->addr,
 							 vms[area]->size,
-							 KASAN_VMALLOC_NONE);
+							 KASAN_VMALLOC_NOEXEC);
 
 	kfree(vas);
 	return vms;