Why should I know this?

LLVM tutor] implements CFI 본문

LLVM-STUDY

LLVM tutor] implements CFI

die4taoam 2021. 4. 12. 02:04

0. Control-flow integrity 란 무엇인가?

 

아래 글은 redhat blog에서 퍼온 글입니다.

 

"CFI, or Control-flow integrity, may refer to any mechanism which tries to ensure the execution flow is valid when calling or returning from functions during the software’s runtime. Some of the hardenings mentioned earlier in the introduction can be considered CFI protection. Most of them are implemented by both GCC and Clang, however Clang also has the more sophisticated ability to validate forward-edge function calls during runtime. This means the generated code restricts the control-flow only to valid execution traces, making arbitrary code execution much more difficult."

 

www.redhat.com/en/blog/fighting-exploits-control-flow-integrity-cfi-clang

 

Fighting exploits with Control-Flow Integrity (CFI) in Clang

In this post, we look at Control-Flow Integrity (CFI) protection implemented by the Clang compiler for x86_64 architecture.

www.redhat.com

CFI는 쉽게 말해서 호출하는 함수 Caller와 호출되는 함수 Callee 간의 Call-Back 흐름을 강력하게 연결하여 중간에 프로그래머의 실수나 공격자에 의해 의도된 방향으로 실행이 조작되지 않도록 하는 보안 기법입니다.

 

CFI는 2005년 microsoft 연구팀에서 발표했습니다. 해당 논문은 다음 링크에서 참고하시면 됩니다.

 

www.microsoft.com/en-us/research/publication/control-flow-integrity/

 

Control-Flow Integrity - Microsoft Research

Current software attacks often build on exploits that subvert machine-code execution. The enforcement of a basic safety property, Control-Flow Integrity (CFI), can prevent such attacks from arbitrarily controlling program behavior. CFI enforcement is simpl

www.microsoft.com

 

아래는 일부 발췌한 내용입니다.

 

이 논문이 발표되는 시점까지도 x86 CPU가 시장 절대 위치를 고수하고 있었으므로 MS 논문에서도 x86 기반에서 실험적으로 구현한 내용을 예로 제시하고 있습니다. 주목해야 하는 지점은 호출-복귀 구간에 어떤 명령이 추가됐는가? 입니다.

 

Caller 부

call eax  ; 로 function ptr 호출이 이뤄지기 전에

 

mov eax, [ebx+8]

cmp [eax+4], 12345678h

 

위처럼 호출하려는 함수에 대한 signature(여기선 12345678) 를 검사한 후에 실패할 경우

 

jne error_label

 

error_label로 뛰게 만듭니다.

 

Callee 부에서도 마찬가지로 리턴이 되기 전에도

 

mov ecx, [esp]

add esp, 14h

cmp [ecx+4] AABBCCDDh

jne error_label

 

위의 명령을 통해 복귀하려는 return address + 4에 AABBCCDD 가 박혀있는지 확인합니다.

이런 메커니즘을 통해 Control-flow 가 무결성을 확보할 수 있다고 말하는데요.

 

 

아무래도 초기 아이디어 제의 측면에서 나온 기법이기에 퍼포먼스 저하가 굉~장히 컸습니다.

 

 

개인적으로 이 기술에 굉장한 매력을 느꼈으며 2013년 USENIX에서 발표된 "Control Flow Integrity for COTS Binaries"논문을 보고 영감을 받아 zigzi라는 개인 프로젝트를 시작했습니다.

논문은 여기서, www.usenix.org/conference/usenixsecurity13/technical-sessions/presentation/Zhang

 

Control Flow Integrity for COTS Binaries | USENIX

 

www.usenix.org

논문에 대한 부가 정보를 분석한 내용은 여기서, 확인하실 수 있습니다...

https://die4taoam.tistory.com/93

 

Control Flow Integrity for COTS Binaries

Control Flow Integrity for COTS Binaries august 2013 USENIX best paper awarded. (https://www.usenix.org/node/174767) “Security thesis review는 보안 관련된 논문의 개인 review로 다수의견과 일치되지 않을 수도 있습니다.” 소개

die4taoam.tistory.com

Zigzi는 COTS binary = 상용 바이너리에 instrumentation을 하기 위해 만들었습니다.

아래처럼 CFI 중 일부를 구현하여 ROP를 mitigation하는 예제를 만들어봤습니다.

 

 

시연을 해보자면 대충 이런데, 설명이 없어서 뭔 내용인지 알 수 없으실 겁니다.

-. C로 취약점이 존재하는 서버를 만들어, Client에서 취약점을 exploit하여 계산기를 실행하고

-. 이후 zigzi를 통해 return 되는 address에 대한 verifying instruction을 추가한 서버를 실행해

-. return 주소가 오염되었을 경우 security handler를 통해 경고창을 보여주는 예제입니다.

 

www.youtube.com/watch?v=PvMBNOIPZs8&t=27s

 

 

소개의 마지막으로 CFI 는 GCC 및 LLVM 양대 컴파일러 모두 지원하는 기능입니다만, LLVM에서는 아직도 완전한 구현이 되어 있지 않고, 호출하는 방향으로의 무결성 검증만을 지원하고 있습니다. 또한 해당 기능은 Android에서 사용하는 kernel과, chromium 에는 기본 적용되고 있습니다.

 

 

 

1. LLVM의 CFI

 

긴 시간을 지나 CFI는 GCC, LLVM 와 같은 컴파일러에 기능으로 추가되었으며 아마도 퍼포먼스 측면에서도 상당한 성능 향상이 있었을 것 같습니다. LLVM에서 CFI를 활용해보고 싶으면 다음과 같은 옵션으로 컴파일해야 합니다.

 

clang -o cfi Ex-CFI.c -flto -fsanitize=cfi -fvisibility=default

 

다음은 예제용 Ex-CFI.c 소스코드 입니다.

#include <stdio.h>
#include <string.h>

void fnptr(void) {
}

int main(int argc, char **argv) {
    void (*func)(void) = fnptr;
    func();
}

위의 코드를 컴파일 한 뒤에 디스어셈블 해보면 다음 명령이 추가된 걸 확인할 수 있습니다.

   0x00000000004004d1 <+33>:    movabs $0x4004f0,%rcx
   0x00000000004004db <+43>:    cmp    %rcx,%rax
   0x00000000004004de <+46>:    je     0x4004e2 <main+50>
   0x00000000004004e0 <+48>:    ud2
   0x00000000004004e2 <+50>:    callq  *%rax

 

Dump of assembler code for function fnptr:

   0x00000000004004f0 <+0>:     jmpq   0x4004a0 <fnptr . cfi >

 

Dump of assembler code for function fnptr . cfi:
   0x00000000004004a0 <+0>:     push   %rbp
   0x00000000004004a1 <+1>:     mov    %rsp,%rbp
   0x00000000004004a4 <+4>:     pop    %rbp
   0x00000000004004a5 <+5>:     retq

 

fnptr 함수가 fnptr.cfi 로 변경된 것을 확인할 수 있고, jmp table이 추가되서 해당 table index가 아닐 경우 ud2 즉, trap 되어 버리게끔 명령이 추가되었습니다. 우리는 LLVM 이 친숙하니까 IR을 한번 보도록 하죠~

 

 

CFI 활성화 시 생성되는 IR

; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @fnptr() #0 !type !3 !type !4 {
  ret void
}

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main(i32 %0, i8** %1) #0 !type !5 !type !6 {
  %3 = alloca i32, align 4
  %4 = alloca i8**, align 8
  %5 = alloca void ()*, align 8
  store i32 %0, i32* %3, align 4
  store i8** %1, i8*** %4, align 8
  store void ()* @fnptr, void ()** %5, align 8
  %6 = load void ()*, void ()** %5, align 8
  %7 = bitcast void ()* %6 to i8*, !nosanitize !7
  %8 = call i1 @llvm.type.test(i8* %7, metadata !"_ZTSFvvE"), !nosanitize !7
  br i1 %8, label %10, label %9, !nosanitize !7

9:                                                ; preds = %2
  call void @llvm.trap() #3, !nosanitize !7
  unreachable, !nosanitize !7

10:                                               ; preds = %2
  call void %6()
  ret i32 0
}

 

CFI 비활성화시 생성되는 IR

 

; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @fnptr() #0 {
  ret void
}

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main(i32 %0, i8** %1) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i8**, align 8
  %5 = alloca void ()*, align 8
  store i32 %0, i32* %3, align 4
  store i8** %1, i8*** %4, align 8
  store void ()* @fnptr, void ()** %5, align 8
  %6 = load void ()*, void ()** %5, align 8
  call void %6()
  ret i32 0
}

 

 

달라지는 부분은 호출되는 함수에 type !3 이 추가되었다는 점.

그리고 call 전에 이런 IR이 추가된 점입니다.

 

  %7 = bitcast void ()* %6 to i8*, !nosanitize !7
  %8 = call i1 @llvm.type.test(i8* %7, metadata !"_ZTSFvvE"), !nosanitize !7
  br i1 %8, label %10, label %9, !nosanitize !7

9:                                                ; preds = %2
  call void @llvm.trap() #3, !nosanitize !7
  unreachable, !nosanitize !7

fnptr을 로드해 i8 * 변환 후 llvm.type.test 를 호출하고 그 결과 디스어셈블한 코드에서 봤듯 같지 않을 경우 trap을 호출시키는 IR이 추가되었습니다. 이 파트가 LLVM 에서 CFI 가 구현된 형태입니다.

 

 

CFI 활성화 시

172.202 us :     ├▶(1) clang::SanitizerSet::has
           :     │
  7.301 us :     ├─(1) clang::CodeGen::CodeGenFunction::SanitizerScope::SanitizerScope
           :     │
 22.200 us :     ├▶(1) clang::CodeGen::CodeGenFunction::EmitSanitizerStatReport
           :     │
 14.600 us :     ├─(2) clang::CodeGen::CodeGenModule::getCodeGenOpts
           :     │
548.409 us :     ├▶(2) clang::QualType::QualType
           :     │
902.114 us :     ├▶(1) clang::CodeGen::CodeGenModule::CreateMetadataIdentifierForType
           :     │
 22.201 us :     ├▶(1) clang::CodeGen::CodeGenFunction::getLLVMContext
           :     │
  4.202 ms :     ├▶(1) llvm::MetadataAsValue::get
           :     │
 42.800 us :     ├▶(1) clang::CodeGen::CGCallee::getFunctionPointer
           :     │
349.904 us :     ├▶(2) llvm::Twine::Twine
           :     │
 17.576 ms :     ├▶(1) llvm::IRBuilder::CreateBitCast
           :     │
346.904 us :     ├▶(5) llvm::ArrayRef::ArrayRef
           :     │
 43.207 ms :     ├▶(1) clang::CodeGen::CodeGenModule::getIntrinsic
           :     │
 13.042 ms :     ├▶(1) llvm::IRBuilder::CreateCall
           :     │
  5.110 ms :     ├▶(1) clang::CodeGen::CodeGenModule::CreateCrossDsoCfiTypeId
           :     │
  2.966 ms :     ├▶(1) llvm::ConstantInt::get
           :     │
  2.385 ms :     ├▶(1) clang::CallExpr::getBeginLoc
           :     │
 50.709 ms :     ├▶(1) clang::CodeGen::CodeGenFunction::EmitCheckSourceLocation
           :     │
249.568 ms :     ├▶(1) clang::CodeGen::CodeGenFunction::EmitCheckTypeDescriptor
           :     │
  1.258 ms :     ├▶(1) llvm::UndefValue::get
           :     │
 67.801 us :     ├▶(1) std::make_pair
           :     │
130.394 ms :     ├▶(1) clang::CodeGen::CodeGenFunction::EmitCheck
           :     │
  7.400 us :     ├─(1) clang::CodeGen::CodeGenFunction::SanitizerScope::~SanitizerScope
           :     │
273.904 us :     ├▶(1) clang::CodeGen::CallArgList::CallArgList
           :     │
457.208 us :     ├▶(2) llvm::dyn_cast
           :     │
  4.553 ms :     ├▶(1) clang::CallExpr::getDirectCallee

 

CFI 비 활성화 시

167.302 us :  ├▶(1) clang::SanitizerSet::has
           :  │
269.104 us :  ├▶(1) clang::CodeGen::CallArgList::CallArgList
           :  │
  4.313 ms :  ├▶(1) clang::CallExpr::getDirectCallee
           :  │
  7.201 us :  ├─(1) clang::CodeGen::CodeGenFunction::AbstractCallee::AbstractCallee
           :  │
313.805 us :  ├▶(1) clang::CallExpr::arguments
           :  │
 25.005 ms :  ├▶(1) clang::CodeGen::CodeGenFunction::EmitCallArgs
           :  │
  7.201 us :  ├─(1) clang::CodeGen::CodeGenModule::getTypes
           :  │
 17.569 ms :  ├▶(1) clang::CodeGen::CodeGenTypes::arrangeFreeFunctionCall
           :  │
  2.200 ms :  ├▶(1) clang::Expr::getExprLoc
           :  │
 51.903 ms :  ├▶(1) clang::CodeGen::CodeGenFunction::EmitCall
           :  │
402.007 us :  └▶(1) clang::CodeGen::CallArgList::~CallArgList

UFTRACE로 trace를 해봅니다. 보시면 위와 아래 케이스에서

(1) clang::SanitizerSet::has

(1) clang::CodeGen::CallArgList::CallArgList

이 두 함수 사이에 상당히 많은 차이가 눈에 띄입니다.

 

 

CFI가 활성화 되었을 때 호출되는함수를 역으로 추적해보면, 

  "llvm.type.checked.load",
  "llvm.type.test",
  "llvm.uadd.sat",
  "llvm.uadd.with.overflow",
  "llvm.udiv.fix",
  "llvm.umul.fix",
  "llvm.umul.fix.sat",
  "llvm.umul.with.overflow",
  "llvm.usub.sat",
  "llvm.usub.with.overflow",
  "llvm.va_copy",
  "llvm.va_end",
  "llvm.va_start",
  "llvm.var.annotation",
  "llvm.write_register",
  "llvm.xray.customevent",
  "llvm.xray.typedevent",
  "llvm.aarch64.addg",
  "llvm.aarch64.clrex",
  "llvm.aarch64.cls",
  "llvm.aarch64.cls64",
  "llvm.aarch64.crc32b",
  "llvm.aarch64.crc32cb",
  "llvm.aarch64.crc32ch",
  "llvm.aarch64.crc32cw",
  "llvm.aarch64.crc32cx",
  "llvm.aarch64.crc32h",
  "llvm.aarch64.crc32w",
  "llvm.aarch64.crc32x",
  "llvm.aarch64.crypto.aesd",
  "llvm.aarch64.crypto.aese",
  "llvm.aarch64.crypto.aesimc",
 VISUAL  +0 ~0 -0 ᚠ ef32c61  build/include/llvm/IR/IntrinsicImpl.inc

compile 시에 생성되는 IntrinsicImpl.inc

 

(gdb) list getDeclaration
file: "/home/m/llvm-project/llvm/include/llvm/IR/DebugInfoMetadata.h", line number: 1810, symbol: "llvm::DISubprogram::getDec
laration() const"
1805      }
1806      void replaceUnit(DICompileUnit *CU) { replaceOperandWith(5, CU); }
1807      DITemplateParameterArray getTemplateParams() const {
1808        return cast_or_null<MDTuple>(getRawTemplateParams());
1809      }
1810      DISubprogram *getDeclaration() const {
1811        return cast_or_null<DISubprogram>(getRawDeclaration());
1812      }
1813      DINodeArray getRetainedNodes() const {
1814        return cast_or_null<MDTuple>(getRawRetainedNodes());
file: "/home/m/llvm-project/llvm/lib/IR/Function.cpp", line number: 1112, symbol: "llvm::Intrinsic::getDeclaration(llvm::Modu
le*, unsigned int, llvm::ArrayRef<llvm::Type*>)"
1107    /// This defines the "Intrinsic::getAttributes(ID id)" method.
1108    #define GET_INTRINSIC_ATTRIBUTES
1109    #include "llvm/IR/IntrinsicImpl.inc"
1110    #undef GET_INTRINSIC_ATTRIBUTES
1111
1112    Function *Intrinsic::getDeclaration(Module *M, ID id, ArrayRef<Type*> Tys) {
1113      // There can never be multiple globals with the same name of different types,
1114      // because intrinsics must be a specific type.
1115      return cast<Function>(
1116          M->getOrInsertFunction(getName(id, Tys),
(gdb) info source
Current source file is /home/m/llvm-project/llvm/lib/IR/Function.cpp
Compilation directory is /home/m/llvm-project/build/lib/IR
Located in /home/m/llvm-project/llvm/lib/IR/Function.cpp

위처럼 import 되어 사용되네요.

 

(gdb) list getIntrinsic
4624
4625      SetCommonAttributes(GD, GIF);
4626    }
4627
4628    llvm::Function *CodeGenModule::getIntrinsic(unsigned IID,
4629                                                ArrayRef<llvm::Type*> Tys) {
4630      return llvm::Intrinsic::getDeclaration(&getModule(), (llvm::Intrinsic::ID)IID,
4631                                             Tys);
4632    }
4633
(gdb) list +
4634    static llvm::StringMapEntry<llvm::GlobalVariable *> &
4635    GetConstantCFStringEntry(llvm::StringMap<llvm::GlobalVariable *> &Map,
4636                             const StringLiteral *Literal, bool TargetIsLSB,
4637                             bool &IsUTF16, unsigned &StringLength) {
4638      StringRef String = Literal->getString();
4639      unsigned NumBytes = String.size();
4640
4641      // Check for simple case.
4642      if (!Literal->containsNonAsciiOrNull()) {
4643        StringLength = NumBytes;
(gdb) list -
4624
4625      SetCommonAttributes(GD, GIF);
4626    }
4627
4628    llvm::Function *CodeGenModule::getIntrinsic(unsigned IID,
4629                                                ArrayRef<llvm::Type*> Tys) {
4630      return llvm::Intrinsic::getDeclaration(&getModule(), (llvm::Intrinsic::ID)IID,
4631                                             Tys);
4632    }
4633
(gdb) info source
Current source file is /home/m/llvm-project/clang/lib/CodeGen/CodeGenModule.cpp
Compilation directory is /home/m/llvm-project/build/tools/clang/lib/CodeGen
Located in /home/m/llvm-project/clang/lib/CodeGen/CodeGenModule.cpp

Intrinsic 함수를 뽑는 방식은 위와 같습니다. enum 선언된 ID를 통해 declaration을 얻어오는 것이죠.

 

(gdb) ni
0x000000000d9151dc      4938          MD = CGM.CreateMetadataIdentifierForType(QualType(FnType, 0));
(gdb)

0x000000000d9151e1      4938          MD = CGM.CreateMetadataIdentifierForType(QualType(FnType, 0));
(gdb)
0x000000000d9151e8      4938          MD = CGM.CreateMetadataIdentifierForType(QualType(FnType, 0));
(gdb)
0x000000000d9151eb      4938          MD = CGM.CreateMetadataIdentifierForType(QualType(FnType, 0));
(gdb)
0x000000000d9151ee      4938          MD = CGM.CreateMetadataIdentifierForType(QualType(FnType, 0));
(gdb)
0x000000000d9151f3      4938          MD = CGM.CreateMetadataIdentifierForType(QualType(FnType, 0));
(gdb)
4940        llvm::Value *TypeId = llvm::MetadataAsValue::get(getLLVMContext(), MD);
(gdb)
0x000000000d915201      4940        llvm::Value *TypeId = llvm::MetadataAsValue::get(getLLVMContext(), MD);
(gdb)
0x000000000d915204      4940        llvm::Value *TypeId = llvm::MetadataAsValue::get(getLLVMContext(), MD);
(gdb)

0x000000000d915209      4940        llvm::Value *TypeId = llvm::MetadataAsValue::get(getLLVMContext(), MD);
(gdb)
0x000000000d91520c      4940        llvm::Value *TypeId = llvm::MetadataAsValue::get(getLLVMContext(), MD);
(gdb)
0x000000000d915213      4940        llvm::Value *TypeId = llvm::MetadataAsValue::get(getLLVMContext(), MD);
(gdb)
0x000000000d915216      4940        llvm::Value *TypeId = llvm::MetadataAsValue::get(getLLVMContext(), MD);
(gdb)
0x000000000d915219      4940        llvm::Value *TypeId = llvm::MetadataAsValue::get(getLLVMContext(), MD);
(gdb)
0x000000000d91521e      4940        llvm::Value *TypeId = llvm::MetadataAsValue::get(getLLVMContext(), MD);
(gdb)
4942        llvm::Value *CalleePtr = Callee.getFunctionPointer();
(gdb)
0x000000000d91522c      4942        llvm::Value *CalleePtr = Callee.getFunctionPointer();
(gdb)
0x000000000d91522f      4942        llvm::Value *CalleePtr = Callee.getFunctionPointer();
(gdb)
0x000000000d915234      4942        llvm::Value *CalleePtr = Callee.getFunctionPointer();
(gdb)
4943        llvm::Value *CastedCallee = Builder.CreateBitCast(CalleePtr, Int8PtrTy);
(gdb)

 

위 back trace는 다음 IR을 생성하는 부분입니다.

  %7 = bitcast void ()* %6 to i8*, !nosanitize !7
  %8 = call i1 @llvm.type.test(i8* %7, metadata !"_ZTSFvvE"), !nosanitize !7

여기서 "_ZTSFvvE"는 mangle 된 변수 이름입니다. 원래 이름은 fnptr이죠

m@Hanbum:~/mystudy$ c++filt _ZTSFvvE
typeinfo name for void ()

 

mangle된 이름을 얻어와

RValue CodeGenFunction::EmitCallExpr(const CallExpr *E,
                                     ReturnValueSlot ReturnValue) {
  // Builtins never have block type.
  if (E->getCallee()->getType()->isBlockPointerType())
    return EmitBlockCallExpr(E, ReturnValue);

  if (const auto *CE = dyn_cast<CXXMemberCallExpr>(E))
    return EmitCXXMemberCallExpr(CE, ReturnValue);

  if (const auto *CE = dyn_cast<CUDAKernelCallExpr>(E))
    return EmitCUDAKernelCallExpr(CE, ReturnValue);

  if (const auto *CE = dyn_cast<CXXOperatorCallExpr>(E))
    if (const CXXMethodDecl *MD =
          dyn_cast_or_null<CXXMethodDecl>(CE->getCalleeDecl()))
      return EmitCXXOperatorMemberCallExpr(CE, MD, ReturnValue);

  CGCallee callee = EmitCallee(E->getCallee());

  if (callee.isBuiltin()) {
    return EmitBuiltinExpr(callee.getBuiltinDecl(), callee.getBuiltinID(),
                           E, ReturnValue);
  }

  if (callee.isPseudoDestructor()) {
    return EmitCXXPseudoDestructorExpr(callee.getPseudoDestructorExpr());
  }

  return EmitCall(E->getCallee()->getType(), callee, E, ReturnValue);
}
(gdb) bt
#0  clang::CodeGen::CodeGenFunction::EmitCall (this=0x7ffffffe8280, CalleeType=..., OrigCallee=..., E=0x18ad7390,
    ReturnValue=..., Chain=0x0) at /home/m/llvm-project/clang/lib/CodeGen/CGExpr.cpp:4943
#1  0x000000000d913147 in clang::CodeGen::CodeGenFunction::EmitCallExpr (this=0x7ffffffe8280, E=0x18ad7390, ReturnValue=...)
    at /home/m/llvm-project/clang/lib/CodeGen/CGExpr.cpp:4613
#2  0x000000000d94a649 in (anonymous namespace)::ScalarExprEmitter::VisitCallExpr (this=0x7ffffffe7db0, E=0x18ad7390)
    at /home/m/llvm-project/clang/lib/CodeGen/CGExprScalar.cpp:583
#3  0x000000000d96218b in clang::StmtVisitorBase<std::add_pointer, (anonymous namespace)::ScalarExprEmitter, llvm::Value*>::V
isit (this=0x7ffffffe7db0, S=0x18ad7390) at /home/m/llvm-project/build/tools/clang/include/clang/AST/StmtNodes.inc:801
#4  0x000000000d94983f in (anonymous namespace)::ScalarExprEmitter::Visit (this=0x7ffffffe7db0, E=0x18ad7390)
    at /home/m/llvm-project/clang/lib/CodeGen/CGExprScalar.cpp:426
#5  0x000000000d95f66b in clang::CodeGen::CodeGenFunction::EmitScalarExpr (this=0x7ffffffe8280, E=0x18ad7390,
    IgnoreResultAssign=true) at /home/m/llvm-project/clang/lib/CodeGen/CGExprScalar.cpp:4548

 

Instrinsic::test 를 호출하는 구문을 추가합니다.

 

4925   const auto *FnType = cast<FunctionType>(PointeeType);
4926
4927   // If we are checking indirect calls and this call is indirect, check that the
4928   // function pointer is a member of the bit set for the function type.
4929   if (SanOpts.has(SanitizerKind::CFIICall) &&
4930       (!TargetDecl || !isa<FunctionDecl>(TargetDecl))) {
4931     SanitizerScope SanScope(this);
4932     EmitSanitizerStatReport(llvm::SanStat_CFI_ICall);
4933
4934     llvm::Metadata *MD;
4935     if (CGM.getCodeGenOpts().SanitizeCfiICallGeneralizePointers)
4936       MD = CGM.CreateMetadataIdentifierGeneralized(QualType(FnType, 0));
4937     else
4938       MD = CGM.CreateMetadataIdentifierForType(QualType(FnType, 0));
4939
4940     llvm::Value *TypeId = llvm::MetadataAsValue::get(getLLVMContext(), MD);
4941
4942     llvm::Value *CalleePtr = Callee.getFunctionPointer();
4943     llvm::Value *CastedCallee = Builder.CreateBitCast(CalleePtr, Int8PtrTy);
4944     llvm::Value *TypeTest = Builder.CreateCall(
4945         CGM.getIntrinsic(llvm::Intrinsic::type_test), {CastedCallee, TypeId});
4946

 

 

실제 내부 구현은 훨~~~~~씬~~~~~~~~~ 복잡합니다만,

아직 분석은 고래 더듬이 정도 밖에 못했으므로 여기서 종료하겠습니다.

 

아무튼 LLVM에서 구현한 CFI를 optimizing path에서 구현할 수 있으며

코드 및 주석으로 추가 설명 덧붙였습니다.

 

      if (isa<CallInst>(inst))
      {
        auto *call = dyn_cast<CallInst>(inst);
        if (!call->isIndirectCall()) // target은 오직 indirectCall 입니다!!
          continue;
        
        LLVMContext &context = inst->getFunction()->getContext();
        IRBuilder<> Builder(inst);
        Type *ty = Type::getInt8PtrTy(context);
        // i8 * bitcast 를 해줍니다! 
        Value *CastedCallee = Builder.CreateBitCast(call->getCalledOperand(), ty);
        // LLVM Intrinsic::type_test 함수를 생성합니다.
        Function *func = Intrinsic::getDeclaration(inst->getModule(), Intrinsic::type_test);
        
        // Callee를 찾는 함수 
        Value *callee = findCallee(call);
        SmallVector<Metadata *, 1> mds;
        IntegerType *I64Ty = Type::getInt64Ty(inst->getParent()->getContext());
        Metadata *size = ConstantAsMetadata::get(ConstantInt::get(I64Ty, 0));
        Metadata *name = MDString::get(context, callee->getName());
        mds.push_back(size);
        mds.push_back(name);
        MDNode *node = MDNode::get(context, mds);

		// fnptr 함수에 !type !3 를 추가하는 부분입니다.
        dyn_cast<Function>(callee)->setMetadata("type", node);
        func->setMetadata(callee->getName(), node);

        Metadata *MD = MDString::get(context, callee->getName());
        MDNode *N = MDNode::get(context, MD);
        func->setMetadata(callee->getName(), N);
        Value *TypeID = MetadataAsValue::get(context, MD);

		// type_test Call 명령을 추가합니다.
        Instruction *TypeTest = Builder.CreateCall(func, {CastedCallee, TypeID});
        BasicBlock *TypeTestBB = TypeTest->getParent();
        BasicBlock *ContBB = TypeTestBB->splitBasicBlock(TypeTest, "cont");
        BasicBlock *TrapBB = createBasicBlock(context, "trap");
        TrapBB->insertInto(inst->getFunction(), ContBB);

        // remove branch instruction which created by splitBasicBlock calling
        Instruction *temp = dyn_cast<Instruction>(CastedCallee);
        Instruction *remove = temp->getNextNode();
        ContBB->removePredecessor(remove->getParent());
        remove->eraseFromParent();

        // move @llvm.type.test to right position
        TypeTest->removeFromParent();
        TypeTest->insertAfter(cast<Instruction>(CastedCallee));

		// @llvm.type.test 호출 결과에 따라 Trap 하거나 call 을 합니다.
        Builder.SetInsertPoint(TypeTestBB);
        Instruction *CondBR = Builder.CreateCondBr(TypeTest, ContBB, TrapBB);
        Builder.SetInsertPoint(TrapBB);
        CallInst *TrapCall = Builder.CreateCall(Intrinsic::getDeclaration(inst->getModule(), Intrinsic::trap));
        TrapCall->setDoesNotReturn();
        TrapCall->setDoesNotThrow();
        Builder.CreateUnreachable();

        errs() << "DEBUGGGGG \n";
        BasicBlock *startHere = inst->getParent();
        iter = startHere->begin();
        E = startHere->end();
        while(1) {
          if (&*BB == startHere)
            break;
          ++BB;
        }
        

 

위의 명령을 실행하면 다음과 같은 IR이 생성됩니다.

 

; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @fnptr() #0 !type !2 {
entry:
  ret void
}

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  %func = alloca void ()*, align 8
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  store void ()* @fnptr, void ()** %func, align 8
  %0 = load void ()*, void ()** %func, align 8
  %1 = bitcast void ()* %0 to i8*
  %2 = call i1 @llvm.type.test(i8* %1, metadata !"fnptr")
  br i1 %2, label %cont, label %trap

trap:                                             ; preds = %entry
  call void @llvm.trap() #3
  unreachable

cont:                                             ; preds = %entry
  call void %0()
  ret i32 0
}

; Function Attrs: nounwind readnone willreturn
declare !fnptr !3 i1 @llvm.type.test(i8*, metadata) #1

; Function Attrs: cold noreturn nounwind
declare void @llvm.trap() #2

 

위의 예제는 github.com/ParkHanbum/mystudy 에 공개되어 있습니다.

특정 케이스에만 적용 가능한 예제로 만든 프로젝트이므로 유의바랍니다.

 

 

캄사합니다.

 

 

'LLVM-STUDY' 카테고리의 다른 글

LoopLatch  (0) 2023.02.26
LLVM Optimization study - LoopFlatten  (0) 2023.02.23
LLVM 기반 TOOL을 제작할 때 라이브러리 링킹하는 법  (0) 2022.09.27
LLVM] phi 간략 살펴보기  (0) 2022.09.17
LLVM compile option  (0) 2022.03.21
Comments