#include <linux/uaccess.h>
 #include <linux/io.h>
 #include <linux/vmalloc.h>
+#include <linux/set_memory.h>
 
 #include <asm/page.h>
 
        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);
 }
 
 /*
                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. */
        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),