查看原文
其他

​浅谈 ARM64 基于硬件 tag 的 KASAN

bang Linux阅码场 2022-12-14

作者简介

bang,linux内核爱好者,目前就职于杭州某安防公司,主要从事于SOC的bring up及驱动开发,喜欢分析linux内核内存管理和调度子系统。



本文所述的基于硬件 tag 的 KASAN 只有理论没有实践,主要参考了一些国外的文献,以及自己对源码的阅读和手册的理解,算是对硬件 tag 的 KASAN 的抛砖引玉。由于水平有限,只能浅谈。内核版本为 kernel-5.16


1.概述


踩内存是非常让人头疼的一类问题,导致的现象也是千奇百怪。如果踩的内存位于数据区域,则可能只是导致逻辑异常,并不会直接产生 kernel painc。虽然只是逻辑错误,但可能会产生各种奇怪的现象,严重时可能会出现系统宕机。如果踩的内存存储的是指针,可能会导致 kernel panic,而且产生 kernel panic的元凶很可能不能直接通过 log 定位出来,因为被踩的指针只是受害者,元凶还在逍遥法外,这时候检测踩内存的工具就呼之欲出了。目前常用的检测内核态踩内存工具有 hardware breakpointSLUB_DEBUGKCSANKASAN 等,其中检测类型最全面最有效的要属 KASAN 工具了。KernelAddressSANitizer(KASAN)是一个动态的内存错误检测工具,主要用来发现 use-after-free out-of-bounds等内存访问异常问题。KASAN 目前有三种实现方法,分别为 Generic KASANSoftware tag-based KASANHardware tag-based KASAN,其中 Generic KASANSoftware tag-based KASAN 是基于软件实现,Hardware tag-based KASAN 是基于硬件实现的。下面我们先了解下三种 KASAN 的基本原理,然后重点分析下基于硬件 tag 的 KASAN 的实现。


1.1 Generic KASAN


软件 KASAN 模式使用影子内存来记录每个内存字节是否可以安全访问,并在编译时插入影子内存检查指令。Generic KASAN 将 1/8 的内存专用于其影子内存,并使用固定偏移映射方式将对应内存地址转换为其相应的影子地址。地址转换关系如下函数:

static inline void *kasan_mem_to_shadow(const void *addr){return (void *)((unsigned long)addr >> KASAN_SHADOW_SCALE_SHIFT)+ KASAN_SHADOW_OFFSET;}


编译器在每次访问大小为 1、2、4、8 或 16 字节的内存之前插入检测函数(``__asan_load*(addr)``、``__asan_store*(addr)``),这些函数会检测内存的访问是否有效。


1.2 Software tag-based KASAN


基于软件 tag 的 KASAN 使用软件内存标记方法来检查访问的内存是否有效,目前仅在 arm64 架构上实现。基于软件 tag 的 KASAN 使用 arm64 CPUTop Byte Ignore(TBI)功能将指针 tag 存储在指针的顶部字节中。它使用影子内存来存储与每个 16 字节内存单元关联的内存 tag(因此,它将内核内存的 1/16 专用于影子内存)。在每次内存分配时,生成一个随机 tag,用这个 tag 标记分配的内存,并将相同的 tag 嵌入到返回的指针中。编译时,在每次内存访问之前插入检测指 令,这些检测指令会比较正在访问的内存的 tag(影子区)和用于访问该内存的指针的 tag 是否匹配。如果不匹配,基于软件 tag 的 KASAN 会打印错误报告。Software tag-based KASAN 使用 0xFF 作为匹配所有指针 tag(不检查通过带有 0xFF 指针 tag 的指针进行的访问)。0xFE 用于标记释放的内存区域。目前Software tag-based KASAN 只支持 slab 和 page_alloc 内存检测。我们简单总结下 Software tag-based KASAN 的特点:1、编译时插入用于检测内存访问指令。2、专用的影子内存大小为检测内存大小的 1/16。3、通过指令对比内存 tag(影子区域)和指针 tag。


1.3 Hardware tag-based KASAN


基于硬件 tag 的 KASAN 和基于软件 tag 的 KASAN 原理非常类似,Software tag-based KASAN 的内存的 tag(影子区)和用于访问该内存的指针的 tag 的比较是由插入的汇编指令完成的,而 Hardware tag-based KASAN 是由硬件自动完成比较的,对于软件是透明的。Hardware tag-based KASAN 目前也是仅支持 arm64 架构,并且它的实现是基于 ARMv8.5 指令集架构中引入的内存标记扩展 (MTE) 和 Top Byte Ignore(TBI)。在每次内存访问时,硬件都会比较正在访问的内存的 tag 和用于访问该内存指针的 tag。如果 tag 不匹配,则会触发一个异常。基于硬件 tag 的 KASAN 使用 0xFF 作为匹配所有指针 tag(不检查通过带有0xFF 指针 tag 的指针进行的访问)。0xFE 用于标记释放的内存区域。目前只支持slab page_alloc 检测。这些特性和 Software tag-based KASAN 一样。如果硬件不支持 MTE(ARMv8.5 之前),则不会启用 Hardware tag-based KASAN。在这种情况下,所有 KASAN 引导参数都将被忽略。


2.基本概念和实现原理


Hardware tag-based KASAN目前仅支持ARM64架构,具体实现依赖Top Byte Ignore(TBI)Memory Tagging Extension(MTE)机制。

2.1 TBI

Top Byte Ignore(TBI)顾名思义高字节忽略。TBIARMv8引入的一项硬件功能,对于ARM64架构会使用64位指针来寻址内存,TBI允许软件使用64位指针的高8 bits用于其它用途。此功能可以通过设置TCR_EL1寄存器的TBI1对应的bit[38]位来实现。启用后,虚拟地址的高八位([63:56])将自动被忽略。通常只有48位被硬件实际使用到了,如果启用了特殊的large-address-space选项,则是52位。所以有12-16位(其中TBI使用了高8bit[63-56])可以拿来用做其它用途。memory tagging extension(MTE)就是其用途之一。



 
2.2 MTE

memory tagging extension(MTE)是ARMv8.5引入的一个新的特性,在指针的高字节中存储一个4 bits的tag,即是key(运用TBI特性)。每16字节的内存块中使用专用的内存(tagging memory)存储tag,即是lock。通过keylock把指针和要访问的16字节内存块关联起来,当通过一个指针来访问内存区域时,存储在指针中的key值会与指针所引用的内存块的lock值进行比较,如果两者不匹配,则会触发异常。

2.2.1 MTE检测原理

MTE的检测过程是由硬件自动完成的。当使能MTE功能,执行Load/Stroe指令时,MTE会检测pointer tags ([59:56])对应的keymemory tags对应的lock是否相等?如果相等,则可以正常访问内存。如果不相等,则抛出异常。


从上图可以看出MTE的基础模型是一个“锁和钥匙”方案。对于图中的前两个指针,该pointer tagskey值和memory tagslock值一致,则使用这些指针对内存的访问和我们平时的使用没什么区别。但是,对于下面两个指针的访问,keylock值并不相等,这将被CPU捕获,然后触发异常。通过这种机制,可以很容易地检测到难以捕获的内存安全问题。这意味着即使是概率很低的问题,一旦触发tag mismatch,也会立即被发现,有助于一般性问题的调试。 

对于MTE的检测我们主要关注pointer tagsmemory tags,下面我们先了解下它们的特性。

2.3 pointer/memory tags

存放在指针中的tag称为pointer tags或者logical tags,内存块对应的tag称之为memory tags或者allocation tags

2.3.1 pointer tags
 


  • 1、 MTEpointer tags就是利用Armv8-ATBI(Top Byte Ignore)特性,使用指针的高4 bits存储tag。因为用4 bits表示,所以共有16种不同的tag。


  • 2、 pointer tags存放在虚拟地址的[59:56] bit。


  • 3、 pointer tags可以通过使用自定义算法或者新增的MTE汇编指令(如IRG指令)生成对应的tag


  • 4、 MTEpointer tags也称为logical tags



2.3.2  memory tags
 


  • 1、 MTEmemory tags放在专用内存中,存储的位置对软件是透明的,无需软件参与显示分配。


  • 2、 每一个tag占用4 bits,所以有16种不同的tag


  • 3、 4-bits tag对应16字节内存块,则memory tags的内存消耗~3%。


  • 4、 memory tags生成和pointer tags类似,而存储是通过新增的MTE汇编指令实现(如STG指令)的。每一个memory tags对应16字节内存块,对于大内存的申请,这意味需要多次执行STG指令才能完全覆盖分配的内存。


  • 5、 MTE memory tags也称为allocation tags。



2.3.3 tags

从上面的描述我们可以知道memory tags由4个bits组成,则理论上的取值范围为[0x0,0xF],从内核头文件中我们可以看到random tags的取值范围为[0xF0,0xFD]。最小值为0xF0,最大值为0xFD。其中0xFF表示匹配所有指针tag(不检查通过0xff标记的指针的内存访问)。0xFE表示是释放的内存区域。

#define KASAN_TAG_KERNEL 0xFF /* native kernel pointers tag */#define KASAN_TAG_INVALID 0xFE /* inaccessible memory tag */ #define KASAN_TAG_MAX 0xFD /* maximum value for random tags */#define KASAN_TAG_MIN 0xF0 /* minimum value for random tags */


Hardware tag-based KASAN的tag既可以通过软件算法生成,又可以通过硬件生成。而linux内核是通过irg指令获取随机tag的。

/* Generate a random tag. */static inline u8 mte_get_random_tag(void){void *addr;asm(__MTE_PREAMBLE "irg %0, %0": "=r" (addr));return mte_get_ptr_tag(addr);}static inline u8 mte_get_ptr_tag(void *ptr) {/* Note: The format of KASAN tags is 0xF<x> */u8 tag = 0xF0 | (u8)(((u64)(ptr)) >> MTE_TAG_SHIFT);return tag;}


mte_get_ptr_tag函数中,我们可以看出Hardware tag-based KASANtag值从irg获取后,会再或上0xF0。从上面的描述我们可以猜测irg生成随机tag的取值范围为[0x0,0xD]。通过下表我们可以了解下其它MTE的新增指令


Instruction
Name
说明
IRG
Insert Random Tag
生成随机的pointer tag
LDG
Load Allocation Tag
获取memory tag
STG
Store Allocation Tag
存储memory tag
STZG
Store Allocation Tag, Zeroing
存储memory tag,并将内存数据归零,比STG单独标记和把数据清0性能好。

其它更多指令见https://en.wikichip.org/wiki/arm/mte

2.4 sync/async

当执行Load/Store指令时,检测到lockkey不匹配,可选择触发一个异常。异常类型可以通过配置SCTLR_EL1寄存器中的TCF([41:40])来选择。


TCF=0忽略Tag checks Faults。
TCF=1Tag checks Faults触发一个同步数据异常(data abort),同时会把造成异常的地址写入到FAR_ELx寄存器中。
TCF=2Tag checks Faults触发一个异步异常,会更新TFSR_EL1中的TF1值 


当发生tag check fault时,如果虚拟地址bit[55]为0,则会把TF0置1。如果bit[55]为1,则会把TF1置1。在内核态产生的异常自然会把TF1置1。
TCF=3当发生tag check fault时,如果是读指令造成的,则触发一个同步异常。如果是写指令造成的,则触发一个异步异常。

下面是三种异常模式的对比

Mode
Tag check runs
Speed
Detection
Sync
During instruction execution
Slower
Precise
Async
Asynchronously
Faster
Imprecise
Asymm
Sync for reads,async for writes



目前内核实现了sync模式async模式asymm模式sync检测对问题的定位更高效,因为它可以精确识别导致异常的指令和地址。但是sync模式检测的overhead比较高,这种性能影响在开发环境中可能是可以被接受的,但对于部署来说太高了。async检测的overhead较低,这意味着即使在生产系统上也可以使用。尽管async检测触发异常的report信息不太精确,但它可以提供一些额外信息用于分析,缩小了定位问题的范围。

2.4.1 sync触发流程

当在同步模式发生tag mismatch时,会触发一个Synchronous Tag Check Fault异常,同时会把ESR_EL1寄存器的DFSC设置为0x11。然后在同步异常里输出异常信息。
  



代码流程如下:

do_mem_abort->do_tag_check_fault /* 1 */->do_bad_area->__do_kernel_fault->do_tag_recovery /* 2 */->report_tag_fault->sysreg_clear_set


  • 1、 产生异常后,读取ESR_EL1寄存器的值,若该值为0x11,判定是一个Synchronous Tag Check Fault异常,然后执行do_tag_check_fault函数。


  • 2、 Report同步异常相关信息,同时关闭tag检测功能。



2.4.2 async触发流程

当在异步模式发生tag mismatch时,会异步更新TFSR_EL1寄存器,并把TF1位置1。为了检测是否发生了异步异常,我们需要在合适的时间点检查TFSR_EL1寄存器的TF1位是否置位?如果被置位,则说明发生了异步异常。

Linux内核主要会在以下时间点检测是否发生了tag check fault

  • 1. Context switching


  • 2. Return to user/EL0 (Not required in entry from EL0 since the kernel did not run)


  • 3. Kernel entry from EL1


  • 4. Kernel exit to EL1



我们以第一点为例,在上下文切换的时候检测是否发生了tag check fault
代码具体调用流程如下:

__schedule ->context_switch->__switch_to->mte_thread_switch->mte_check_tfsr_el1


重点关注mte_check_tfsr_el1函数实现。

void mte_check_tfsr_el1(void){ u64 tfsr_el1 = read_sysreg_s(SYS_TFSR_EL1); if (unlikely(tfsr_el1 & SYS_TFSR_EL1_TF1)) { write_sysreg_s(0, SYS_TFSR_EL1); kasan_report_async(); } }

从代码中可以看到,在进程上下文切换的时候会检测TFSR_EL1寄存器的TF1位,如果置位,先失能tag check功能,然后再调用kasan_report_async输出异步异常信息。其它三点类似不再分析。

2.5 小结

发生tag mismatch后,不管是同步异常还是异步异常只报告第一次触发的异常,之后MTE tags检查功能被禁用。通过本小结的描述,我们应该可以回答以下几个问题?

  • 1、pointer tags是如何分配和储存的?


  • 2、memory tags是如何分配和存储的?


  • 3、有多少不同的tag


  • 4、何时对tag进行检测的?


  • 5、发生tag mismatch会发生什么?



基于MTE的特性,我们可以将其应用于use-after-freeout-of-bounds等问题的定位。目前内核实现了对page_allocslab的支持,暂不支持vmalloc, stack and globals的检测。下面我们重点分析下page_allocslab在内核中的实现原理。

3.MTE在kernel中的使用


Hardware Tag-Based KASAN 是基于 MTE 实现的,MTE 的特性上面已经描述了,下面我们重点分析下 page_alloc kmalloc 的申请和释放的 memory tagspointer tags 的变化。

3.1 Buddy allocator 申请和释放

首先看一下伙伴系统的释放流程中 memory tags 的变化。


3.1.1 free page(s)


  • 1、假设我们释放 4 个 pages 到伙伴系统中。


  • 2、在释放过程中会调用 kasan_free_pages 函数填充 memory tags 为 0xe,表示此内存块已经被释放,不能直接访问,需要先申请后使用。


  • 3、填充 memory tags 的大小为 512 bytes。



3.1.2 allocate page(s)


  • 1、假设我们从伙伴系统申请 4 个 pages。系统首先从 order=2 的链表中摘下一块连续的内存块。


  • 2、在获取内存块的过程中会调用 kasan_unpoison_pages 函数填充 pointer tagsmemory tags。在填充 tag 之前,我们首先会通过 kasan_random_tag函数 irg 指令获取一个随机的 tag(假设生成的 tag 为 0x2),然后或上 0xf0,最后再通过 page_kasan_tag_set 函数把 pointer tags 设置到 struct page flags 字段中,如下函数:




static inline void page_kasan_tag_set(struct page *page, u8 tag){ unsigned long old_flags, flags; if (!kasan_enabled()) return; tag ^= 0xff; old_flags = READ_ONCE(page->flags); do { flags = old_flags; flags &= ~(KASAN_TAG_MASK << KASAN_TAG_PGSHIFT); flags |= (tag & KASAN_TAG_MASK) << KASAN_TAG_PGSHIFT; } while (unlikely(!try_cmpxchg(&page->flags, &old_flags, flags)));}


从上述代码中我们可以看到设置到page->flagstagtag^0xff后的值为0x0d,即page->flags = 0x0d << KASAN_TAG_PGSHIFT

tag的异或值存放在pageflags中,那我们如何使用呢?首先我们需要把物理page转换为虚拟地址,然后再去访问对应的虚拟地址。我们找到线性映射的page_to_virt宏,通过宏我们可以看到,在拿虚拟地址之前会通过__tag_set函数从flags中获取异或前的tag(0xf2),并填充到虚拟地址的高位。这样我们就可以正常访问对应的内存区域了。

#define page_to_virt(x)({\__typeof__(x) __page = x;\u64 __idx = ((u64)__page - VMEMMAP_START) / sizeof(struct page);\u64 __addr = PAGE_OFFSET + (__idx * PAGE_SIZE);\(void *)__tag_set((const void *)__addr, page_kasan_tag(__page));\})


  • 3、通过kasan_unpoison函数填充memory tags为0x2。

  • 4、填充 memory tags 的大小为 512 bytes。



3.2 kmalloc 申请和释放

slubtag 填充和 kmalloc 类似,下面以 kmalloc 的申请和释放流程为例进行分析[注 SLUB DEBUG is off]。首先看下 kmalloc 申请过程中的 pointer tagsmemory tags 的填充过程。

3.2.1 kmalloc

slub 系统刚刚创建出来,第一次申请内存,此时 kmem_cache_cpukmem_cache_node 中没有任何可用的 slubobject 对象,因此只能向伙伴系统申请空闲的内存页。同时会通过 kasan_poison_slab 函数把获取到的内存页的memory tags 填充为 0xe,具体见 allocate_slab 函数。



  • 1、假设通过 kmalloc 申请 35 bytes,对于 ARM64 而言会匹配到 kmalloc-128 的kmem_cache,因此实际分配的 object 大小是 128 bytes。至于为什么不是匹配到 kmalloc-64,可以从__kmalloc_index 函数中找到答案。


  • 2、通过 kasan_slab_alloc 函数填充 pointer tagsmemory tags。首先会通过 kasan_random_tag 函数(irg 指令)生成一个随机的 tag(say,0xc),或上0xf0 后的 tag 是 0xfc。其次再通过 set_tag(object, tag)函数把 tag 设置到pointer 中,最后通过 kasan_unpoison 函数把 0xc 设置到 memory tags 中。


  • 3、kmalloc(35)只申请了 35 个字节,由于 mte 的特性,会把前 48 个字节的 memory tags 标记为 0xc,表示能够正常访问。当使用完成之后,会调用 kfree 进行释放,释放之后的 memory tags 情况又是怎样的呢?往下看。




3.2.2 kfree


  • 1、根据释放地址 p,找到对应的 object,然后释放 object


  • 2、在释放 object 的过程中调用 kasan_slab_free 函数填充 memory tags 为 0xe,表示释放的内存,不能再访问。


  • 3、把释放的 object 的前 48 bytes 对应 memory tags 填充为 0xe。


3.2.3 Preventing memory corruptions

了解了 kmalloc 申请和释放过程中的 pointer tagsmemory tags 的变化后,下面我们通过图示的方式来说明下 kmalloc 申请和释放过程中,内存访问可能产生的各种情况。

allocating memory


  • 1、kmalloc(35)会从kmalloc-128 slub描述符中获取object


  • 2、通过IRG指令生成一个random tag (say,0x2)。 


  • 3、用0x2标记pointer tags and memory tags,其中memory tags以16 bytes向上取整,即对应[0,48)。


  • 4、用 KASAN_TAG_INVALID(0xe)标记剩余内存 80 bytes[48,128),表示不能使用的内存。


  • 5、不同颜色的内存块对应的 memory tags 不同。


In-bounds


上图表示,我们访问的指针在 kmalloc 申请的地址范围内,属于 In-bounds。由于 pointer tags == memory tags,因此能正常访问 p[20]的数据。 

Preventing out-of-bounds


  • A:p 申请的内存大小为 35 bytes,访问 p[70]属于越界访问。由于 pointer tags != memory tags,会触发一个异常(out-of-bounds),表示非法访问。


  • B:如果 p[136]对应的 memory tags 和 p 对应的内存块的 memory tags 不一样,则同样会触发一个异常(out-of-bounds)。


Not Preventing out-of-bounds


  • A:kmaollc(35)申请 35 bytes,因为 memory tags 以 16 bytes 为大小向上取整填充,所以[32,48)这个范围内的 memory tags pointer tags 一样。所以如果我们访问 p[36],MTE 是无法检测出来的(out-of-bounds),因为此时的 pointer tags == memory tags


  • B:对于 p[136]属于越界访问,填充的 memory tags 值有可能和 p 对应的内存块的 memory tags 一样,由于 pointer tags == memory tags,此时就无法检测出来(out-of-bounds)。


prevnting use-after-free


如果申请的内存块使用完后释放,对应的内存块的 memory tags 会用 0xe 进 行填充。如果再次访问释放的内存块,如 p[8]这个位置的数据,因为 pointer tags !=memory tags(0xe),则会触发一个异常(use-after-free)。 

preventing use-after-realloc


如果通过 kmalloc(35)函数申请的内存块在使用完后释放。然后再通过kmalloc(60)重新申请,假设拿到同一个 object。如果 kmalloc(60)申请的内存块填充的 memory tags kmaolloc(35)的不一样,此时再访问 p[8], pointer tags != memory tags,则会触发一个异常(use-after-realloc)。 

Not preventing use-after-realloc


和上面 preventing use-after-realloc 类似,如果 kmalloc(60)申请的内存块填充的 memory tags kmaolloc(35)的一样,此时访问 p[8],pointer tags== memory tags,则无法检测出来(use-after-realloc)。简单总结一下, Hardware Tag-Based KASAN 能检测出来 slab page_alloc相关问题,但并不是所有的 slab page_alloc 问题都能被检测出来,存在两个连续内存分配相同的 memory tags,也就会出现 1/16 概率无法被检测出来的情况。如果能保证相邻的 memory tags 不同,tag mismatch 的概率会大大降低。

4.使用方法


4.1 内核配置宏

需要添加以下内核配置
CONFIG_KASAN=y
CONFIG_KASAN_HW_TAGS=y
如果启用 CONFIG_KASAN_HW_TAGS 宏,则会默认启用ARM64的TBI特性。

4.2 启动参数

启动参数
内容
kasan=on/off
是否开启kasan,默认on。
kasan.mode=sync/async/asymm
选择不同的模式(同步/异步/混合),默认sync模式。
kasan.stacktrace=on/off
是否使能alloc and free stack traces,默认on。
kasan.fault=report/panic
是report还是panic,默认report。


5.案例分析


从其它地方找了两份log信息,一份是sync模式下触发的异常信息,另一份是async模式下触发的异常信息,首先看下sync模式。

5.1 Report: mode=sync, stacktrace=on
==================================================================
BUG: KASAN: invalid-access in kmalloc_oob_right+0x1ac/0x23c lib/test_kasan.c:144
Read at addr f2ff0000039ca880 by task kunit_try_catch/102
Pointer tag: [f2], memory tag: [fe]
CPU: 0 PID: 102 Comm: kunit_try_catch Tainted: G B 5.14.0-rc5 #389
Hardware name: linux,dummy-virt (DT)
Call trace:
...
el1h_64_sync_handler+0x50/0x80 arch/arm64/kernel/entry-common.c:318
el1h_64_sync+0x78/0x7c arch/arm64/kernel/entry.S:569
kmalloc_oob_right+0x1ac/0x23c lib/test_kasan.c:144
...
Allocated by task 102:
...
kasan_kmalloc include/linux/kasan.h:264
kmem_cache_alloc_trace include/linux/slab.h:489
kmalloc include/linux/slab.h:591
kmalloc_oob_right+0x60/0x23c lib/test_kasan.c:127
...
Freed by task 0:
(stack is not available)
The buggy address belongs to the object at ffff0000039ca800
which belongs to the cache kmalloc-128 of size 128
The buggy address is located 0 bytes to the right of
128-byte region [ffff0000039ca800, ffff0000039ca880)
The buggy address belongs to the page:
page:(____ptrval____) refcount:1 mapcount:0
mapping:0000000000000000 index:0xf6ff0000039ca600 pfn:0x439ca
flags: 0xffff00000000200(slab|node=0|zone=0|lastcpupid=0xffff|kasantag=0x0)
raw: 0ffff00000000200 0000000000000000 dead000000000122 f5ff000002401200
raw: f6ff0000039ca600 000000008010000f 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff0000039ca600: fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe
ffff0000039ca700: f6 f6 f6 f6 f6 f6 f6 fe fe fe fe fe fe fe fe fe
>ffff0000039ca800: f2 f2 f2 f2 f2 f2 f2 fe fe fe fe fe fe fe fe fe
^
ffff0000039ca900: fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe
ffff0000039caa00: fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe
==================================================================

从上面的log我们能获取到以下信息:

  • 1、触发问题的位置是kmalloc_oob_right+0x1ac/0x23c。



  • 2、CPU0,PID为102的kunit_try_catch进程读取0xf2ff0000039ca880地址数据造成的tag mismatch,其中pointer tag:0xf2, memory tag:0xfe。同时能获取到造成异常时的call trace信息。



  • 3、触发异常的内存块的申请和释放的call trace信息及对应的进程pid号。



  • 4、从kmalloc-128的kmem_cache中分配的object,kmalloc起始地址是0x ffff0000039ca800,申请的内存大小为(96,112]字节之间,触发异常的地址位于object起始地址偏移128 bytes的位置,属于out-of-bounds问题。



从上面的分析可以看出sync模式下的report信息非常丰富,我们通过log信息可以快速有效的定位到问题所在。下面我们看一下在async模式下,能获取到哪些信息?

5.2 Report: mode=async

==================================================================
BUG: KASAN: invalid-access
Asynchronous mode enabled: no access details available
CPU: 1 PID: 102 Comm: kunit_try_catch Not tainted 5.14.0-rc5 #389
Hardware name: linux,dummy-virt (DT)
Call trace:
...
kasan_report_async+0xf0/0x170 mm/kasan/report.c:378
mte_check_tfsr_el1+0x40/0x44 arch/arm64/kernel/mte.c:191
mte_check_tfsr_entry arch/arm64/include/asm/mte.h:108
enter_from_kernel_mode+0x24/0x48 arch/arm64/kernel/entry-common.c:49
enter_el1_irq_or_nmi+0x10/0x1c arch/arm64/kernel/entry-common.c:113
el1_interrupt+0x28/0xa0 arch/arm64/kernel/entry-common.c:350
el1h_64_irq_handler+0x18/0x24 arch/arm64/kernel/entry-common.c:367
el1h_64_irq+0x78/0x7c arch/arm64/kernel/entry.S:570
kmalloc_type include/linux/slab.h:348
...
====================================================================

log信息中我们可以知道,异常发生在CPU1上,pid为102的kunit_try_catch进程中,同时也能获取到report异常时的call trace信息。

虽然async模式report信息相比sync模式少很多,但是也提供了一些有效的信息,可以缩小排查问题的范围。同时由于其性能开销小,可以部署在生产环境中。



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存