일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- OSR
- LLVM
- anti debugging
- uftrace
- 안티디버깅
- initial-exec
- 난독화
- pinpoint
- apm
- v8 optimizing
- Linux packer
- LLVM Obfuscator
- on stack replacement
- Injection
- v8 tracing
- tracerpid
- pthread
- tracing
- linux thread
- Linux custom packer
- on-stack replacement
- thread local storage
- linux debugging
- custom packer
- android inject
- Android
- Obfuscator
- so inject
- LLVM 난독화
- TLS
- Today
- Total
Why should I know this?
LLVM tutor] implements CFI 본문
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
CFI는 쉽게 말해서 호출하는 함수 Caller와 호출되는 함수 Callee 간의 Call-Back 흐름을 강력하게 연결하여 중간에 프로그래머의 실수나 공격자에 의해 의도된 방향으로 실행이 조작되지 않도록 하는 보안 기법입니다.
CFI는 2005년 microsoft 연구팀에서 발표했습니다. 해당 논문은 다음 링크에서 참고하시면 됩니다.
www.microsoft.com/en-us/research/publication/control-flow-integrity/
아래는 일부 발췌한 내용입니다.
이 논문이 발표되는 시점까지도 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
논문에 대한 부가 정보를 분석한 내용은 여기서, 확인하실 수 있습니다...
https://die4taoam.tistory.com/93
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 |