Index: lld/COFF/Config.h =================================================================== --- lld/COFF/Config.h +++ lld/COFF/Config.h @@ -72,6 +72,12 @@ Fixup = 0x4, /// Relocation Table }; +enum GuardCFLevel : unsigned { + CFG_Off = 0, + CFG_NoLongJmp = 1, // Don't produce longjmp tables. + CFG_Full = 2, // Enable all protections. +}; + // Global configuration. struct Configuration { enum ManifestKind { SideBySide, Embed, No }; @@ -113,7 +119,7 @@ bool SaveTemps = false; // /guard:cf - bool GuardCF; + unsigned GuardCF = 0; // Used for SafeSEH. Symbol *SEHTable = nullptr; Index: lld/COFF/DriverUtils.cpp =================================================================== --- lld/COFF/DriverUtils.cpp +++ lld/COFF/DriverUtils.cpp @@ -128,13 +128,21 @@ 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 SplitArgs; + FullArg.split(SplitArgs, ","); + for (StringRef Arg : SplitArgs) { + if (Arg.equals_lower("no")) + Config->GuardCF = CFG_Off; + else if (Arg.equals_lower("cf")) + Config->GuardCF = CFG_Full; + else if (Arg.equals_lower("longjmp")) + Config->GuardCF = CFG_Full; + else if (Arg.equals_lower("nolongjmp")) + Config->GuardCF = CFG_NoLongJmp; + else + fatal("invalid argument to /GUARD: " + Arg); + } } // Parses a string in the form of "[,[.]]". Index: lld/COFF/InputFiles.h =================================================================== --- lld/COFF/InputFiles.h +++ lld/COFF/InputFiles.h @@ -112,6 +112,7 @@ ArrayRef getDebugChunks() { return DebugChunks; } ArrayRef getSXDataChunks() { return SXDataChunks; } ArrayRef getGuardFidChunks() { return GuardFidChunks; } + ArrayRef getGuardLJmpChunks() { return GuardLJmpChunks; } ArrayRef getSymbols() { return Symbols; } // Returns a Symbol object for the SymbolIndex'th symbol in the @@ -175,9 +176,10 @@ // 32-bit x86. std::vector 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 GuardFidChunks; + std::vector GuardLJmpChunks; // This vector contains the same chunks as Chunks, but they are // indexed such that you can get a SectionChunk by section index. Index: lld/COFF/InputFiles.cpp =================================================================== --- lld/COFF/InputFiles.cpp +++ lld/COFF/InputFiles.cpp @@ -185,6 +185,8 @@ DebugChunks.push_back(C); else if (Config->GuardCF && Name == ".gfids$y") GuardFidChunks.push_back(C); + else if (Config->GuardCF && Name == ".gljmp$y") + GuardLJmpChunks.push_back(C); else if (Name == ".sxdata") SXDataChunks.push_back(C); else Index: lld/COFF/Writer.cpp =================================================================== --- lld/COFF/Writer.cpp +++ lld/COFF/Writer.cpp @@ -124,7 +124,8 @@ void openFile(StringRef OutputPath); template void writeHeader(); void createSEHTable(OutputSection *RData); - void createGFIDTable(OutputSection *RData); + void createGuardCFTables(OutputSection *RData); + void createGLJmpTable(OutputSection *RData); void markSymbolsForRVATable(ObjFile *File, ArrayRef SymIdxChunks, SymbolRVASet &TableSymbols); @@ -440,9 +441,9 @@ if (Config->Machine == I386) createSEHTable(RData); - // Create the guard function id table if requested. + // Create /guard:cf tables if requested. if (Config->GuardCF) - createGFIDTable(RData); + createGuardCFTables(RData); } // Create .idata section for the DLL-imported symbol table. @@ -896,17 +897,21 @@ // 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 @@ 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 > CFG_NoLongJmp) + 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 > CFG_NoLongJmp) + GuardFlags |= uint32_t(coff_guard_flags::HasLongJmpTable); Symbol *FlagSym = Symtab->findUnderscore("__guard_flags"); cast(FlagSym)->setVA(GuardFlags); } Index: lld/test/COFF/gfids-corrupt.s =================================================================== --- lld/test/COFF/gfids-corrupt.s +++ lld/test/COFF/gfids-corrupt.s @@ -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{{.*}} Index: lld/test/COFF/gfids-fallback.s =================================================================== --- lld/test/COFF/gfids-fallback.s +++ lld/test/COFF/gfids-fallback.s @@ -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 Index: lld/test/COFF/gfids-gc.s =================================================================== --- lld/test/COFF/gfids-gc.s +++ lld/test/COFF/gfids-gc.s @@ -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 Index: lld/test/COFF/gfids-icf.s =================================================================== --- lld/test/COFF/gfids-icf.s +++ lld/test/COFF/gfids-icf.s @@ -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: Index: lld/test/COFF/guard-longjmp.s =================================================================== --- /dev/null +++ lld/test/COFF/guard-longjmp.s @@ -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 +# 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 Index: llvm/tools/llvm-readobj/COFFDumper.cpp =================================================================== --- llvm/tools/llvm-readobj/COFFDumper.cpp +++ llvm/tools/llvm-readobj/COFFDumper.cpp @@ -67,6 +67,8 @@ 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 @@ printRVATable(Tables.GuardFidTableVA, Tables.GuardFidTableCount, 4); } } + + if (Tables.GuardLJmpTableVA) { + ListScope LS(W, "GuardLJmpTable"); + printRVATable(Tables.GuardLJmpTableVA, Tables.GuardLJmpTableCount, 4); + } } template @@ -879,6 +886,9 @@ 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) {