Post

KN 地址消毒

KN 地址消毒

One of the first Internet-spread computer worms was the Internet Worm in 1988, which exploited a buffer overrun. More than thirty years later, we are still seeing attacks that exploit this type of programming bug. – Detecting memory safety violations, Arm Developer Manual

背景信息

 ASANHWASANMemDebugGWP-ASan
全名Address SanitizerHardware-Assisted Address SanitizerMemory DebugGoogle-Wide Profiling
GWP-ASan Will Provide Allocation SANity
部分检测依赖插桩
开销cpu,内存 200%cpu 200% 内存 10~35%cpu,内存 10~20%cpu,内存 < 5%,可调
基本原理Shadow Memory,红区Shadow Memory,指针Tag page电网
     

鸿蒙san文档

KN故障模式:认为关键是谁申请的内存,谁拿着指针做非法访问

 C指针KN指针
C使用正常san1. KN栈内存没发现获取指针方式,给不到C
2. KN堆内存溢出到内存池外
3. KN堆内存溢出到内存池内
KN使用konan接llvm san插桩pass同C使用

异常类型

  • 空间
    • heap buffer under/overflow
    • stack buffer under/overflow
  • 时间
    • heap use after free
    • stack use after scope (return?)
    • double free
    • free nonallocated memory

鸿蒙官网异常类型

  • asan: heap-buffer-overflow stack-buffer-overflow stack-buffer-underflow heap-use-after-free stack-use-after-scope attempt-free-nonallocated-memory double-free
  • hwasan:stack-buffer-overflow/underflow stack-use-after-return heap-buffer-overflow heap-buffer-underflow heap-use-after-free double-free
  • gwpasan:double free use_after_free invalid free left invalid free right buffer underflow

堆栈分配方法

    • calloc/malloc/free
    • jemalloc
    • new delete
    • alloc

san 原理

Asan

  • 被投毒(poison)的内存叫红区(redzone),哪里有毒的信息记在影子内存(shadow memory)里
  • 1 shadow memory byte对应8个normal memory byte,分配的正常内存8 byte对齐,如malloc 13 bytes,05代表sm这个byte对应的应用8byte内存中前5byte是可以访问的,后3byte是不能访问的 alt text alt text

  • 插桩:分配栈内存时前后poison,读写内存前根据sm进行校验
  • 运行时:分配堆内存时前后poison,释放的堆内存整个poison在FIFO队列中等一段时间

  • 性能开销:sm操作
  • 内存开销:8 byte对齐导致的空洞,前后的红区,释放后的隔离区,申请的内存8 byte向上取整和的1/8用于sm

HWASAN

Detecting-memory-safety-violations

  • 利用arm硬件寻址时的Top Bit Ignore功能:128G = 2 ^ 7 * 2 ^ 10 (kb) * 2 ^ 10 (mb) * 2 ^ 10 (gb) = 2 ^ 37 bytes。37 bit就能表示128G的地址空间,64位系统上寻址时高位是用不到的
  • 拿到的地址是带tag的,使用地址时检查地址中的tag和被访问地址的tag是不是一致 alt text alt text
  • LLVM的hwasan实现tag是8位,也有shadow memory,1 sm byte对16应用byte。应用地址分配16 byte对齐
    • 分配
      • 分配了 n*16 + 1~15 bytes:sm中保存实际分配的byte数,应用内存最后一个byte保存tag
      • 分配了 n* 16 bytes:sm中保存tag
    • 检查
      • 如果shadow byte值 1 ~ 15,包含:
        • 指针tag == shadow byte:视为完整16字节粒度的正常tag,有效
        • 指针tag != shadow byte:视为短粒度tag,检查访问地址是否在16字节粒度的前N字节内(N为shadow byte值),且指针tag是否与应用内存16 byte中最后一个byte存的tag匹配
          • 如果都满足:短粒度访问有效
          • 否则:无效
      • 否则(shadow byte值 > 15或为0):
        • 如果指针tag与shadow byte匹配:有效
        • 如果指针tag与shadow byte不匹配:无效

GWP

GWP-ASan的实现通常被描述为电网。性能方面为了减小开销只随机sample部分内存分配进行防护,检查通过mmu硬件进行开销较小,分配和释放内存时会回栈有一些开销,总体性能开销~5%。内存方面开销来自分配guard页和空洞,开销是固定可调的。

alt text

  • 上下溢:如检查下溢时将数据分配到gwp内存池中向slot低地址方向对齐,这样指针-1的位置会访问到上一个不可读不可写不可执行的guard page,触发mmu异常信号
  • uaf:释放内存时将这段内存所在的整个Slot设为不可读不可写不可执行,持续一段时间,uaf访问时触发mmu异常信号

  • 一次申请超过1 page的内存不会防护

DevEco LLVM san编译参数

开启hvigor详细日志查看cmake参数

alt text

构建日志中搜 [cmake]

alt text

对比DevEco中是否勾选asan,cmake选项差一个 '-DOHOS_ENABLE_ASAN=ON'

alt text

对比 .cxx/default/default/debug/arm64-v8a/compile_commands.json 中具体cpp文件的编译命令,clang++多了几个选项

1
2
3
4
-shared-libasan
-fsanitize=address
-fno-omit-frame-pointer
-fsanitize-recover=address

alt text

比较加不加这四个选项的链接命令,编一个最简单的 int main() {}

1
2
3
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native/llvm/bin/clang++ --target=aarch64-linux-ohos test.cpp -v
# vs
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native/llvm/bin/clang++ --target=aarch64-linux-ohos test.cpp -v -shared-libasan -fsanitize=address -fno-omit-frame-pointer -fsanitize-recover=address

比较输出中的ld命令

alt text

KN 内存分配

分配过程

对象的地址可以赋给堆或者栈上的变量

alt text

alt text

alt text

alt text

alt text

alt text

alt text

内存布局

!!!本章节基本是大模型走读KN runtime代码总结,缺乏验证!!!

对象:

OffsetFieldSizeAlignmentBit-Level Details
0x00gc::GC::ObjectData8 bytes8 bytesGC Header
 next_ (atomic pointer)8 bytes8 bytes8 bytes for all 3 gc types
0x08KObject (ObjHeader)varies8 bytesKotlin Object Data
 typeInfoOrMeta_8 bytes8 bytesBits 0-63: Pointer to TypeInfo or MetaObjHeader
Bits 0-1: Tag bits (OBJECT_TAG_MASK)    
0x10+Object fieldsN bytesnatural⚠️ Fields use NATURAL alignment, NOT 8-byte!: Boolean: 1 byte (1-byte aligned),Byte: 1 byte (1-byte aligned),Short: 2 bytes (2-byte aligned),Int/Float: 4 bytes (4-byte aligned),Long/Double/Reference: 8 bytes (8-byte aligned)
Compiler reorders by size (large→small) to minimize padding    
END[Padding]0-7 bytesPad entire object to 8-byte boundary
1
2
3
4
5
class Example {
    val flag: Boolean    // 1 byte
    val count: Int       // 4 bytes  
    val ref: String?     // 8 bytes
}
OffsetFieldSizeExplanation
0x00ObjectData.next_8 bytesGC header
0x08typeInfoOrMeta_8 bytesObject header
0x10ref8 bytesLargest field first (8-byte aligned)
0x18count4 bytesMedium field (4-byte aligned, NOT 8!)
0x1Cflag1 byteOnly 1 byte used (1-byte aligned)
0x1D[padding]3 bytesPad to 8-byte boundary
    
Total32 bytes 8 + 24 where instanceSize_ = 24
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
┌─────────────────────────────────────────────┐
  HEAP (per-instance data)                   
├─────────────────────────────────────────────┤
  0x00: gc::GC::ObjectData                   
        - next_ (8 bytes)                    
├─────────────────────────────────────────────┤
  0x08: ObjHeader (KObject)                  
        - typeInfoOrMeta_ (8 bytes) ────┐    
├─────────────────────────────────────────│────┤
  0x10: [Object fields]                      
        - field1                             
        - field2                             
        - ...                                
└─────────────────────────────────────────│────┘
                                          
                    Points to             
                                         
┌─────────────────────────────────────────┴────┐
  STATIC MEMORY (per-class metadata)          
├──────────────────────────────────────────────┤
  TypeInfo (shared by all instances):         
    - typeInfo_ (self-reference)              
    - extendedInfo_                           
    - unused_                                 
    - instanceSize_        Size of fields    
    - superType_                              
    - objOffsets_          GC scan info      
    - objOffsetsCount_                        
    - implementedInterfaces_                  
    - flags_                                  
    - classId_                                
    - associatedObjects                       
    - processObjectInMark                     
    - instanceAlignment_                      
    - vtable_[]            Virtual methods   
└──────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
TypeInfo
┌─────────────────────────────────────┐
 gc::GC::ObjectData (8 bytes)        
├─────────────────────────────────────┤
 ObjHeader                            
   typeInfoOrMeta_ ────────┐         
                                     
├────────────────────────────┼─────────┤
 [Object fields...]                  
└────────────────────────────┼─────────┘
                             
                             
                    ┌────────────────┐
                     TypeInfo       
                       typeInfo_ ─┐   Self-reference
                                   
                       (ptr == ───┘   ptr->typeInfo_)
                       ...)         
                    └────────────────┘

Meta
┌─────────────────────────────────────┐
 gc::GC::ObjectData (8 bytes)        
├─────────────────────────────────────┤
 ObjHeader                            
   typeInfoOrMeta_ ─────┐            
                                    
├────────────────────────┼────────────┤
 [Object fields...]                 
└────────────────────────┼────────────┘
                         
                         
              ┌──────────────────────────┐
               ExtraObjectData          
                 typeInfo_ ──────────┐  
                 flags_                  ptr != ptr->typeInfo_
                 associatedObject_       (so it's detected as meta)
                 weakReference...      
              └───────────────────────┼──┘
                                      
                                      
                             ┌────────────────┐
                              TypeInfo       
                                typeInfo_ ─┐ 
                                            
                                (...)    ──┘ 
                             └────────────────┘

数组

OffsetFieldSizeAlignmentBit-Level Details
0x00gc::GC::ObjectData8 bytes8 bytesGC Header
 next_ (atomic pointer)8 bytes8 bytesSame as HeapObject
0x08KArray (ArrayHeader)16 bytes8 bytesArray Header (fixed size)
 typeInfoOrMeta_8 bytes8 bytesBits 0-63: Pointer to array TypeInfo
Bits 0-1: Tag bits    
0x10count_4 bytes4 bytesBits 0-31: Element count (uint32_t)
0x14objcFlags_ / padding4 bytes4 bytesBits 0-31: ObjC flags if KONAN_OBJC_INTEROP, Otherwise: padding to 8-byte struct alignment
0x18[Padding]0-7 bytesE bytesAlign to element size boundary
AlignUp(sizeof(ArrayHeader), elementSize)    
0x18+Array elementsE × countE bytes⚠️ Elements use ELEMENT SIZE alignment, NOT 8-byte!
Each element size E = -typeInfo->instanceSize_
Elements are tightly packed with no extra padding
END[Padding]0-7 bytesPad entire array to 8-byte boundary
1
val flags = BooleanArray(5)  // 5 booleans
OffsetFieldSizeExplanation
0x00ObjectData.next_8 bytesGC header
0x08typeInfoOrMeta_8 bytesArray header
0x10count_ = 54 bytes 
0x14padding4 bytes 
0x18element[0]1 byteOnly 1 byte per boolean!
0x19element[1]1 byteNo padding between elements
0x1Aelement[2]1 byteTightly packed
0x1Belement[3]1 byte 
0x1Celement[4]1 byte 
0x1D[padding]3 bytesFinal 8-byte alignment
Total32 bytes AlignUp(24 + 5, 8) = 32

page:

ConstantOHOS TargetStandard Target
kPageAlignment8 bytes8 bytes
Cell size8 bytes8 bytes
FixedBlockCell size8 bytes8 bytes
NextFitPage::SIZE128 KiB256 KiB
FixedBlockPage::SIZE128 KiB256 KiB
ExtraObjectPage::SIZE64 KiB64 KiB
MAX_BLOCK_SIZE32 cells (256 bytes)128 cells (1024 bytes)

SingleObjectPage

OffsetFieldSizeAlignmentDescription
0x00**AnyPage**16 bytes8 bytesBase class
0x00├─ next_8 bytes8 bytesstd::atomic<SingleObjectPage*> for page list
0x08└─ allocatedSizeTracker_8 bytes8 bytesAllocatedSizeTracker::Page (size_t field)
 └─ threadName_24 bytes8 bytesafter libc++ small string optimization a string is 24 bytes
0x10isAllocated_1 byte1 byteBoolean: 0 = available, 1 = occupied
0x11[padding]7 bytesPad to 8-byte boundary
0x18size_8 bytes8 bytesTotal page allocation size (size_t)
0x20dataAddress8 bytes8 bytesTencent Code: cached uintptr_t of data_ address
0x28alignas(8) struct0 bytes8 bytesAlignment directive only
0x28├─ data_[]N bytes8 bytesFlexible array - object begins here ↓
     
0x28Object Layout  Within data_[] array:
0x28├─ gc::GC::ObjectData8 bytes8 bytesGC header: std::atomic<ObjectData*> next_
0x30├─ ObjHeader8 bytes8 bytesTypeInfo* typeInfoOrMeta_ pointer
0x38├─ [Object fields…]variesnaturalField data (1/2/4/8-byte aligned)
???└─ [padding]0-7 bytesPad total object to 8-byte boundary
All Rights Reserved.