【LLVM】Dynamic Alloca及Prologue/Epilogue Insertion & Frame Finalization Pass

最近写代码调Bug时闹了乌龙,一开始以为是ASan的Bug,真正理解后才发现是自己Pass的Bug。

大致问题是这样的:我的第①个前端Pass插入了一个If-Else分支,导致分支后面的Static Alloca变成了Dynamic Alloca并被ASan前端Pass(②)拿去处理,但是在prologepilog后端Pass(④,用于敲定StackLayout,下面会专门讲)处理之前,这个If-Else分支被Combine Redudent Instruction前端(③)Pass优化成IR Select指令,使得已经被ASan处理过的Dynamic Alloca又被还原成了Static Alloca。另一方面,LLVM Commit 98b18599新增了DynamicAllocaLayoutDynamicAllocaLayout本身是一个栈变量,位于Dynamic Alloca区域的底部,内部存了Dynamic Alloca区域的顶部的地址,方便ASan一口气清理Dynamic Alloca区域所对应的ShadowByte。那么问题就出现了,由于前述的Dynamic Alloca被③还原成了Static Alloca,使得DynamicAllocaLayout后面的若干Dynamic Alloca都还原成了Static Alloca,但是此时ASan②早已处理完了它所知的Static Alloca,这种突然从Dynamic Alloca转成Static Alloca的就未被处理,导致对应的Shadow就没有被清理了;另一方面,我们也没法通过DynamicAllocaLayout来清理,因为DynamicAllocaLayout已经没有办法再对已经转成Static Alloca的伪Dynamic Alloca对应的Shadow进行处理了。

出现这个问题的原因主要是我对Dynamic Alloca不了解。

Static Alloca VS Dynamic Alloca

Static Alloca是指在函数Entry BasicBlock中出现的且大小固定的栈变量,在Binary层面表现的特征就是函数头部RSP寄存器会减一个大的值用来一口气分配一些栈空间供这些Static Alloca的栈变量使用。

Dynamic Alloca就不同了,它要么是大小不固定的,要么就是不在Entry BasicBlock,也就是说需要代码执行到函数中部才调整RSP,用来根据所需大小分配单个空间仅供当前Dynamic Alloca使用。【LLVM】Dynamic Alloca及Prologue/Epilogue Insertion & Frame Finalization Pass_第1张图片
如果编译时开启了Stack Protector,那么Prologue/Epilogue Insertion & Frame Finalization(prologepilog) Pass内对Static Alloca的布局进一步进行编排。

Prologue/Epilogue Insertion & Frame Finalization(prologepilog) Pass

这是一个后端Pass,对Machine IR进行加工。它的作用之一是敲定函数内的栈帧布局(函数传参、StackGuardSlot、局部变量等的布局)。
(这里推荐编译选项--print-after-all可以帮助我们看到各个阶段的Pass,包括前端的和后端的)

这个Pass首先会为传参分配好栈,位置最贴近栈帧底部;之后处理局部变量。

如果编译时开启了StackProtector选项,那么就将其放在贴近传参变量的位置(顶上,栈的上面是靠向低地址的方向)之后找到三种栈对象:LargeArray、SmallArray、AddrOf,考虑到LargeArray最可能出现溢出的情况,SmallArray其次,AddrOf最后,因此将LargeArray放在最靠近StackProtector的位置,SmallArray其次,AddrOf最后。下面是代码里的注释说明。最后会针对剩下的SSPLK_None,根据Hook函数里设计的需求排序并分配栈空间(O0编译时就不排序了)。

  /// Stack Smashing Protection (SSP) rules require that vulnerable stack
  /// allocations are located close the stack protector.
  enum SSPLayoutKind {
    SSPLK_None,       ///< Did not trigger a stack protector.  No effect on data
                      ///< layout.
    SSPLK_LargeArray, ///< Array or nested array >= SSP-buffer-size.  Closest
                      ///< to the stack protector.
    SSPLK_SmallArray, ///< Array or nested array < SSP-buffer-size. 2nd closest
                      ///< to the stack protector.
    SSPLK_AddrOf      ///< The address of this allocation is exposed and
                      ///< triggered protection.  3rd closest to the protector.
  };

你可能感兴趣的:(内存安全,asan)