Commit 1a2473f0 authored by Andrey Konovalov's avatar Andrey Konovalov Committed by Linus Torvalds
Browse files

kasan: improve vmalloc tests

Update the existing vmalloc_oob() test to account for the specifics of the
tag-based modes.  Also add a few new checks and comments.

Add new vmalloc-related tests:

 - vmalloc_helpers_tags() to check that exported vmalloc helpers can
   handle tagged pointers.

 - vmap_tags() to check that SW_TAGS mode properly tags vmap() mappings.

 - vm_map_ram_tags() to check that SW_TAGS mode properly tags
   vm_map_ram() mappings.

 - vmalloc_percpu() to check that SW_TAGS mode tags regions allocated
   for __alloc_percpu(). The tagging of per-cpu mappings is best-effort;
   proper tagging is tracked in [1].

[1] https://bugzilla.kernel.org/show_bug.cgi?id=215019

[sfr@canb.auug.org.au: similar to "kasan: test: fix compatibility with FORTIFY_SOURCE"]
  Link: https://lkml.kernel.org/r/20220128144801.73f5ced0@canb.auug.org.au
  Link: https://lkml.kernel.org/r/865c91ba49b90623ab50c7526b79ccb955f544f0.1644950160.git.andreyknvl@google.com
[andreyknvl@google.com: set_memory_rw/ro() are not exported to modules]
  Link: https://lkml.kernel.org/r/019ac41602e0c4a7dfe96dc8158a95097c2b2ebd.1645554036.git.andreyknvl@google.com
[akpm@linux-foundation.org: fix build]

Cc: Andrey Konovalov <andreyknvl@gmail.com>
[andreyknvl@google.com: vmap_tags() and vm_map_ram_tags() pass invalid page array size]
Link: https://lkml.kernel.org/r/bbdc1c0501c5275e7f26fdb8e2a7b14a40a9f36b.1643047180.git.andreyknvl@google.com


Signed-off-by: default avatarAndrey Konovalov <andreyknvl@google.com>
Signed-off-by: default avatarStephen Rothwell <sfr@canb.auug.org.au>
Acked-by: default avatarMarco Elver <elver@google.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Andrey Ryabinin <ryabinin.a.a@gmail.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Evgenii Stepanov <eugenis@google.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Peter Collingbourne <pcc@google.com>
Cc: Vincenzo Frascino <vincenzo.frascino@arm.com>
Cc: Will Deacon <will@kernel.org>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 8479d7b5
Loading
Loading
Loading
Loading
+188 −6
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/vmalloc.h>
#include <linux/set_memory.h>

#include <asm/page.h>

@@ -1057,21 +1058,186 @@ static void kmalloc_double_kzfree(struct kunit *test)
	KUNIT_EXPECT_KASAN_FAIL(test, kfree_sensitive(ptr));
}

static void vmalloc_helpers_tags(struct kunit *test)
{
	void *ptr;

	/* This test is intended for tag-based modes. */
	KASAN_TEST_NEEDS_CONFIG_OFF(test, CONFIG_KASAN_GENERIC);

	KASAN_TEST_NEEDS_CONFIG_ON(test, CONFIG_KASAN_VMALLOC);

	ptr = vmalloc(PAGE_SIZE);
	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ptr);

	/* Check that the returned pointer is tagged. */
	KUNIT_EXPECT_GE(test, (u8)get_tag(ptr), (u8)KASAN_TAG_MIN);
	KUNIT_EXPECT_LT(test, (u8)get_tag(ptr), (u8)KASAN_TAG_KERNEL);

	/* Make sure exported vmalloc helpers handle tagged pointers. */
	KUNIT_ASSERT_TRUE(test, is_vmalloc_addr(ptr));
	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, vmalloc_to_page(ptr));

#if !IS_MODULE(CONFIG_KASAN_KUNIT_TEST)
	{
		int rv;

		/* Make sure vmalloc'ed memory permissions can be changed. */
		rv = set_memory_ro((unsigned long)ptr, 1);
		KUNIT_ASSERT_GE(test, rv, 0);
		rv = set_memory_rw((unsigned long)ptr, 1);
		KUNIT_ASSERT_GE(test, rv, 0);
	}
#endif

	vfree(ptr);
}

static void vmalloc_oob(struct kunit *test)
{
	void *area;
	char *v_ptr, *p_ptr;
	struct page *page;
	size_t size = PAGE_SIZE / 2 - KASAN_GRANULE_SIZE - 5;

	KASAN_TEST_NEEDS_CONFIG_ON(test, CONFIG_KASAN_VMALLOC);

	v_ptr = vmalloc(size);
	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, v_ptr);

	OPTIMIZER_HIDE_VAR(v_ptr);

	/*
	 * We have to be careful not to hit the guard page.
	 * We have to be careful not to hit the guard page in vmalloc tests.
	 * The MMU will catch that and crash us.
	 */
	area = vmalloc(3000);
	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, area);

	KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)area)[3100]);
	vfree(area);
	/* Make sure in-bounds accesses are valid. */
	v_ptr[0] = 0;
	v_ptr[size - 1] = 0;

	/*
	 * An unaligned access past the requested vmalloc size.
	 * Only generic KASAN can precisely detect these.
	 */
	if (IS_ENABLED(CONFIG_KASAN_GENERIC))
		KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)v_ptr)[size]);

	/* An aligned access into the first out-of-bounds granule. */
	KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)v_ptr)[size + 5]);

	/* Check that in-bounds accesses to the physical page are valid. */
	page = vmalloc_to_page(v_ptr);
	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, page);
	p_ptr = page_address(page);
	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p_ptr);
	p_ptr[0] = 0;

	vfree(v_ptr);

	/*
	 * We can't check for use-after-unmap bugs in this nor in the following
	 * vmalloc tests, as the page might be fully unmapped and accessing it
	 * will crash the kernel.
	 */
}

static void vmap_tags(struct kunit *test)
{
	char *p_ptr, *v_ptr;
	struct page *p_page, *v_page;

	/*
	 * This test is specifically crafted for the software tag-based mode,
	 * the only tag-based mode that poisons vmap mappings.
	 */
	KASAN_TEST_NEEDS_CONFIG_ON(test, CONFIG_KASAN_SW_TAGS);

	KASAN_TEST_NEEDS_CONFIG_ON(test, CONFIG_KASAN_VMALLOC);

	p_page = alloc_pages(GFP_KERNEL, 1);
	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p_page);
	p_ptr = page_address(p_page);
	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p_ptr);

	v_ptr = vmap(&p_page, 1, VM_MAP, PAGE_KERNEL);
	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, v_ptr);

	/*
	 * We can't check for out-of-bounds bugs in this nor in the following
	 * vmalloc tests, as allocations have page granularity and accessing
	 * the guard page will crash the kernel.
	 */

	KUNIT_EXPECT_GE(test, (u8)get_tag(v_ptr), (u8)KASAN_TAG_MIN);
	KUNIT_EXPECT_LT(test, (u8)get_tag(v_ptr), (u8)KASAN_TAG_KERNEL);

	/* Make sure that in-bounds accesses through both pointers work. */
	*p_ptr = 0;
	*v_ptr = 0;

	/* Make sure vmalloc_to_page() correctly recovers the page pointer. */
	v_page = vmalloc_to_page(v_ptr);
	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, v_page);
	KUNIT_EXPECT_PTR_EQ(test, p_page, v_page);

	vunmap(v_ptr);
	free_pages((unsigned long)p_ptr, 1);
}

static void vm_map_ram_tags(struct kunit *test)
{
	char *p_ptr, *v_ptr;
	struct page *page;

	/*
	 * This test is specifically crafted for the software tag-based mode,
	 * the only tag-based mode that poisons vm_map_ram mappings.
	 */
	KASAN_TEST_NEEDS_CONFIG_ON(test, CONFIG_KASAN_SW_TAGS);

	page = alloc_pages(GFP_KERNEL, 1);
	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, page);
	p_ptr = page_address(page);
	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p_ptr);

	v_ptr = vm_map_ram(&page, 1, -1);
	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, v_ptr);

	KUNIT_EXPECT_GE(test, (u8)get_tag(v_ptr), (u8)KASAN_TAG_MIN);
	KUNIT_EXPECT_LT(test, (u8)get_tag(v_ptr), (u8)KASAN_TAG_KERNEL);

	/* Make sure that in-bounds accesses through both pointers work. */
	*p_ptr = 0;
	*v_ptr = 0;

	vm_unmap_ram(v_ptr, 1);
	free_pages((unsigned long)p_ptr, 1);
}

static void vmalloc_percpu(struct kunit *test)
{
	char __percpu *ptr;
	int cpu;

	/*
	 * This test is specifically crafted for the software tag-based mode,
	 * the only tag-based mode that poisons percpu mappings.
	 */
	KASAN_TEST_NEEDS_CONFIG_ON(test, CONFIG_KASAN_SW_TAGS);

	ptr = __alloc_percpu(PAGE_SIZE, PAGE_SIZE);

	for_each_possible_cpu(cpu) {
		char *c_ptr = per_cpu_ptr(ptr, cpu);

		KUNIT_EXPECT_GE(test, (u8)get_tag(c_ptr), (u8)KASAN_TAG_MIN);
		KUNIT_EXPECT_LT(test, (u8)get_tag(c_ptr), (u8)KASAN_TAG_KERNEL);

		/* Make sure that in-bounds accesses don't crash the kernel. */
		*c_ptr = 0;
	}

	free_percpu(ptr);
}

/*
@@ -1105,6 +1271,18 @@ static void match_all_not_assigned(struct kunit *test)
		KUNIT_EXPECT_LT(test, (u8)get_tag(ptr), (u8)KASAN_TAG_KERNEL);
		free_pages((unsigned long)ptr, order);
	}

	if (!IS_ENABLED(CONFIG_KASAN_VMALLOC))
		return;

	for (i = 0; i < 256; i++) {
		size = (get_random_int() % 1024) + 1;
		ptr = vmalloc(size);
		KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ptr);
		KUNIT_EXPECT_GE(test, (u8)get_tag(ptr), (u8)KASAN_TAG_MIN);
		KUNIT_EXPECT_LT(test, (u8)get_tag(ptr), (u8)KASAN_TAG_KERNEL);
		vfree(ptr);
	}
}

/* Check that 0xff works as a match-all pointer tag for tag-based modes. */
@@ -1210,7 +1388,11 @@ static struct kunit_case kasan_kunit_test_cases[] = {
	KUNIT_CASE(kasan_bitops_generic),
	KUNIT_CASE(kasan_bitops_tags),
	KUNIT_CASE(kmalloc_double_kzfree),
	KUNIT_CASE(vmalloc_helpers_tags),
	KUNIT_CASE(vmalloc_oob),
	KUNIT_CASE(vmap_tags),
	KUNIT_CASE(vm_map_ram_tags),
	KUNIT_CASE(vmalloc_percpu),
	KUNIT_CASE(match_all_not_assigned),
	KUNIT_CASE(match_all_ptr_tag),
	KUNIT_CASE(match_all_mem_tag),