Skip to content

Commit 365d0a5

Browse files
alvinhochunmstorsjo
authored andcommitted
[LLD][COFF] Add load config checks to warn if incorrect for CFGuard
Control Flow Guard requires specific flags and VA's be included in the load config directory to be functional. In case CFGuard is enabled via linker flags, we can check to make sure this is the case and give the user a warning if otherwise. MSVC provides a proper `_load_config_used` by default, so this is more relevant for the MinGW target in which current versions of mingw-w64 does not provide this symbol. The checks (only if CFGuard is enabled) include: - The `_load_config_used` struct shall exist. - Alignment of the `_load_config_used` struct (shall be aligned to pointer size.) - The `_load_config_used` struct shall be large enough to contain the required fields. - The values of the following fields are checked against the expected values: - GuardCFFunctionTable - GuardCFFunctionCount - GuardFlags - GuardAddressTakenIatEntryTable - GuardAddressTakenIatEntryCount - GuardLongJumpTargetTable - GuardLongJumpTargetCount - GuardEHContinuationTable - GuardEHContinuationCount Reviewed By: rnk Differential Revision: https://reviews.llvm.org/D133099
1 parent 9509f4e commit 365d0a5

File tree

2 files changed

+313
-0
lines changed

2 files changed

+313
-0
lines changed

lld/COFF/Writer.cpp

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,9 @@ class Writer {
249249

250250
uint32_t getSizeOfInitializedData();
251251

252+
void checkLoadConfig();
253+
template <typename T> void checkLoadConfigGuardData(const T *loadConfig);
254+
252255
std::unique_ptr<FileOutputBuffer> &buffer;
253256
std::map<PartialSectionKey, PartialSection *> partialSections;
254257
std::vector<char> strtab;
@@ -625,6 +628,7 @@ void Writer::run() {
625628
writeHeader<pe32_header>();
626629
}
627630
writeSections();
631+
checkLoadConfig();
628632
sortExceptionTable();
629633

630634
// Fix up the alignment in the TLS Directory's characteristic field,
@@ -2113,3 +2117,86 @@ void Writer::fixTlsAlignment() {
21132117
tlsDir->setAlignment(tlsAlignment);
21142118
}
21152119
}
2120+
2121+
void Writer::checkLoadConfig() {
2122+
Symbol *sym = ctx.symtab.findUnderscore("_load_config_used");
2123+
auto *b = cast_if_present<DefinedRegular>(sym);
2124+
if (!b) {
2125+
if (config->guardCF != GuardCFLevel::Off)
2126+
warn("Control Flow Guard is enabled but '_load_config_used' is missing");
2127+
return;
2128+
}
2129+
2130+
OutputSection *sec = ctx.getOutputSection(b->getChunk());
2131+
uint8_t *buf = buffer->getBufferStart();
2132+
uint8_t *secBuf = buf + sec->getFileOff();
2133+
uint8_t *symBuf = secBuf + (b->getRVA() - sec->getRVA());
2134+
uint32_t expectedAlign = config->is64() ? 8 : 4;
2135+
if (b->getChunk()->getAlignment() < expectedAlign)
2136+
warn("'_load_config_used' is misaligned (expected alignment to be " +
2137+
Twine(expectedAlign) + " bytes, got " +
2138+
Twine(b->getChunk()->getAlignment()) + " instead)");
2139+
else if (!isAligned(Align(expectedAlign), b->getRVA()))
2140+
warn("'_load_config_used' is misaligned (RVA is 0x" +
2141+
Twine::utohexstr(b->getRVA()) + " not aligned to " +
2142+
Twine(expectedAlign) + " bytes)");
2143+
2144+
if (config->is64())
2145+
checkLoadConfigGuardData(
2146+
reinterpret_cast<const coff_load_configuration64 *>(symBuf));
2147+
else
2148+
checkLoadConfigGuardData(
2149+
reinterpret_cast<const coff_load_configuration32 *>(symBuf));
2150+
}
2151+
2152+
template <typename T>
2153+
void Writer::checkLoadConfigGuardData(const T *loadConfig) {
2154+
size_t loadConfigSize = loadConfig->Size;
2155+
2156+
#define RETURN_IF_NOT_CONTAINS(field) \
2157+
if (loadConfigSize < offsetof(T, field) + sizeof(T::field)) { \
2158+
warn("'_load_config_used' structure too small to include " #field); \
2159+
return; \
2160+
}
2161+
2162+
#define IF_CONTAINS(field) \
2163+
if (loadConfigSize >= offsetof(T, field) + sizeof(T::field))
2164+
2165+
#define CHECK_VA(field, sym) \
2166+
if (auto *s = dyn_cast<DefinedSynthetic>(ctx.symtab.findUnderscore(sym))) \
2167+
if (loadConfig->field != config->imageBase + s->getRVA()) \
2168+
warn(#field " not set correctly in '_load_config_used'");
2169+
2170+
#define CHECK_ABSOLUTE(field, sym) \
2171+
if (auto *s = dyn_cast<DefinedAbsolute>(ctx.symtab.findUnderscore(sym))) \
2172+
if (loadConfig->field != s->getVA()) \
2173+
warn(#field " not set correctly in '_load_config_used'");
2174+
2175+
if (config->guardCF == GuardCFLevel::Off)
2176+
return;
2177+
RETURN_IF_NOT_CONTAINS(GuardFlags)
2178+
CHECK_VA(GuardCFFunctionTable, "__guard_fids_table")
2179+
CHECK_ABSOLUTE(GuardCFFunctionCount, "__guard_fids_count")
2180+
CHECK_ABSOLUTE(GuardFlags, "__guard_flags")
2181+
IF_CONTAINS(GuardAddressTakenIatEntryCount) {
2182+
CHECK_VA(GuardAddressTakenIatEntryTable, "__guard_iat_table")
2183+
CHECK_ABSOLUTE(GuardAddressTakenIatEntryCount, "__guard_iat_count")
2184+
}
2185+
2186+
if (!(config->guardCF & GuardCFLevel::LongJmp))
2187+
return;
2188+
RETURN_IF_NOT_CONTAINS(GuardLongJumpTargetCount)
2189+
CHECK_VA(GuardLongJumpTargetTable, "__guard_longjmp_table")
2190+
CHECK_ABSOLUTE(GuardLongJumpTargetCount, "__guard_longjmp_count")
2191+
2192+
if (!(config->guardCF & GuardCFLevel::EHCont))
2193+
return;
2194+
RETURN_IF_NOT_CONTAINS(GuardEHContinuationCount)
2195+
CHECK_VA(GuardEHContinuationTable, "__guard_eh_cont_table")
2196+
CHECK_ABSOLUTE(GuardEHContinuationCount, "__guard_eh_cont_count")
2197+
2198+
#undef RETURN_IF_NOT_CONTAINS
2199+
#undef IF_CONTAINS
2200+
#undef CHECK_VA
2201+
#undef CHECK_ABSOLUTE
2202+
}

lld/test/COFF/guard-warnings.s

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
# Make a DLL that exports exportfn1.
2+
# RUN: yaml2obj %p/Inputs/export.yaml -o %basename_t-exp.obj
3+
# RUN: lld-link /out:%basename_t-exp.dll /dll %basename_t-exp.obj /export:exportfn1 /implib:%basename_t-exp.lib
4+
# RUN: split-file %s %t
5+
# RUN: llvm-mc -triple x86_64-windows-msvc %t/main.s -filetype=obj -o %t/main.obj
6+
7+
# RUN: lld-link %t/main.obj -guard:cf,longjmp,ehcont -out:%t-missing.exe -entry:main %basename_t-exp.lib 2>&1 | FileCheck %s --check-prefix=WARN_MISSING
8+
# WARN_MISSING: warning: Control Flow Guard is enabled but '_load_config_used' is missing
9+
10+
# RUN: llvm-mc -triple x86_64-windows-msvc %t/loadcfg-invalid.s -filetype=obj -o %t/loadcfg-invalid.obj
11+
# RUN: lld-link %t/main.obj %t/loadcfg-invalid.obj -guard:cf,longjmp,ehcont -out:%t-invalid.exe -entry:main %basename_t-exp.lib 2>&1 | FileCheck %s --check-prefix=WARN_INVALID
12+
# WARN_INVALID: warning: GuardCFFunctionTable not set correctly in '_load_config_used'
13+
# WARN_INVALID-NEXT: warning: GuardCFFunctionCount not set correctly in '_load_config_used'
14+
# WARN_INVALID-NEXT: warning: GuardFlags not set correctly in '_load_config_used'
15+
# WARN_INVALID-NEXT: warning: GuardAddressTakenIatEntryTable not set correctly in '_load_config_used'
16+
# WARN_INVALID-NEXT: warning: GuardAddressTakenIatEntryCount not set correctly in '_load_config_used'
17+
# WARN_INVALID-NEXT: warning: GuardLongJumpTargetTable not set correctly in '_load_config_used'
18+
# WARN_INVALID-NEXT: warning: GuardLongJumpTargetCount not set correctly in '_load_config_used'
19+
# WARN_INVALID-NEXT: warning: GuardEHContinuationTable not set correctly in '_load_config_used'
20+
# WARN_INVALID-NEXT: warning: GuardEHContinuationCount not set correctly in '_load_config_used'
21+
22+
# RUN: llvm-mc -triple x86_64-windows-msvc %t/loadcfg-small112.s -filetype=obj -o %t/loadcfg-small112.obj
23+
# RUN: lld-link %t/main.obj %t/loadcfg-small112.obj -guard:cf,longjmp -out:%t-small112.exe -entry:main %basename_t-exp.lib 2>&1 | FileCheck %s --check-prefix=WARN_SMALL_112
24+
# WARN_SMALL_112: warning: '_load_config_used' structure too small to include GuardFlags
25+
26+
# RUN: llvm-mc -triple x86_64-windows-msvc %t/loadcfg-small148.s -filetype=obj -o %t/loadcfg-small148.obj
27+
# RUN: lld-link %t/main.obj %t/loadcfg-small148.obj -guard:cf,longjmp -out:%t-small148.exe -entry:main %basename_t-exp.lib 2>&1 | FileCheck %s --check-prefix=WARN_SMALL_148
28+
# WARN_SMALL_148: warning: '_load_config_used' structure too small to include GuardLongJumpTargetCount
29+
30+
# RUN: llvm-mc -triple x86_64-windows-msvc %t/loadcfg-small244.s -filetype=obj -o %t/loadcfg-small244.obj
31+
# RUN: lld-link %t/main.obj %t/loadcfg-small244.obj -guard:cf,longjmp,ehcont -out:%t-small244.exe -entry:main %basename_t-exp.lib 2>&1 | FileCheck %s --check-prefix=WARN_SMALL_244
32+
# WARN_SMALL_244: warning: '_load_config_used' structure too small to include GuardEHContinuationCount
33+
34+
# RUN: llvm-mc -triple x86_64-windows-msvc %t/loadcfg-misaligned1.s -filetype=obj -o %t/loadcfg-misaligned1.obj
35+
# RUN: lld-link %t/main.obj %t/loadcfg-misaligned1.obj -guard:cf,longjmp,ehcont -out:%t-misaligned1.exe -entry:main %basename_t-exp.lib 2>&1 | FileCheck %s --check-prefix=WARN_ALIGN1
36+
# WARN_ALIGN1: warning: '_load_config_used' is misaligned (expected alignment to be 8 bytes, got 4 instead)
37+
38+
# RUN: llvm-mc -triple x86_64-windows-msvc %t/loadcfg-misaligned2.s -filetype=obj -o %t/loadcfg-misaligned2.obj
39+
# RUN: lld-link %t/main.obj %t/loadcfg-misaligned2.obj -guard:cf,longjmp,ehcont -out:%t-misaligned2.exe -entry:main %basename_t-exp.lib 2>&1 | FileCheck %s --check-prefix=WARN_ALIGN2
40+
# WARN_ALIGN2: warning: '_load_config_used' is misaligned (RVA is 0x{{[0-9A-F]*}}2 not aligned to 8 bytes)
41+
42+
# RUN: llvm-mc -triple x86_64-windows-msvc %t/loadcfg-full.s -filetype=obj -o %t/loadcfg-full.obj
43+
# RUN: lld-link %t/main.obj %t/loadcfg-full.obj -guard:cf,longjmp,ehcont -out:%t.exe -entry:main %basename_t-exp.lib 2>&1 | FileCheck %s --check-prefix=NOWARN --allow-empty
44+
# NOWARN-NOT: warning
45+
46+
# Sanity check to make sure the no-warn version has the expected data.
47+
# RUN: llvm-readobj --file-headers --coff-load-config %t.exe | FileCheck --check-prefix=CHECK %s
48+
# CHECK: ImageBase: 0x140000000
49+
# CHECK: LoadConfig [
50+
# CHECK: SEHandlerTable: 0x0
51+
# CHECK: SEHandlerCount: 0
52+
# CHECK: GuardCFCheckFunction: 0x0
53+
# CHECK: GuardCFCheckDispatch: 0x0
54+
# CHECK: GuardCFFunctionTable: 0x14000{{([0-9A-F]{4})}}
55+
# CHECK: GuardCFFunctionCount: 1
56+
# CHECK: GuardFlags [ (0x410500)
57+
# CHECK: CF_FUNCTION_TABLE_PRESENT (0x400)
58+
# CHECK: CF_INSTRUMENTED (0x100)
59+
# CHECK: CF_LONGJUMP_TABLE_PRESENT (0x10000)
60+
# CHECK: EH_CONTINUATION_TABLE_PRESENT (0x400000)
61+
# CHECK: ]
62+
# CHECK: GuardAddressTakenIatEntryTable: 0x14000{{([0-9A-F]{4})}}
63+
# CHECK: GuardAddressTakenIatEntryCount: 1
64+
# CHECK: GuardLongJumpTargetTable: 0x14000{{([0-9A-F]{4})}}
65+
# CHECK: GuardLongJumpTargetCount: 1
66+
# CHECK: GuardEHContinuationTable: 0x14000{{([0-9A-F]{4})}}
67+
# CHECK: GuardEHContinuationCount: 1
68+
# CHECK: ]
69+
# CHECK-NEXT: GuardFidTable [
70+
# CHECK-NEXT: 0x14000{{([0-9A-F]{4})}}
71+
# CHECK-NEXT: ]
72+
# CHECK-NEXT: GuardIatTable [
73+
# CHECK-NEXT: 0x14000{{([0-9A-F]{4})}}
74+
# CHECK-NEXT: ]
75+
# CHECK-NEXT: GuardLJmpTable [
76+
# CHECK-NEXT: 0x14000{{([0-9A-F]{4})}}
77+
# CHECK-NEXT: ]
78+
# CHECK-NEXT: GuardEHContTable [
79+
# CHECK-NEXT: 0x14000{{([0-9A-F]{4})}}
80+
# CHECK-NEXT: ]
81+
82+
83+
#--- main.s
84+
85+
# We need @feat.00 to have 0x4000 | 0x800 to indicate /guard:cf and /guard:ehcont.
86+
.def @feat.00;
87+
.scl 3;
88+
.type 0;
89+
.endef
90+
.globl @feat.00
91+
@feat.00 = 0x4800
92+
.def main; .scl 2; .type 32; .endef
93+
.globl main # -- Begin function main
94+
.p2align 4, 0x90
95+
main:
96+
mov %eax, %eax
97+
movq __imp_exportfn1(%rip), %rax
98+
callq *%rax
99+
nop
100+
# Fake setjmp target
101+
$cfgsj_main0:
102+
mov %ebx, %ebx
103+
nop
104+
# Fake ehcont target
105+
$ehgcr_0_1:
106+
mov %ecx, %ecx
107+
nop
108+
retq
109+
# -- End function
110+
.section .gfids$y,"dr"
111+
.symidx main
112+
.section .giats$y,"dr"
113+
.symidx __imp_exportfn1
114+
.section .gljmp$y,"dr"
115+
.symidx $cfgsj_main0
116+
.section .gehcont$y,"dr"
117+
.symidx $ehgcr_0_1
118+
.addrsig_sym main
119+
.addrsig_sym __imp_exportfn1
120+
.section .rdata,"dr"
121+
122+
#--- loadcfg-invalid.s
123+
124+
.globl _load_config_used
125+
.p2align 3
126+
_load_config_used:
127+
.long 312
128+
.fill 308, 1, 0
129+
130+
#--- loadcfg-small112.s
131+
132+
.globl _load_config_used
133+
.p2align 3
134+
_load_config_used:
135+
.long 112
136+
.fill 108, 1, 0
137+
138+
#--- loadcfg-small148.s
139+
140+
.globl _load_config_used
141+
.p2align 3
142+
_load_config_used:
143+
.long 148
144+
.fill 124, 1, 0
145+
.quad __guard_fids_table
146+
.quad __guard_fids_count
147+
.long __guard_flags
148+
149+
#--- loadcfg-small244.s
150+
151+
.globl _load_config_used
152+
.p2align 3
153+
_load_config_used:
154+
.long 244
155+
.fill 124, 1, 0
156+
.quad __guard_fids_table
157+
.quad __guard_fids_count
158+
.long __guard_flags
159+
.fill 12, 1, 0
160+
.quad __guard_iat_table
161+
.quad __guard_iat_count
162+
.quad __guard_longjmp_table
163+
.quad __guard_longjmp_count
164+
.fill 52, 1, 0 # Up to HotPatchTableOffset
165+
166+
#--- loadcfg-misaligned1.s
167+
168+
.globl _load_config_used
169+
.fill 2, 1, 0 # offset by 2
170+
.p2align 2 # then align to 0x04
171+
_load_config_used:
172+
.long 312
173+
.fill 124, 1, 0
174+
.quad __guard_fids_table
175+
.quad __guard_fids_count
176+
.long __guard_flags
177+
.fill 12, 1, 0
178+
.quad __guard_iat_table
179+
.quad __guard_iat_count
180+
.quad __guard_longjmp_table
181+
.quad __guard_longjmp_count
182+
.fill 72, 1, 0
183+
.quad __guard_eh_cont_table
184+
.quad __guard_eh_cont_count
185+
.fill 32, 1, 0
186+
187+
#--- loadcfg-misaligned2.s
188+
189+
.globl _load_config_used
190+
.p2align 4 # align to 0x10
191+
.fill 2, 1, 0 # then offset by 2
192+
_load_config_used:
193+
.long 312
194+
.fill 124, 1, 0
195+
.quad __guard_fids_table
196+
.quad __guard_fids_count
197+
.long __guard_flags
198+
.fill 12, 1, 0
199+
.quad __guard_iat_table
200+
.quad __guard_iat_count
201+
.quad __guard_longjmp_table
202+
.quad __guard_longjmp_count
203+
.fill 72, 1, 0
204+
.quad __guard_eh_cont_table
205+
.quad __guard_eh_cont_count
206+
.fill 32, 1, 0
207+
208+
#--- loadcfg-full.s
209+
210+
.globl _load_config_used
211+
.p2align 3
212+
_load_config_used:
213+
.long 312
214+
.fill 124, 1, 0
215+
.quad __guard_fids_table
216+
.quad __guard_fids_count
217+
.long __guard_flags
218+
.fill 12, 1, 0
219+
.quad __guard_iat_table
220+
.quad __guard_iat_count
221+
.quad __guard_longjmp_table
222+
.quad __guard_longjmp_count
223+
.fill 72, 1, 0
224+
.quad __guard_eh_cont_table
225+
.quad __guard_eh_cont_count
226+
.fill 32, 1, 0

0 commit comments

Comments
 (0)