diff mbox series

[v2,28/34] kasan, vmalloc: add vmalloc support to HW_TAGS

Message ID 72a8a7aa09eb279d7eabf7ea1101556d13360950.1638825394.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. 6, 2021, 9:44 p.m. UTC
From: Andrey Konovalov <andreyknvl@google.com>

This patch adds vmalloc tagging support to HW_TAGS KASAN.

The key difference between HW_TAGS and the other two KASAN modes
when it comes to vmalloc: HW_TAGS KASAN can only assign tags to
physical memory. The other two modes have shadow memory covering
every mapped virtual memory region.

This patch makes __kasan_unpoison_vmalloc() for HW_TAGS KASAN:

- Skip non-VM_ALLOC mappings as HW_TAGS KASAN can only tag a single
  mapping of normal physical memory; see the comment in the function.
- Generate a random tag, tag the returned pointer and the allocation,
  and initialize the allocation at the same time.
- Propagate the tag into the page stucts to allow accesses through
  page_address(vmalloc_to_page()).

The rest of vmalloc-related KASAN hooks are not needed:

- The shadow-related ones are fully skipped.
- __kasan_poison_vmalloc() is kept as a no-op with a comment.

Poisoning and zeroing of physical pages that are backing vmalloc()
allocations are skipped via __GFP_SKIP_KASAN_UNPOISON and
__GFP_SKIP_ZERO: __kasan_unpoison_vmalloc() does that instead.

This patch allows enabling CONFIG_KASAN_VMALLOC with HW_TAGS
and adjusts CONFIG_KASAN_VMALLOC description:

- Mention HW_TAGS support.
- Remove unneeded internal details: they have no place in Kconfig
  description and are already explained in the documentation.

Signed-off-by: Andrey Konovalov <andreyknvl@google.com>
Co-developed-by: Vincenzo Frascino <vincenzo.frascino@arm.com>

---

Changes v1->v2:
- Allow enabling CONFIG_KASAN_VMALLOC with HW_TAGS in this patch.
- Move memory init for page_alloc pages backing vmalloc() into
  kasan_unpoison_vmalloc().
---
 include/linux/kasan.h | 30 +++++++++++++--
 lib/Kconfig.kasan     | 20 +++++-----
 mm/kasan/hw_tags.c    | 89 +++++++++++++++++++++++++++++++++++++++++++
 mm/kasan/shadow.c     | 11 +++++-
 mm/vmalloc.c          | 32 +++++++++++++---
 5 files changed, 162 insertions(+), 20 deletions(-)

Comments

Andrey Konovalov Dec. 6, 2021, 9:49 p.m. UTC | #1
On Mon, Dec 6, 2021 at 10:46 PM <andrey.konovalov@linux.dev> wrote:
>
> From: Andrey Konovalov <andreyknvl@google.com>
>
> This patch adds vmalloc tagging support to HW_TAGS KASAN.
>
> The key difference between HW_TAGS and the other two KASAN modes
> when it comes to vmalloc: HW_TAGS KASAN can only assign tags to
> physical memory. The other two modes have shadow memory covering
> every mapped virtual memory region.
>
> This patch makes __kasan_unpoison_vmalloc() for HW_TAGS KASAN:
>
> - Skip non-VM_ALLOC mappings as HW_TAGS KASAN can only tag a single
>   mapping of normal physical memory; see the comment in the function.
> - Generate a random tag, tag the returned pointer and the allocation,
>   and initialize the allocation at the same time.
> - Propagate the tag into the page stucts to allow accesses through
>   page_address(vmalloc_to_page()).
>
> The rest of vmalloc-related KASAN hooks are not needed:
>
> - The shadow-related ones are fully skipped.
> - __kasan_poison_vmalloc() is kept as a no-op with a comment.
>
> Poisoning and zeroing of physical pages that are backing vmalloc()
> allocations are skipped via __GFP_SKIP_KASAN_UNPOISON and
> __GFP_SKIP_ZERO: __kasan_unpoison_vmalloc() does that instead.
>
> This patch allows enabling CONFIG_KASAN_VMALLOC with HW_TAGS
> and adjusts CONFIG_KASAN_VMALLOC description:
>
> - Mention HW_TAGS support.
> - Remove unneeded internal details: they have no place in Kconfig
>   description and are already explained in the documentation.
>
> Signed-off-by: Andrey Konovalov <andreyknvl@google.com>
> Co-developed-by: Vincenzo Frascino <vincenzo.frascino@arm.com>
>
> ---
>
> Changes v1->v2:
> - Allow enabling CONFIG_KASAN_VMALLOC with HW_TAGS in this patch.
> - Move memory init for page_alloc pages backing vmalloc() into
>   kasan_unpoison_vmalloc().
> ---
>  include/linux/kasan.h | 30 +++++++++++++--
>  lib/Kconfig.kasan     | 20 +++++-----
>  mm/kasan/hw_tags.c    | 89 +++++++++++++++++++++++++++++++++++++++++++
>  mm/kasan/shadow.c     | 11 +++++-
>  mm/vmalloc.c          | 32 +++++++++++++---
>  5 files changed, 162 insertions(+), 20 deletions(-)
>
> diff --git a/include/linux/kasan.h b/include/linux/kasan.h
> index 6a2619759e93..0bdc2b824b9c 100644
> --- a/include/linux/kasan.h
> +++ b/include/linux/kasan.h
> @@ -417,19 +417,40 @@ static inline void kasan_init_hw_tags(void) { }
>
>  #ifdef CONFIG_KASAN_VMALLOC
>
> +#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
> +
>  void kasan_populate_early_vm_area_shadow(void *start, unsigned long size);
>  int kasan_populate_vmalloc(unsigned long addr, unsigned long size);
>  void kasan_release_vmalloc(unsigned long start, unsigned long end,
>                            unsigned long free_region_start,
>                            unsigned long free_region_end);
>
> +#else /* CONFIG_KASAN_GENERIC || CONFIG_KASAN_SW_TAGS */
> +
> +static inline void kasan_populate_early_vm_area_shadow(void *start,
> +                                                      unsigned long size)
> +{ }
> +static inline int kasan_populate_vmalloc(unsigned long start,
> +                                       unsigned long size)
> +{
> +       return 0;
> +}
> +static inline void kasan_release_vmalloc(unsigned long start,
> +                                        unsigned long end,
> +                                        unsigned long free_region_start,
> +                                        unsigned long free_region_end) { }
> +
> +#endif /* CONFIG_KASAN_GENERIC || CONFIG_KASAN_SW_TAGS */
> +
>  void * __must_check __kasan_unpoison_vmalloc(const void *start,
> -                                            unsigned long size);
> +                                            unsigned long size,
> +                                            bool vm_alloc, bool init);
>  static __always_inline void * __must_check kasan_unpoison_vmalloc(
> -                                       const void *start, unsigned long size)
> +                                       const void *start, unsigned long size,
> +                                       bool vm_alloc, bool init)
>  {
>         if (kasan_enabled())
> -               return __kasan_unpoison_vmalloc(start, size);
> +               return __kasan_unpoison_vmalloc(start, size, vm_alloc, init);
>         return (void *)start;
>  }
>
> @@ -456,7 +477,8 @@ static inline void kasan_release_vmalloc(unsigned long start,
>                                          unsigned long free_region_end) { }
>
>  static inline void *kasan_unpoison_vmalloc(const void *start,
> -                                          unsigned long size, bool unique)
> +                                          unsigned long size,
> +                                          bool vm_alloc, bool init)
>  {
>         return (void *)start;
>  }
> diff --git a/lib/Kconfig.kasan b/lib/Kconfig.kasan
> index 3f144a87f8a3..7834c35a7964 100644
> --- a/lib/Kconfig.kasan
> +++ b/lib/Kconfig.kasan
> @@ -178,17 +178,17 @@ config KASAN_TAGS_IDENTIFY
>           memory consumption.
>
>  config KASAN_VMALLOC
> -       bool "Back mappings in vmalloc space with real shadow memory"
> -       depends on (KASAN_GENERIC || KASAN_SW_TAGS) && HAVE_ARCH_KASAN_VMALLOC
> +       bool "Check accesses to vmalloc allocations"
> +       depends on HAVE_ARCH_KASAN_VMALLOC
>         help
> -         By default, the shadow region for vmalloc space is the read-only
> -         zero page. This means that KASAN cannot detect errors involving
> -         vmalloc space.
> -
> -         Enabling this option will hook in to vmap/vmalloc and back those
> -         mappings with real shadow memory allocated on demand. This allows
> -         for KASAN to detect more sorts of errors (and to support vmapped
> -         stacks), but at the cost of higher memory usage.
> +         This mode makes KASAN check accesses to vmalloc allocations for
> +         validity.
> +
> +         With software KASAN modes, checking is done for all types of vmalloc
> +         allocations. Enabling this option leads to higher memory usage.
> +
> +         With hardware tag-based KASAN, only VM_ALLOC mappings are checked.
> +         There is no additional memory usage.
>
>  config KASAN_KUNIT_TEST
>         tristate "KUnit-compatible tests of KASAN bug detection capabilities" if !KUNIT_ALL_TESTS
> diff --git a/mm/kasan/hw_tags.c b/mm/kasan/hw_tags.c
> index 76cf2b6229c7..837c260beec6 100644
> --- a/mm/kasan/hw_tags.c
> +++ b/mm/kasan/hw_tags.c
> @@ -192,6 +192,95 @@ void __init kasan_init_hw_tags(void)
>                 kasan_stack_collection_enabled() ? "on" : "off");
>  }
>
> +#ifdef CONFIG_KASAN_VMALLOC
> +
> +static void unpoison_vmalloc_pages(const void *addr, u8 tag)
> +{
> +       struct vm_struct *area;
> +       int i;
> +
> +       /*
> +        * As hardware tag-based KASAN only tags VM_ALLOC vmalloc allocations
> +        * (see the comment in __kasan_unpoison_vmalloc), all of the pages
> +        * should belong to a single area.
> +        */
> +       area = find_vm_area((void *)addr);
> +       if (WARN_ON(!area))
> +               return;
> +
> +       for (i = 0; i < area->nr_pages; i++) {
> +               struct page *page = area->pages[i];
> +
> +               page_kasan_tag_set(page, tag);
> +       }
> +}
> +
> +void *__kasan_unpoison_vmalloc(const void *start, unsigned long size,
> +                               bool vm_alloc, bool init)
> +{
> +       u8 tag;
> +       unsigned long redzone_start, redzone_size;
> +
> +       if (!is_vmalloc_or_module_addr(start))
> +               return (void *)start;
> +
> +       /* Unpoisoning and pointer tag assignment is skipped for non-VM_ALLOC
> +        * mappings as:
> +        *
> +        * 1. Unlike the software KASAN modes, hardware tag-based KASAN only
> +        *    supports tagging physical memory. Therefore, it can only tag a
> +        *    single mapping of normal physical pages.
> +        * 2. Hardware tag-based KASAN can only tag memory mapped with special
> +        *    mapping protection bits, see arch_vmalloc_pgprot_modify().
> +        *    As non-VM_ALLOC mappings can be mapped outside of vmalloc code,
> +        *    providing these bits would require tracking all non-VM_ALLOC
> +        *    mappers.
> +        *
> +        * Thus, for VM_ALLOC mappings, hardware tag-based KASAN only tags
> +        * the first virtual mapping, which is created by vmalloc().
> +        * Tagging the page_alloc memory backing that vmalloc() allocation is
> +        * skipped, see ___GFP_SKIP_KASAN_UNPOISON.
> +        *
> +        * For non-VM_ALLOC allocations, page_alloc memory is tagged as usual.
> +        */
> +       if (!vm_alloc)
> +               return (void *)start;
> +
> +       tag = kasan_random_tag();
> +       start = set_tag(start, tag);
> +
> +       /* Unpoison and initialize memory up to size. */
> +       kasan_unpoison(start, size, init);
> +
> +       /*
> +        * Explicitly poison and initialize the in-page vmalloc() redzone.
> +        * Unlike software KASAN modes, hardware tag-based KASAN doesn't
> +        * unpoison memory when populating shadow for vmalloc() space.
> +        */
> +       redzone_start = round_up((unsigned long)start + size, KASAN_GRANULE_SIZE);
> +       redzone_size = round_up(redzone_start, PAGE_SIZE) - redzone_start;
> +       kasan_poison((void *)redzone_start, redzone_size, KASAN_TAG_INVALID, init);
> +
> +       /*
> +        * Set per-page tag flags to allow accessing physical memory for the
> +        * vmalloc() mapping through page_address(vmalloc_to_page()).
> +        */
> +       unpoison_vmalloc_pages(start, tag);
> +
> +       return (void *)start;
> +}
> +
> +void __kasan_poison_vmalloc(const void *start, unsigned long size)
> +{
> +       /*
> +        * No tagging here.
> +        * The physical pages backing the vmalloc() allocation are poisoned
> +        * through the usual page_alloc paths.
> +        */
> +}
> +
> +#endif
> +
>  #if IS_ENABLED(CONFIG_KASAN_KUNIT_TEST)
>
>  void kasan_enable_tagging_sync(void)
> diff --git a/mm/kasan/shadow.c b/mm/kasan/shadow.c
> index 4ca280a96fbc..8600dd925f35 100644
> --- a/mm/kasan/shadow.c
> +++ b/mm/kasan/shadow.c
> @@ -475,8 +475,17 @@ void kasan_release_vmalloc(unsigned long start, unsigned long end,
>         }
>  }
>
> -void *__kasan_unpoison_vmalloc(const void *start, unsigned long size)
> +void *__kasan_unpoison_vmalloc(const void *start, unsigned long size,
> +                               bool vm_alloc, bool init)
>  {
> +       /*
> +        * Software tag-based KASAN tags both VM_ALLOC and non-VM_ALLOC
> +        * mappings, so the vm_alloc argument is ignored.
> +        * Software tag-based KASAN can't optimize zeroing memory by combining
> +        * it with setting memory tags, so the init argument is ignored;
> +        * vmalloc() memory is poisoned via page_alloc.
> +        */
> +
>         if (!is_vmalloc_or_module_addr(start))
>                 return (void *)start;
>
> diff --git a/mm/vmalloc.c b/mm/vmalloc.c
> index 82ef1e27e2e4..d48db7cc3358 100644
> --- a/mm/vmalloc.c
> +++ b/mm/vmalloc.c
> @@ -2214,8 +2214,12 @@ void *vm_map_ram(struct page **pages, unsigned int count, int node)
>                 return NULL;
>         }
>
> -       /* Mark the pages as accessible after they were mapped in. */
> -       mem = kasan_unpoison_vmalloc(mem, size);
> +       /*
> +        * Mark the pages as accessible after they were mapped in.
> +        * With hardware tag-based KASAN, marking is skipped for
> +        * non-VM_ALLOC mappings, see __kasan_unpoison_vmalloc().
> +        */
> +       mem = kasan_unpoison_vmalloc(mem, size, false, false);
>
>         return mem;
>  }
> @@ -2449,9 +2453,12 @@ static struct vm_struct *__get_vm_area_node(unsigned long size,
>          * accessible after they are mapped in.
>          * Otherwise, as the pages can be mapped outside of vmalloc code,
>          * mark them now as a best-effort approach.
> +        * With hardware tag-based KASAN, marking is skipped for
> +        * non-VM_ALLOC mappings, see __kasan_unpoison_vmalloc().
>          */
>         if (!(flags & VM_ALLOC))
> -               area->addr = kasan_unpoison_vmalloc(area->addr, requested_size);
> +               area->addr = kasan_unpoison_vmalloc(area->addr, requested_size,
> +                                                       false, false);
>
>         return area;
>  }
> @@ -2849,6 +2856,12 @@ vm_area_alloc_pages(gfp_t gfp, int nid,
>         struct page *page;
>         int i;
>
> +       /*
> +        * Skip page_alloc poisoning and zeroing for pages backing VM_ALLOC
> +        * mappings. Only effective in HW_TAGS mode.
> +        */
> +       gfp &= __GFP_SKIP_KASAN_UNPOISON & __GFP_SKIP_ZERO;
> +
>         /*
>          * For order-0 pages we make use of bulk allocator, if
>          * the page array is partly or not at all populated due
> @@ -3027,6 +3040,7 @@ void *__vmalloc_node_range(unsigned long size, unsigned long align,
>  {
>         struct vm_struct *area;
>         void *addr;
> +       bool init;
>         unsigned long real_size = size;
>         unsigned long real_align = align;
>         unsigned int shift = PAGE_SHIFT;
> @@ -3083,8 +3097,13 @@ void *__vmalloc_node_range(unsigned long size, unsigned long align,
>         /*
>          * Mark the pages for VM_ALLOC mappings as accessible after they were
>          * mapped in.
> +        * The init condition should match the one in post_alloc_hook()
> +        * (except for the should_skip_init() check) to make sure that memory
> +        * is initialized under the same conditions regardless of the enabled
> +        * KASAN mode.
>          */
> -       addr = kasan_unpoison_vmalloc(addr, real_size);
> +       init = !want_init_on_free() && want_init_on_alloc(gfp_mask);
> +       addr = kasan_unpoison_vmalloc(addr, real_size, true, init);
>
>         /*
>          * In this function, newly allocated vm_struct has VM_UNINITIALIZED
> @@ -3784,10 +3803,13 @@ struct vm_struct **pcpu_get_vm_areas(const unsigned long *offsets,
>          * Mark allocated areas as accessible.
>          * As the pages are mapped outside of vmalloc code,
>          * mark them now as a best-effort approach.
> +        * With hardware tag-based KASAN, marking is skipped for
> +        * non-VM_ALLOC mappings, see __kasan_unpoison_vmalloc().
>          */
>         for (area = 0; area < nr_vms; area++)
>                 vms[area]->addr = kasan_unpoison_vmalloc(vms[area]->addr,
> -                                                        vms[area]->size);
> +                                                        vms[area]->size,
> +                                                        false, false);
>
>         kfree(vas);
>         return vms;
> --
> 2.25.1
>

Hi Vincenzo,

This patch is partially based on an early version of the HW_TAGS
series you had. Could you PTAL and give your sign-off?

Thanks!
Vincenzo Frascino Dec. 13, 2021, 3:34 p.m. UTC | #2
Hi Andrey,

On 12/6/21 9:44 PM, andrey.konovalov@linux.dev wrote:
> From: Andrey Konovalov <andreyknvl@google.com>
> 
> This patch adds vmalloc tagging support to HW_TAGS KASAN.
> 

Can we reorganize the patch description in line with what I commented on patch 24?

> The key difference between HW_TAGS and the other two KASAN modes
> when it comes to vmalloc: HW_TAGS KASAN can only assign tags to
> physical memory. The other two modes have shadow memory covering
> every mapped virtual memory region.
> 
> This patch makes __kasan_unpoison_vmalloc() for HW_TAGS KASAN:
> 
> - Skip non-VM_ALLOC mappings as HW_TAGS KASAN can only tag a single
>   mapping of normal physical memory; see the comment in the function.
> - Generate a random tag, tag the returned pointer and the allocation,
>   and initialize the allocation at the same time.
> - Propagate the tag into the page stucts to allow accesses through
>   page_address(vmalloc_to_page()).
> 
> The rest of vmalloc-related KASAN hooks are not needed:
> 
> - The shadow-related ones are fully skipped.
> - __kasan_poison_vmalloc() is kept as a no-op with a comment.
> 
> Poisoning and zeroing of physical pages that are backing vmalloc()
> allocations are skipped via __GFP_SKIP_KASAN_UNPOISON and
> __GFP_SKIP_ZERO: __kasan_unpoison_vmalloc() does that instead.
> 
> This patch allows enabling CONFIG_KASAN_VMALLOC with HW_TAGS
> and adjusts CONFIG_KASAN_VMALLOC description:
> 
> - Mention HW_TAGS support.
> - Remove unneeded internal details: they have no place in Kconfig
>   description and are already explained in the documentation.
> 
> Signed-off-by: Andrey Konovalov <andreyknvl@google.com>
> Co-developed-by: Vincenzo Frascino <vincenzo.frascino@arm.com>
> 
> ---
> 
> Changes v1->v2:
> - Allow enabling CONFIG_KASAN_VMALLOC with HW_TAGS in this patch.
> - Move memory init for page_alloc pages backing vmalloc() into
>   kasan_unpoison_vmalloc().
> ---
>  include/linux/kasan.h | 30 +++++++++++++--
>  lib/Kconfig.kasan     | 20 +++++-----
>  mm/kasan/hw_tags.c    | 89 +++++++++++++++++++++++++++++++++++++++++++
>  mm/kasan/shadow.c     | 11 +++++-
>  mm/vmalloc.c          | 32 +++++++++++++---
>  5 files changed, 162 insertions(+), 20 deletions(-)
> 
> diff --git a/include/linux/kasan.h b/include/linux/kasan.h
> index 6a2619759e93..0bdc2b824b9c 100644
> --- a/include/linux/kasan.h
> +++ b/include/linux/kasan.h
> @@ -417,19 +417,40 @@ static inline void kasan_init_hw_tags(void) { }
>  
>  #ifdef CONFIG_KASAN_VMALLOC
>  
> +#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
> +
>  void kasan_populate_early_vm_area_shadow(void *start, unsigned long size);
>  int kasan_populate_vmalloc(unsigned long addr, unsigned long size);
>  void kasan_release_vmalloc(unsigned long start, unsigned long end,
>  			   unsigned long free_region_start,
>  			   unsigned long free_region_end);
>  
> +#else /* CONFIG_KASAN_GENERIC || CONFIG_KASAN_SW_TAGS */
> +
> +static inline void kasan_populate_early_vm_area_shadow(void *start,
> +						       unsigned long size)
> +{ }
> +static inline int kasan_populate_vmalloc(unsigned long start,
> +					unsigned long size)
> +{
> +	return 0;
> +}
> +static inline void kasan_release_vmalloc(unsigned long start,
> +					 unsigned long end,
> +					 unsigned long free_region_start,
> +					 unsigned long free_region_end) { }
> +
> +#endif /* CONFIG_KASAN_GENERIC || CONFIG_KASAN_SW_TAGS */
> +
>  void * __must_check __kasan_unpoison_vmalloc(const void *start,
> -					     unsigned long size);
> +					     unsigned long size,
> +					     bool vm_alloc, bool init);
>  static __always_inline void * __must_check kasan_unpoison_vmalloc(
> -					const void *start, unsigned long size)
> +					const void *start, unsigned long size,
> +					bool vm_alloc, bool init)

Can we replace booleans with enumerations? It should make the code clearer on
the calling site.

...

With these changes:

Signed-off-by: Vincenzo Frascino <vincenzo.frascino@arm.com>

---

Regards,
Vincenzo
Andrey Konovalov Dec. 13, 2021, 9:57 p.m. UTC | #3
On Mon, Dec 13, 2021 at 4:34 PM Vincenzo Frascino
<vincenzo.frascino@arm.com> wrote:
>
> Hi Andrey,
>
> On 12/6/21 9:44 PM, andrey.konovalov@linux.dev wrote:
> > From: Andrey Konovalov <andreyknvl@google.com>
> >
> > This patch adds vmalloc tagging support to HW_TAGS KASAN.
> >
>
> Can we reorganize the patch description in line with what I commented on patch 24?

Hi Vincenzo,

Done in v3.

> >  void * __must_check __kasan_unpoison_vmalloc(const void *start,
> > -                                          unsigned long size);
> > +                                          unsigned long size,
> > +                                          bool vm_alloc, bool init);
> >  static __always_inline void * __must_check kasan_unpoison_vmalloc(
> > -                                     const void *start, unsigned long size)
> > +                                     const void *start, unsigned long size,
> > +                                     bool vm_alloc, bool init)
>
> Can we replace booleans with enumerations? It should make the code clearer on
> the calling site.

I think we can add a single argument for named flags to improve
readability. Done in v3.

> With these changes:
>
> Signed-off-by: Vincenzo Frascino <vincenzo.frascino@arm.com>

Thanks!
diff mbox series

Patch

diff --git a/include/linux/kasan.h b/include/linux/kasan.h
index 6a2619759e93..0bdc2b824b9c 100644
--- a/include/linux/kasan.h
+++ b/include/linux/kasan.h
@@ -417,19 +417,40 @@  static inline void kasan_init_hw_tags(void) { }
 
 #ifdef CONFIG_KASAN_VMALLOC
 
+#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
+
 void kasan_populate_early_vm_area_shadow(void *start, unsigned long size);
 int kasan_populate_vmalloc(unsigned long addr, unsigned long size);
 void kasan_release_vmalloc(unsigned long start, unsigned long end,
 			   unsigned long free_region_start,
 			   unsigned long free_region_end);
 
+#else /* CONFIG_KASAN_GENERIC || CONFIG_KASAN_SW_TAGS */
+
+static inline void kasan_populate_early_vm_area_shadow(void *start,
+						       unsigned long size)
+{ }
+static inline int kasan_populate_vmalloc(unsigned long start,
+					unsigned long size)
+{
+	return 0;
+}
+static inline void kasan_release_vmalloc(unsigned long start,
+					 unsigned long end,
+					 unsigned long free_region_start,
+					 unsigned long free_region_end) { }
+
+#endif /* CONFIG_KASAN_GENERIC || CONFIG_KASAN_SW_TAGS */
+
 void * __must_check __kasan_unpoison_vmalloc(const void *start,
-					     unsigned long size);
+					     unsigned long size,
+					     bool vm_alloc, bool init);
 static __always_inline void * __must_check kasan_unpoison_vmalloc(
-					const void *start, unsigned long size)
+					const void *start, unsigned long size,
+					bool vm_alloc, bool init)
 {
 	if (kasan_enabled())
-		return __kasan_unpoison_vmalloc(start, size);
+		return __kasan_unpoison_vmalloc(start, size, vm_alloc, init);
 	return (void *)start;
 }
 
@@ -456,7 +477,8 @@  static inline void kasan_release_vmalloc(unsigned long start,
 					 unsigned long free_region_end) { }
 
 static inline void *kasan_unpoison_vmalloc(const void *start,
-					   unsigned long size, bool unique)
+					   unsigned long size,
+					   bool vm_alloc, bool init)
 {
 	return (void *)start;
 }
diff --git a/lib/Kconfig.kasan b/lib/Kconfig.kasan
index 3f144a87f8a3..7834c35a7964 100644
--- a/lib/Kconfig.kasan
+++ b/lib/Kconfig.kasan
@@ -178,17 +178,17 @@  config KASAN_TAGS_IDENTIFY
 	  memory consumption.
 
 config KASAN_VMALLOC
-	bool "Back mappings in vmalloc space with real shadow memory"
-	depends on (KASAN_GENERIC || KASAN_SW_TAGS) && HAVE_ARCH_KASAN_VMALLOC
+	bool "Check accesses to vmalloc allocations"
+	depends on HAVE_ARCH_KASAN_VMALLOC
 	help
-	  By default, the shadow region for vmalloc space is the read-only
-	  zero page. This means that KASAN cannot detect errors involving
-	  vmalloc space.
-
-	  Enabling this option will hook in to vmap/vmalloc and back those
-	  mappings with real shadow memory allocated on demand. This allows
-	  for KASAN to detect more sorts of errors (and to support vmapped
-	  stacks), but at the cost of higher memory usage.
+	  This mode makes KASAN check accesses to vmalloc allocations for
+	  validity.
+
+	  With software KASAN modes, checking is done for all types of vmalloc
+	  allocations. Enabling this option leads to higher memory usage.
+
+	  With hardware tag-based KASAN, only VM_ALLOC mappings are checked.
+	  There is no additional memory usage.
 
 config KASAN_KUNIT_TEST
 	tristate "KUnit-compatible tests of KASAN bug detection capabilities" if !KUNIT_ALL_TESTS
diff --git a/mm/kasan/hw_tags.c b/mm/kasan/hw_tags.c
index 76cf2b6229c7..837c260beec6 100644
--- a/mm/kasan/hw_tags.c
+++ b/mm/kasan/hw_tags.c
@@ -192,6 +192,95 @@  void __init kasan_init_hw_tags(void)
 		kasan_stack_collection_enabled() ? "on" : "off");
 }
 
+#ifdef CONFIG_KASAN_VMALLOC
+
+static void unpoison_vmalloc_pages(const void *addr, u8 tag)
+{
+	struct vm_struct *area;
+	int i;
+
+	/*
+	 * As hardware tag-based KASAN only tags VM_ALLOC vmalloc allocations
+	 * (see the comment in __kasan_unpoison_vmalloc), all of the pages
+	 * should belong to a single area.
+	 */
+	area = find_vm_area((void *)addr);
+	if (WARN_ON(!area))
+		return;
+
+	for (i = 0; i < area->nr_pages; i++) {
+		struct page *page = area->pages[i];
+
+		page_kasan_tag_set(page, tag);
+	}
+}
+
+void *__kasan_unpoison_vmalloc(const void *start, unsigned long size,
+				bool vm_alloc, bool init)
+{
+	u8 tag;
+	unsigned long redzone_start, redzone_size;
+
+	if (!is_vmalloc_or_module_addr(start))
+		return (void *)start;
+
+	/* Unpoisoning and pointer tag assignment is skipped for non-VM_ALLOC
+	 * mappings as:
+	 *
+	 * 1. Unlike the software KASAN modes, hardware tag-based KASAN only
+	 *    supports tagging physical memory. Therefore, it can only tag a
+	 *    single mapping of normal physical pages.
+	 * 2. Hardware tag-based KASAN can only tag memory mapped with special
+	 *    mapping protection bits, see arch_vmalloc_pgprot_modify().
+	 *    As non-VM_ALLOC mappings can be mapped outside of vmalloc code,
+	 *    providing these bits would require tracking all non-VM_ALLOC
+	 *    mappers.
+	 *
+	 * Thus, for VM_ALLOC mappings, hardware tag-based KASAN only tags
+	 * the first virtual mapping, which is created by vmalloc().
+	 * Tagging the page_alloc memory backing that vmalloc() allocation is
+	 * skipped, see ___GFP_SKIP_KASAN_UNPOISON.
+	 *
+	 * For non-VM_ALLOC allocations, page_alloc memory is tagged as usual.
+	 */
+	if (!vm_alloc)
+		return (void *)start;
+
+	tag = kasan_random_tag();
+	start = set_tag(start, tag);
+
+	/* Unpoison and initialize memory up to size. */
+	kasan_unpoison(start, size, init);
+
+	/*
+	 * Explicitly poison and initialize the in-page vmalloc() redzone.
+	 * Unlike software KASAN modes, hardware tag-based KASAN doesn't
+	 * unpoison memory when populating shadow for vmalloc() space.
+	 */
+	redzone_start = round_up((unsigned long)start + size, KASAN_GRANULE_SIZE);
+	redzone_size = round_up(redzone_start, PAGE_SIZE) - redzone_start;
+	kasan_poison((void *)redzone_start, redzone_size, KASAN_TAG_INVALID, init);
+
+	/*
+	 * Set per-page tag flags to allow accessing physical memory for the
+	 * vmalloc() mapping through page_address(vmalloc_to_page()).
+	 */
+	unpoison_vmalloc_pages(start, tag);
+
+	return (void *)start;
+}
+
+void __kasan_poison_vmalloc(const void *start, unsigned long size)
+{
+	/*
+	 * No tagging here.
+	 * The physical pages backing the vmalloc() allocation are poisoned
+	 * through the usual page_alloc paths.
+	 */
+}
+
+#endif
+
 #if IS_ENABLED(CONFIG_KASAN_KUNIT_TEST)
 
 void kasan_enable_tagging_sync(void)
diff --git a/mm/kasan/shadow.c b/mm/kasan/shadow.c
index 4ca280a96fbc..8600dd925f35 100644
--- a/mm/kasan/shadow.c
+++ b/mm/kasan/shadow.c
@@ -475,8 +475,17 @@  void kasan_release_vmalloc(unsigned long start, unsigned long end,
 	}
 }
 
-void *__kasan_unpoison_vmalloc(const void *start, unsigned long size)
+void *__kasan_unpoison_vmalloc(const void *start, unsigned long size,
+				bool vm_alloc, bool init)
 {
+	/*
+	 * Software tag-based KASAN tags both VM_ALLOC and non-VM_ALLOC
+	 * mappings, so the vm_alloc argument is ignored.
+	 * Software tag-based KASAN can't optimize zeroing memory by combining
+	 * it with setting memory tags, so the init argument is ignored;
+	 * vmalloc() memory is poisoned via page_alloc.
+	 */
+
 	if (!is_vmalloc_or_module_addr(start))
 		return (void *)start;
 
diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index 82ef1e27e2e4..d48db7cc3358 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -2214,8 +2214,12 @@  void *vm_map_ram(struct page **pages, unsigned int count, int node)
 		return NULL;
 	}
 
-	/* Mark the pages as accessible after they were mapped in. */
-	mem = kasan_unpoison_vmalloc(mem, size);
+	/*
+	 * Mark the pages as accessible after they were mapped in.
+	 * With hardware tag-based KASAN, marking is skipped for
+	 * non-VM_ALLOC mappings, see __kasan_unpoison_vmalloc().
+	 */
+	mem = kasan_unpoison_vmalloc(mem, size, false, false);
 
 	return mem;
 }
@@ -2449,9 +2453,12 @@  static struct vm_struct *__get_vm_area_node(unsigned long size,
 	 * accessible after they are mapped in.
 	 * Otherwise, as the pages can be mapped outside of vmalloc code,
 	 * mark them now as a best-effort approach.
+	 * With hardware tag-based KASAN, marking is skipped for
+	 * non-VM_ALLOC mappings, see __kasan_unpoison_vmalloc().
 	 */
 	if (!(flags & VM_ALLOC))
-		area->addr = kasan_unpoison_vmalloc(area->addr, requested_size);
+		area->addr = kasan_unpoison_vmalloc(area->addr, requested_size,
+							false, false);
 
 	return area;
 }
@@ -2849,6 +2856,12 @@  vm_area_alloc_pages(gfp_t gfp, int nid,
 	struct page *page;
 	int i;
 
+	/*
+	 * Skip page_alloc poisoning and zeroing for pages backing VM_ALLOC
+	 * mappings. Only effective in HW_TAGS mode.
+	 */
+	gfp &= __GFP_SKIP_KASAN_UNPOISON & __GFP_SKIP_ZERO;
+
 	/*
 	 * For order-0 pages we make use of bulk allocator, if
 	 * the page array is partly or not at all populated due
@@ -3027,6 +3040,7 @@  void *__vmalloc_node_range(unsigned long size, unsigned long align,
 {
 	struct vm_struct *area;
 	void *addr;
+	bool init;
 	unsigned long real_size = size;
 	unsigned long real_align = align;
 	unsigned int shift = PAGE_SHIFT;
@@ -3083,8 +3097,13 @@  void *__vmalloc_node_range(unsigned long size, unsigned long align,
 	/*
 	 * Mark the pages for VM_ALLOC mappings as accessible after they were
 	 * mapped in.
+	 * The init condition should match the one in post_alloc_hook()
+	 * (except for the should_skip_init() check) to make sure that memory
+	 * is initialized under the same conditions regardless of the enabled
+	 * KASAN mode.
 	 */
-	addr = kasan_unpoison_vmalloc(addr, real_size);
+	init = !want_init_on_free() && want_init_on_alloc(gfp_mask);
+	addr = kasan_unpoison_vmalloc(addr, real_size, true, init);
 
 	/*
 	 * In this function, newly allocated vm_struct has VM_UNINITIALIZED
@@ -3784,10 +3803,13 @@  struct vm_struct **pcpu_get_vm_areas(const unsigned long *offsets,
 	 * Mark allocated areas as accessible.
 	 * As the pages are mapped outside of vmalloc code,
 	 * mark them now as a best-effort approach.
+	 * With hardware tag-based KASAN, marking is skipped for
+	 * non-VM_ALLOC mappings, see __kasan_unpoison_vmalloc().
 	 */
 	for (area = 0; area < nr_vms; area++)
 		vms[area]->addr = kasan_unpoison_vmalloc(vms[area]->addr,
-							 vms[area]->size);
+							 vms[area]->size,
+							 false, false);
 
 	kfree(vas);
 	return vms;