[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:
Reid Kleckner 2018-02-13 20:32:53 +00:00
parent 447ae4044d
commit fd52096259
11 changed files with 169 additions and 28 deletions

View File

@ -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;

View File

@ -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>]]".

View File

@ -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

View File

@ -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.

View File

@ -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);
}

View File

@ -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{{.*}}

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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) {