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 |
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!
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
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 --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;