Skip to content

Commit ef4e971

Browse files
ajpaverddavidchisnall
authored andcommittedOct 1, 2020
[CFGuard] Add address-taken IAT tables and delay-load support
This patch adds support for creating Guard Address-Taken IAT Entry Tables (.giats$y sections) in object files, matching the behavior of MSVC. These contain lists of address-taken imported functions, which are used by the linker to create the final GIATS table. Additionally, if any DLLs are delay-loaded, the linker must look through the .giats tables and add the respective load thunks of address-taken imports to the GFIDS table, as these are also valid call targets. Reviewed By: rnk Differential Revision: https://reviews.llvm.org/D87544
1 parent fcf70e1 commit ef4e971

File tree

12 files changed

+259
-18
lines changed

12 files changed

+259
-18
lines changed
 

‎lld/COFF/DLL.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "DLL.h"
2121
#include "Chunks.h"
22+
#include "SymbolTable.h"
2223
#include "llvm/Object/COFF.h"
2324
#include "llvm/Support/Endian.h"
2425
#include "llvm/Support/Path.h"
@@ -653,9 +654,18 @@ void DelayLoadContents::create(Defined *h) {
653654
auto *c = make<HintNameChunk>(extName, 0);
654655
names.push_back(make<LookupChunk>(c));
655656
hintNames.push_back(c);
657+
// Add a syntentic symbol for this load thunk, using the "__imp_load"
658+
// prefix, in case this thunk needs to be added to the list of valid
659+
// call targets for Control Flow Guard.
660+
StringRef symName = saver.save("__imp_load_" + extName);
661+
s->loadThunkSym =
662+
cast<DefinedSynthetic>(symtab->addSynthetic(symName, t));
656663
}
657664
}
658665
thunks.push_back(tm);
666+
StringRef tmName =
667+
saver.save("__tailMerge_" + syms[0]->getDLLName().lower());
668+
symtab->addSynthetic(tmName, tm);
659669
// Terminate with null values.
660670
addresses.push_back(make<NullChunk>(8));
661671
names.push_back(make<NullChunk>(8));

‎lld/COFF/ICF.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ bool ICF::assocEquals(const SectionChunk *a, const SectionChunk *b) {
131131
auto considerForICF = [](const SectionChunk &assoc) {
132132
StringRef Name = assoc.getSectionName();
133133
return !(Name.startswith(".debug") || Name == ".gfids$y" ||
134-
Name == ".gljmp$y");
134+
Name == ".giats$y" || Name == ".gljmp$y");
135135
};
136136
auto ra = make_filter_range(a->children(), considerForICF);
137137
auto rb = make_filter_range(b->children(), considerForICF);

‎lld/COFF/InputFiles.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ SectionChunk *ObjFile::readSection(uint32_t sectionNumber,
280280
debugChunks.push_back(c);
281281
else if (name == ".gfids$y")
282282
guardFidChunks.push_back(c);
283+
else if (name == ".giats$y")
284+
guardIATChunks.push_back(c);
283285
else if (name == ".gljmp$y")
284286
guardLJmpChunks.push_back(c);
285287
else if (name == ".sxdata")

‎lld/COFF/InputFiles.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ class ObjFile : public InputFile {
144144
ArrayRef<SectionChunk *> getDebugChunks() { return debugChunks; }
145145
ArrayRef<SectionChunk *> getSXDataChunks() { return sxDataChunks; }
146146
ArrayRef<SectionChunk *> getGuardFidChunks() { return guardFidChunks; }
147+
ArrayRef<SectionChunk *> getGuardIATChunks() { return guardIATChunks; }
147148
ArrayRef<SectionChunk *> getGuardLJmpChunks() { return guardLJmpChunks; }
148149
ArrayRef<Symbol *> getSymbols() { return symbols; }
149150

@@ -283,9 +284,11 @@ class ObjFile : public InputFile {
283284
// 32-bit x86.
284285
std::vector<SectionChunk *> sxDataChunks;
285286

286-
// Chunks containing symbol table indices of address taken symbols and longjmp
287-
// targets. These are not linked into the final binary when /guard:cf is set.
287+
// Chunks containing symbol table indices of address taken symbols, address
288+
// taken IAT entries, and longjmp targets. These are not linked into the
289+
// final binary when /guard:cf is set.
288290
std::vector<SectionChunk *> guardFidChunks;
291+
std::vector<SectionChunk *> guardIATChunks;
289292
std::vector<SectionChunk *> guardLJmpChunks;
290293

291294
// This vector contains a list of all symbols defined or referenced by this

‎lld/COFF/Symbols.h

+7
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,13 @@ class DefinedImportData : public Defined {
343343
uint16_t getOrdinal() { return file->hdr->OrdinalHint; }
344344

345345
ImportFile *file;
346+
347+
// This is a pointer to the synthetic symbol associated with the load thunk
348+
// for this symbol that will be called if the DLL is delay-loaded. This is
349+
// needed for Control Flow Guard because if this DefinedImportData symbol is a
350+
// valid call target, the corresponding load thunk must also be marked as a
351+
// valid call target.
352+
DefinedSynthetic *loadThunkSym;
346353
};
347354

348355
// This class represents a symbol for a jump table entry which jumps

‎lld/COFF/Writer.cpp

+40-6
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ class Writer {
227227
void markSymbolsForRVATable(ObjFile *file,
228228
ArrayRef<SectionChunk *> symIdxChunks,
229229
SymbolRVASet &tableSymbols);
230+
void getSymbolsFromSections(ObjFile *file,
231+
ArrayRef<SectionChunk *> symIdxChunks,
232+
std::vector<Symbol *> &symbols);
230233
void maybeAddRVATable(SymbolRVASet tableSymbols, StringRef tableSym,
231234
StringRef countSym);
232235
void setSectionPermissions();
@@ -605,8 +608,9 @@ void Writer::run() {
605608

606609
createImportTables();
607610
createSections();
608-
createMiscChunks();
609611
appendImportThunks();
612+
// Import thunks must be added before the Control Flow Guard tables are added.
613+
createMiscChunks();
610614
createExportTable();
611615
mergeSections();
612616
removeUnusedSections();
@@ -1618,6 +1622,8 @@ static void markSymbolsWithRelocations(ObjFile *file,
16181622
// table.
16191623
void Writer::createGuardCFTables() {
16201624
SymbolRVASet addressTakenSyms;
1625+
SymbolRVASet giatsRVASet;
1626+
std::vector<Symbol *> giatsSymbols;
16211627
SymbolRVASet longJmpTargets;
16221628
for (ObjFile *file : ObjFile::instances) {
16231629
// If the object was compiled with /guard:cf, the address taken symbols
@@ -1627,6 +1633,8 @@ void Writer::createGuardCFTables() {
16271633
// possibly address-taken.
16281634
if (file->hasGuardCF()) {
16291635
markSymbolsForRVATable(file, file->getGuardFidChunks(), addressTakenSyms);
1636+
markSymbolsForRVATable(file, file->getGuardIATChunks(), giatsRVASet);
1637+
getSymbolsFromSections(file, file->getGuardIATChunks(), giatsSymbols);
16301638
markSymbolsForRVATable(file, file->getGuardLJmpChunks(), longJmpTargets);
16311639
} else {
16321640
markSymbolsWithRelocations(file, addressTakenSyms);
@@ -1641,6 +1649,16 @@ void Writer::createGuardCFTables() {
16411649
for (Export &e : config->exports)
16421650
maybeAddAddressTakenFunction(addressTakenSyms, e.sym);
16431651

1652+
// For each entry in the .giats table, check if it has a corresponding load
1653+
// thunk (e.g. because the DLL that defines it will be delay-loaded) and, if
1654+
// so, add the load thunk to the address taken (.gfids) table.
1655+
for (Symbol *s : giatsSymbols) {
1656+
if (auto *di = dyn_cast<DefinedImportData>(s)) {
1657+
if (di->loadThunkSym)
1658+
addSymbolToRVASet(addressTakenSyms, di->loadThunkSym);
1659+
}
1660+
}
1661+
16441662
// Ensure sections referenced in the gfid table are 16-byte aligned.
16451663
for (const ChunkAndOffset &c : addressTakenSyms)
16461664
if (c.inputChunk->getAlignment() < 16)
@@ -1649,6 +1667,10 @@ void Writer::createGuardCFTables() {
16491667
maybeAddRVATable(std::move(addressTakenSyms), "__guard_fids_table",
16501668
"__guard_fids_count");
16511669

1670+
// Add the Guard Address Taken IAT Entry Table (.giats).
1671+
maybeAddRVATable(std::move(giatsRVASet), "__guard_iat_table",
1672+
"__guard_iat_count");
1673+
16521674
// Add the longjmp target table unless the user told us not to.
16531675
if (config->guardCF == GuardCFLevel::Full)
16541676
maybeAddRVATable(std::move(longJmpTargets), "__guard_longjmp_table",
@@ -1665,11 +1687,11 @@ void Writer::createGuardCFTables() {
16651687
}
16661688

16671689
// Take a list of input sections containing symbol table indices and add those
1668-
// symbols to an RVA table. The challenge is that symbol RVAs are not known and
1690+
// symbols to a vector. The challenge is that symbol RVAs are not known and
16691691
// depend on the table size, so we can't directly build a set of integers.
1670-
void Writer::markSymbolsForRVATable(ObjFile *file,
1692+
void Writer::getSymbolsFromSections(ObjFile *file,
16711693
ArrayRef<SectionChunk *> symIdxChunks,
1672-
SymbolRVASet &tableSymbols) {
1694+
std::vector<Symbol *> &symbols) {
16731695
for (SectionChunk *c : symIdxChunks) {
16741696
// Skip sections discarded by linker GC. This comes up when a .gfids section
16751697
// is associated with something like a vtable and the vtable is discarded.
@@ -1687,7 +1709,7 @@ void Writer::markSymbolsForRVATable(ObjFile *file,
16871709
}
16881710

16891711
// Read each symbol table index and check if that symbol was included in the
1690-
// final link. If so, add it to the table symbol set.
1712+
// final link. If so, add it to the vector of symbols.
16911713
ArrayRef<ulittle32_t> symIndices(
16921714
reinterpret_cast<const ulittle32_t *>(data.data()), data.size() / 4);
16931715
ArrayRef<Symbol *> objSymbols = file->getSymbols();
@@ -1699,12 +1721,24 @@ void Writer::markSymbolsForRVATable(ObjFile *file,
16991721
}
17001722
if (Symbol *s = objSymbols[symIndex]) {
17011723
if (s->isLive())
1702-
addSymbolToRVASet(tableSymbols, cast<Defined>(s));
1724+
symbols.push_back(cast<Symbol>(s));
17031725
}
17041726
}
17051727
}
17061728
}
17071729

1730+
// Take a list of input sections containing symbol table indices and add those
1731+
// symbols to an RVA table.
1732+
void Writer::markSymbolsForRVATable(ObjFile *file,
1733+
ArrayRef<SectionChunk *> symIdxChunks,
1734+
SymbolRVASet &tableSymbols) {
1735+
std::vector<Symbol *> syms;
1736+
getSymbolsFromSections(file, symIdxChunks, syms);
1737+
1738+
for (Symbol *s : syms)
1739+
addSymbolToRVASet(tableSymbols, cast<Defined>(s));
1740+
}
1741+
17081742
// Replace the absolute table symbol with a synthetic symbol pointing to
17091743
// tableChunk so that we can emit base relocations for it and resolve section
17101744
// relative relocations.

‎lld/test/COFF/giats.s

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# REQUIRES: x86
2+
3+
# Make a DLL that exports exportfn1.
4+
# RUN: yaml2obj %p/Inputs/export.yaml -o %basename_t-exp.obj
5+
# RUN: lld-link /out:%basename_t-exp.dll /dll %basename_t-exp.obj /export:exportfn1 /implib:%basename_t-exp.lib
6+
7+
# Make an object file that imports exportfn1.
8+
# RUN: llvm-mc -triple x86_64-windows-msvc %s -filetype=obj -o %basename_t.obj
9+
10+
# Check that the Guard address-taken IAT entry tables are propagated to the final executable.
11+
# RUN: lld-link %basename_t.obj -guard:cf -entry:main -out:%basename_t-nodelay.exe %basename_t-exp.lib
12+
# RUN: llvm-readobj --file-headers --coff-load-config %basename_t-nodelay.exe | FileCheck %s --check-prefix CHECK
13+
14+
# CHECK: ImageBase: 0x140000000
15+
# CHECK: LoadConfig [
16+
# CHECK: GuardCFFunctionTable: 0x140002114
17+
# CHECK: GuardCFFunctionCount: 1
18+
# CHECK: GuardFlags: 0x10500
19+
# CHECK: GuardAddressTakenIatEntryTable: 0x140002118
20+
# CHECK: GuardAddressTakenIatEntryCount: 1
21+
# CHECK: ]
22+
# CHECK: GuardFidTable [
23+
# CHECK-NEXT: 0x14000{{.*}}
24+
# CHECK-NEXT: ]
25+
# CHECK: GuardIatTable [
26+
# CHECK-NEXT: 0x14000{{.*}}
27+
# CHECK-NEXT: ]
28+
29+
30+
# Check that the additional load thunk symbol is added to the GFIDs table.
31+
# RUN: lld-link %basename_t.obj -guard:cf -entry:main -out:%basename_t-delay.exe %basename_t-exp.lib -alternatename:__delayLoadHelper2=main -delayload:%basename_t-exp.dll
32+
# RUN: llvm-readobj --file-headers --coff-load-config %basename_t-delay.exe | FileCheck %s --check-prefix DELAY-CHECK
33+
34+
# DELAY-CHECK: ImageBase: 0x140000000
35+
# DELAY-CHECK: LoadConfig [
36+
# DELAY-CHECK: GuardCFFunctionTable: 0x140002114
37+
# DELAY-CHECK: GuardCFFunctionCount: 2
38+
# DELAY-CHECK: GuardFlags: 0x10500
39+
# DELAY-CHECK: GuardAddressTakenIatEntryTable: 0x14000211C
40+
# DELAY-CHECK: GuardAddressTakenIatEntryCount: 1
41+
# DELAY-CHECK: ]
42+
# DELAY-CHECK: GuardFidTable [
43+
# DELAY-CHECK-NEXT: 0x14000{{.*}}
44+
# DELAY-CHECK-NEXT: 0x14000{{.*}}
45+
# DELAY-CHECK-NEXT: ]
46+
# DELAY-CHECK: GuardIatTable [
47+
# DELAY-CHECK-NEXT: 0x14000{{.*}}
48+
# DELAY-CHECK-NEXT: ]
49+
50+
51+
# This assembly is reduced from C code like:
52+
# __declspec(noinline)
53+
# void IndirectCall(BOOL (func)(HANDLE)) {
54+
# (*func)(NULL);
55+
# }
56+
# int main(int argc, char** argv) {
57+
# IndirectCall(exportfn1);
58+
# }
59+
60+
.text
61+
.def @feat.00;
62+
.scl 3;
63+
.type 0;
64+
.endef
65+
.globl @feat.00
66+
.set @feat.00, 2048
67+
.def IndirectCall; .scl 2; .type 32; .endef
68+
.globl IndirectCall # -- Begin function IndirectCall
69+
.p2align 4, 0x90
70+
IndirectCall: # @IndirectCall
71+
# %bb.0:
72+
subq $40, %rsp
73+
movq %rcx, 32(%rsp)
74+
movq 32(%rsp), %rax
75+
movq %rax, %rdx # This would otherwise have be: movq __guard_dispatch_icall_fptr(%rip), %rdx
76+
xorl %ecx, %ecx
77+
callq *%rdx
78+
nop
79+
addq $40, %rsp
80+
retq
81+
# -- End function
82+
.def main; .scl 2; .type 32; .endef
83+
.globl main # -- Begin function main
84+
.p2align 4, 0x90
85+
main: # @main
86+
# %bb.0:
87+
subq $56, %rsp
88+
movq __imp_exportfn1(%rip), %rax
89+
movq %rdx, 48(%rsp)
90+
movl %ecx, 44(%rsp)
91+
movq %rax, %rcx
92+
callq IndirectCall
93+
xorl %eax, %eax
94+
addq $56, %rsp
95+
retq
96+
# -- End function
97+
.section .gfids$y,"dr"
98+
.section .giats$y,"dr"
99+
.symidx __imp_exportfn1
100+
.section .gljmp$y,"dr"
101+
102+
# Load configuration directory entry (winnt.h _IMAGE_LOAD_CONFIG_DIRECTORY64).
103+
# The linker will define the __guard_* symbols.
104+
.section .rdata,"dr"
105+
.globl _load_config_used
106+
_load_config_used:
107+
.long 256
108+
.fill 124, 1, 0
109+
.quad __guard_fids_table
110+
.quad __guard_fids_count
111+
.long __guard_flags
112+
.fill 12, 1, 0
113+
.quad __guard_iat_table
114+
.quad __guard_iat_count
115+
.quad __guard_longjmp_table
116+
.quad __guard_fids_count
117+
.fill 84, 1, 0

‎llvm/include/llvm/MC/MCObjectFileInfo.h

+2
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ class MCObjectFileInfo {
215215
MCSection *XDataSection = nullptr;
216216
MCSection *SXDataSection = nullptr;
217217
MCSection *GFIDsSection = nullptr;
218+
MCSection *GIATsSection = nullptr;
218219
MCSection *GLJMPSection = nullptr;
219220

220221
// XCOFF specific sections
@@ -398,6 +399,7 @@ class MCObjectFileInfo {
398399
MCSection *getXDataSection() const { return XDataSection; }
399400
MCSection *getSXDataSection() const { return SXDataSection; }
400401
MCSection *getGFIDsSection() const { return GFIDsSection; }
402+
MCSection *getGIATsSection() const { return GIATsSection; }
401403
MCSection *getGLJMPSection() const { return GLJMPSection; }
402404

403405
// XCOFF specific sections

0 commit comments

Comments
 (0)
Please sign in to comment.