forked from OSchip/llvm-project
[LLD] Implement /guard:[no]longjmp
Summary: This protects calls to longjmp from transferring control to arbitrary program points. Instead, longjmp calls are limited to the set of registered setjmp return addresses. This also implements /guard:nolongjmp to allow users to link in object files that call setjmp that weren't compiled with /guard:cf. In this case, the linker will approximate the set of address taken functions, but it will leave longjmp unprotected. I used the following program to test, compiling it with different -guard flags: $ cl -c t.c -guard:cf $ lld-link t.obj -guard:cf #include <setjmp.h> #include <stdio.h> jmp_buf buf; void g() { printf("before longjmp\n"); fflush(stdout); longjmp(buf, 1); } void f() { if (setjmp(buf)) { printf("setjmp returned non-zero\n"); return; } g(); } int main() { f(); printf("hello world\n"); } In particular, the program aborts when the code is compiled *without* -guard:cf and linked with -guard:cf. That indicates that longjmps are protected. Reviewers: ruiu, inglorion, amccarth Subscribers: llvm-commits Differential Revision: https://reviews.llvm.org/D43217 llvm-svn: 325047
This commit is contained in:
parent
447ae4044d
commit
fd52096259
|
@ -72,6 +72,12 @@ enum class DebugType {
|
|||
Fixup = 0x4, /// Relocation Table
|
||||
};
|
||||
|
||||
enum class GuardCFLevel {
|
||||
Off,
|
||||
NoLongJmp, // Emit gfids but no longjmp tables
|
||||
Full, // Enable all protections.
|
||||
};
|
||||
|
||||
// Global configuration.
|
||||
struct Configuration {
|
||||
enum ManifestKind { SideBySide, Embed, No };
|
||||
|
@ -113,7 +119,7 @@ struct Configuration {
|
|||
bool SaveTemps = false;
|
||||
|
||||
// /guard:cf
|
||||
bool GuardCF;
|
||||
GuardCFLevel GuardCF = GuardCFLevel::Off;
|
||||
|
||||
// Used for SafeSEH.
|
||||
Symbol *SEHTable = nullptr;
|
||||
|
|
|
@ -128,13 +128,19 @@ void parseVersion(StringRef Arg, uint32_t *Major, uint32_t *Minor) {
|
|||
fatal("invalid number: " + S2);
|
||||
}
|
||||
|
||||
void parseGuard(StringRef Arg) {
|
||||
if (Arg.equals_lower("no"))
|
||||
Config->GuardCF = false;
|
||||
else if (Arg.equals_lower("cf"))
|
||||
Config->GuardCF = true;
|
||||
else
|
||||
fatal("invalid argument to /GUARD: " + Arg);
|
||||
void parseGuard(StringRef FullArg) {
|
||||
SmallVector<StringRef, 1> SplitArgs;
|
||||
FullArg.split(SplitArgs, ",");
|
||||
for (StringRef Arg : SplitArgs) {
|
||||
if (Arg.equals_lower("no"))
|
||||
Config->GuardCF = GuardCFLevel::Off;
|
||||
else if (Arg.equals_lower("nolongjmp"))
|
||||
Config->GuardCF = GuardCFLevel::NoLongJmp;
|
||||
else if (Arg.equals_lower("cf") || Arg.equals_lower("longjmp"))
|
||||
Config->GuardCF = GuardCFLevel::Full;
|
||||
else
|
||||
fatal("invalid argument to /GUARD: " + Arg);
|
||||
}
|
||||
}
|
||||
|
||||
// Parses a string in the form of "<subsystem>[,<integer>[.<integer>]]".
|
||||
|
|
|
@ -183,8 +183,10 @@ SectionChunk *ObjFile::readSection(uint32_t SectionNumber,
|
|||
// linked in the regular manner.
|
||||
if (C->isCodeView())
|
||||
DebugChunks.push_back(C);
|
||||
else if (Config->GuardCF && Name == ".gfids$y")
|
||||
else if (Config->GuardCF != GuardCFLevel::Off && Name == ".gfids$y")
|
||||
GuardFidChunks.push_back(C);
|
||||
else if (Config->GuardCF != GuardCFLevel::Off && Name == ".gljmp$y")
|
||||
GuardLJmpChunks.push_back(C);
|
||||
else if (Name == ".sxdata")
|
||||
SXDataChunks.push_back(C);
|
||||
else
|
||||
|
|
|
@ -112,6 +112,7 @@ public:
|
|||
ArrayRef<SectionChunk *> getDebugChunks() { return DebugChunks; }
|
||||
ArrayRef<SectionChunk *> getSXDataChunks() { return SXDataChunks; }
|
||||
ArrayRef<SectionChunk *> getGuardFidChunks() { return GuardFidChunks; }
|
||||
ArrayRef<SectionChunk *> getGuardLJmpChunks() { return GuardLJmpChunks; }
|
||||
ArrayRef<Symbol *> getSymbols() { return Symbols; }
|
||||
|
||||
// Returns a Symbol object for the SymbolIndex'th symbol in the
|
||||
|
@ -175,9 +176,10 @@ private:
|
|||
// 32-bit x86.
|
||||
std::vector<SectionChunk *> SXDataChunks;
|
||||
|
||||
// Chunks containing symbol table indices of address taken symbols. These are
|
||||
// not linked into the final binary when /guard:cf is set.
|
||||
// Chunks containing symbol table indices of address taken symbols and longjmp
|
||||
// targets. These are not linked into the final binary when /guard:cf is set.
|
||||
std::vector<SectionChunk *> GuardFidChunks;
|
||||
std::vector<SectionChunk *> GuardLJmpChunks;
|
||||
|
||||
// This vector contains the same chunks as Chunks, but they are
|
||||
// indexed such that you can get a SectionChunk by section index.
|
||||
|
|
|
@ -124,7 +124,8 @@ private:
|
|||
void openFile(StringRef OutputPath);
|
||||
template <typename PEHeaderTy> void writeHeader();
|
||||
void createSEHTable(OutputSection *RData);
|
||||
void createGFIDTable(OutputSection *RData);
|
||||
void createGuardCFTables(OutputSection *RData);
|
||||
void createGLJmpTable(OutputSection *RData);
|
||||
void markSymbolsForRVATable(ObjFile *File,
|
||||
ArrayRef<SectionChunk *> SymIdxChunks,
|
||||
SymbolRVASet &TableSymbols);
|
||||
|
@ -440,9 +441,9 @@ void Writer::createMiscChunks() {
|
|||
if (Config->Machine == I386)
|
||||
createSEHTable(RData);
|
||||
|
||||
// Create the guard function id table if requested.
|
||||
if (Config->GuardCF)
|
||||
createGFIDTable(RData);
|
||||
// Create /guard:cf tables if requested.
|
||||
if (Config->GuardCF != GuardCFLevel::Off)
|
||||
createGuardCFTables(RData);
|
||||
}
|
||||
|
||||
// Create .idata section for the DLL-imported symbol table.
|
||||
|
@ -734,7 +735,7 @@ template <typename PEHeaderTy> void Writer::writeHeader() {
|
|||
PE->DLLCharacteristics |= IMAGE_DLL_CHARACTERISTICS_NX_COMPAT;
|
||||
if (!Config->AllowIsolation)
|
||||
PE->DLLCharacteristics |= IMAGE_DLL_CHARACTERISTICS_NO_ISOLATION;
|
||||
if (Config->GuardCF)
|
||||
if (Config->GuardCF != GuardCFLevel::Off)
|
||||
PE->DLLCharacteristics |= IMAGE_DLL_CHARACTERISTICS_GUARD_CF;
|
||||
if (Config->Machine == I386 && !SEHTable &&
|
||||
!Symtab->findUnderscore("_load_config_used"))
|
||||
|
@ -896,17 +897,21 @@ static void markSymbolsWithRelocations(ObjFile *File,
|
|||
// Create the guard function id table. This is a table of RVAs of all
|
||||
// address-taken functions. It is sorted and uniqued, just like the safe SEH
|
||||
// table.
|
||||
void Writer::createGFIDTable(OutputSection *RData) {
|
||||
void Writer::createGuardCFTables(OutputSection *RData) {
|
||||
SymbolRVASet AddressTakenSyms;
|
||||
SymbolRVASet LongJmpTargets;
|
||||
for (ObjFile *File : ObjFile::Instances) {
|
||||
// If the object was compiled with /guard:cf, the address taken symbols are
|
||||
// in the .gfids$y sections. Otherwise, we approximate the set of address
|
||||
// taken symbols by checking which symbols were used by relocations in live
|
||||
// sections.
|
||||
if (File->hasGuardCF())
|
||||
// If the object was compiled with /guard:cf, the address taken symbols
|
||||
// are in .gfids$y sections, and the longjmp targets are in .gljmp$y
|
||||
// sections. If the object was not compiled with /guard:cf, we assume there
|
||||
// were no setjmp targets, and that all code symbols with relocations are
|
||||
// possibly address-taken.
|
||||
if (File->hasGuardCF()) {
|
||||
markSymbolsForRVATable(File, File->getGuardFidChunks(), AddressTakenSyms);
|
||||
else
|
||||
markSymbolsForRVATable(File, File->getGuardLJmpChunks(), LongJmpTargets);
|
||||
} else {
|
||||
markSymbolsWithRelocations(File, AddressTakenSyms);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the image entry as address-taken.
|
||||
|
@ -916,10 +921,17 @@ void Writer::createGFIDTable(OutputSection *RData) {
|
|||
maybeAddRVATable(RData, std::move(AddressTakenSyms), "__guard_fids_table",
|
||||
"__guard_fids_count");
|
||||
|
||||
// Add the longjmp target table unless the user told us not to.
|
||||
if (Config->GuardCF == GuardCFLevel::Full)
|
||||
maybeAddRVATable(RData, std::move(LongJmpTargets), "__guard_longjmp_table",
|
||||
"__guard_longjmp_count");
|
||||
|
||||
// Set __guard_flags, which will be used in the load config to indicate that
|
||||
// /guard:cf was enabled.
|
||||
uint32_t GuardFlags = uint32_t(coff_guard_flags::CFInstrumented) |
|
||||
uint32_t(coff_guard_flags::HasFidTable);
|
||||
if (Config->GuardCF == GuardCFLevel::Full)
|
||||
GuardFlags |= uint32_t(coff_guard_flags::HasLongJmpTable);
|
||||
Symbol *FlagSym = Symtab->findUnderscore("__guard_flags");
|
||||
cast<DefinedAbsolute>(FlagSym)->setVA(GuardFlags);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# RUN: llvm-mc -triple x86_64-windows-msvc %s -filetype=obj -o %t.obj
|
||||
# RUN: lld-link %t.obj -opt:noref -guard:cf -out:%t.exe -entry:main 2>&1 | FileCheck %s --check-prefix=ERRS
|
||||
# RUN: lld-link %t.obj -opt:noref -guard:nolongjmp -out:%t.exe -entry:main 2>&1 | FileCheck %s --check-prefix=ERRS
|
||||
# RUN: llvm-readobj -file-headers -coff-load-config %t.exe | FileCheck %s
|
||||
|
||||
# ERRS: warning: ignoring .gfids$y symbol table index section in object {{.*}}gfids-corrupt{{.*}}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# RUN: grep -B99999 [S]PLITMARKER %s | llvm-mc -triple x86_64-windows-msvc -filetype=obj -o %t1.obj
|
||||
# RUN: grep -A99999 [S]PLITMARKER %s | llvm-mc -triple x86_64-windows-msvc -filetype=obj -o %t2.obj
|
||||
# RUN: lld-link %t1.obj %t2.obj -guard:cf -out:%t.exe -entry:main -opt:noref
|
||||
# RUN: lld-link %t1.obj %t2.obj -guard:nolongjmp -out:%t.exe -entry:main -opt:noref
|
||||
# RUN: llvm-readobj -file-headers -coff-load-config %t.exe | FileCheck %s
|
||||
|
||||
# CHECK: ImageBase: 0x140000000
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# RUN: llvm-mc -triple x86_64-windows-msvc %s -filetype=obj -o %t.obj
|
||||
# RUN: lld-link %t.obj -guard:cf -out:%t.exe -opt:noref -entry:main
|
||||
# RUN: lld-link %t.obj -guard:nolongjmp -out:%t.exe -opt:noref -entry:main
|
||||
# RUN: llvm-readobj -file-headers -coff-load-config %t.exe | FileCheck %s --check-prefix=CHECK-NOGC
|
||||
# RUN: lld-link %t.obj -guard:cf -out:%t.exe -opt:ref -entry:main
|
||||
# RUN: lld-link %t.obj -guard:nolongjmp -out:%t.exe -opt:ref -entry:main
|
||||
# RUN: llvm-readobj -file-headers -coff-load-config %t.exe | FileCheck %s --check-prefix=CHECK-GC
|
||||
|
||||
# This assembly is meant to mimic what CL emits for this kind of C code when
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# RUN: llvm-mc -triple x86_64-windows-msvc %s -filetype=obj -o %t.obj
|
||||
# RUN: lld-link %t.obj -guard:cf -out:%t.exe -opt:icf -entry:main
|
||||
# RUN: lld-link %t.obj -guard:nolongjmp -out:%t.exe -opt:icf -entry:main
|
||||
# RUN: llvm-readobj -file-headers -coff-load-config %t.exe | FileCheck %s --check-prefix=CHECK
|
||||
|
||||
# This assembly is meant to mimic what CL emits for this kind of C code:
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
# RUN: llvm-mc -triple x86_64-windows-msvc %s -filetype=obj -o %t.obj
|
||||
# RUN: lld-link %t.obj -guard:cf -out:%t.exe -entry:main
|
||||
# RUN: llvm-readobj -file-headers -coff-load-config %t.exe | FileCheck %s
|
||||
|
||||
# CHECK: ImageBase: 0x140000000
|
||||
# CHECK: LoadConfig [
|
||||
# CHECK: SEHandlerTable: 0x0
|
||||
# CHECK: SEHandlerCount: 0
|
||||
# CHECK: GuardCFCheckFunction: 0x0
|
||||
# CHECK: GuardCFCheckDispatch: 0x0
|
||||
# CHECK: GuardCFFunctionTable: 0x14000{{.*}}
|
||||
# CHECK: GuardCFFunctionCount: 1
|
||||
# CHECK: GuardFlags: 0x10500
|
||||
# CHECK: GuardAddressTakenIatEntryTable: 0x0
|
||||
# CHECK: GuardAddressTakenIatEntryCount: 0
|
||||
# CHECK: GuardLongJumpTargetTable: 0x14000{{.*}}
|
||||
# CHECK: GuardLongJumpTargetCount: 1
|
||||
# CHECK: ]
|
||||
# CHECK: GuardLJmpTable [
|
||||
# CHECK-NEXT: 0x14000{{.*}}
|
||||
# CHECK-NEXT: ]
|
||||
|
||||
|
||||
# This assembly is reduced from C code like:
|
||||
# #include <setjmp.h>
|
||||
# jmp_buf buf;
|
||||
# void g() { longjmp(buf, 1); }
|
||||
# void f() {
|
||||
# if (setjmp(buf))
|
||||
# return;
|
||||
# g();
|
||||
# }
|
||||
# int main() { f(); }
|
||||
|
||||
# We need @feat.00 to have 0x800 to indicate /guard:cf.
|
||||
.def @feat.00;
|
||||
.scl 3;
|
||||
.type 0;
|
||||
.endef
|
||||
.globl @feat.00
|
||||
@feat.00 = 0x801
|
||||
.def f; .scl 2; .type 32; .endef
|
||||
.globl f
|
||||
f:
|
||||
pushq %rbp
|
||||
subq $32, %rsp
|
||||
leaq 32(%rsp), %rbp
|
||||
leaq buf(%rip), %rcx
|
||||
leaq -32(%rbp), %rdx
|
||||
callq _setjmp
|
||||
.Lljmp1:
|
||||
testl %eax, %eax
|
||||
je .LBB1_1
|
||||
addq $32, %rsp
|
||||
popq %rbp
|
||||
retq
|
||||
.LBB1_1: # %if.end
|
||||
leaq buf(%rip), %rcx
|
||||
movl $1, %edx
|
||||
callq longjmp
|
||||
ud2
|
||||
|
||||
# Record the longjmp target.
|
||||
.section .gljmp$y,"dr"
|
||||
.symidx .Lljmp1
|
||||
.text
|
||||
|
||||
# Provide setjmp/longjmp stubs.
|
||||
.def _setjmp; .scl 2; .type 32; .endef
|
||||
.globl _setjmp
|
||||
_setjmp:
|
||||
retq
|
||||
|
||||
.def longjmp; .scl 2; .type 32; .endef
|
||||
.globl longjmp
|
||||
longjmp:
|
||||
retq
|
||||
|
||||
.def main; .scl 2; .type 32; .endef
|
||||
.globl main # -- Begin function main
|
||||
main: # @main
|
||||
subq $40, %rsp
|
||||
callq f
|
||||
xorl %eax, %eax
|
||||
addq $40, %rsp
|
||||
retq
|
||||
|
||||
.comm buf,256,4 # @buf
|
||||
|
||||
.section .rdata,"dr"
|
||||
.globl _load_config_used
|
||||
_load_config_used:
|
||||
.long 256
|
||||
.fill 124, 1, 0
|
||||
.quad __guard_fids_table
|
||||
.quad __guard_fids_count
|
||||
.long __guard_flags
|
||||
.fill 12, 1, 0
|
||||
.quad __guard_iat_table
|
||||
.quad __guard_iat_count
|
||||
.quad __guard_longjmp_table
|
||||
.quad __guard_fids_count
|
||||
.fill 84, 1, 0
|
|
@ -67,6 +67,8 @@ struct LoadConfigTables {
|
|||
uint32_t GuardFlags = 0;
|
||||
uint64_t GuardFidTableVA = 0;
|
||||
uint64_t GuardFidTableCount = 0;
|
||||
uint64_t GuardLJmpTableVA = 0;
|
||||
uint64_t GuardLJmpTableCount = 0;
|
||||
};
|
||||
|
||||
class COFFDumper : public ObjDumper {
|
||||
|
@ -800,6 +802,11 @@ void COFFDumper::printCOFFLoadConfig() {
|
|||
printRVATable(Tables.GuardFidTableVA, Tables.GuardFidTableCount, 4);
|
||||
}
|
||||
}
|
||||
|
||||
if (Tables.GuardLJmpTableVA) {
|
||||
ListScope LS(W, "GuardLJmpTable");
|
||||
printRVATable(Tables.GuardLJmpTableVA, Tables.GuardLJmpTableCount, 4);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
@ -879,6 +886,9 @@ void COFFDumper::printCOFFLoadConfig(const T *Conf, LoadConfigTables &Tables) {
|
|||
W.printHex("GuardRFVerifyStackPointerFunctionPointer",
|
||||
Conf->GuardRFVerifyStackPointerFunctionPointer);
|
||||
W.printHex("HotPatchTableOffset", Conf->HotPatchTableOffset);
|
||||
|
||||
Tables.GuardLJmpTableVA = Conf->GuardLongJumpTargetTable;
|
||||
Tables.GuardLJmpTableCount = Conf->GuardLongJumpTargetCount;
|
||||
}
|
||||
|
||||
void COFFDumper::printBaseOfDataField(const pe32_header *Hdr) {
|
||||
|
|
Loading…
Reference in New Issue