diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp --- a/lld/COFF/Writer.cpp +++ b/lld/COFF/Writer.cpp @@ -249,6 +249,9 @@ uint32_t getSizeOfInitializedData(); + void checkLoadConfig(); + template void checkLoadConfigGuardData(const T *loadConfig); + std::unique_ptr &buffer; std::map partialSections; std::vector strtab; @@ -625,6 +628,7 @@ writeHeader(); } writeSections(); + checkLoadConfig(); sortExceptionTable(); // Fix up the alignment in the TLS Directory's characteristic field, @@ -2113,3 +2117,86 @@ tlsDir->setAlignment(tlsAlignment); } } + +void Writer::checkLoadConfig() { + Symbol *sym = ctx.symtab.findUnderscore("_load_config_used"); + auto *b = cast_if_present(sym); + if (!b) { + if (config->guardCF != GuardCFLevel::Off) + warn("Control Flow Guard is enabled but '_load_config_used' is missing"); + return; + } + + OutputSection *sec = ctx.getOutputSection(b->getChunk()); + uint8_t *buf = buffer->getBufferStart(); + uint8_t *secBuf = buf + sec->getFileOff(); + uint8_t *symBuf = secBuf + (b->getRVA() - sec->getRVA()); + uint32_t expectedAlign = config->is64() ? 8 : 4; + if (b->getChunk()->getAlignment() < expectedAlign) + warn("'_load_config_used' is misaligned (expected alignment to be " + + Twine(expectedAlign) + " bytes, got " + + Twine(b->getChunk()->getAlignment()) + " instead)"); + else if (!isAligned(Align(expectedAlign), b->getRVA())) + warn("'_load_config_used' is misaligned (RVA is 0x" + + Twine::utohexstr(b->getRVA()) + " not aligned to " + + Twine(expectedAlign) + " bytes)"); + + if (config->is64()) + checkLoadConfigGuardData( + reinterpret_cast(symBuf)); + else + checkLoadConfigGuardData( + reinterpret_cast(symBuf)); +} + +template +void Writer::checkLoadConfigGuardData(const T *loadConfig) { + size_t loadConfigSize = loadConfig->Size; + +#define RETURN_IF_NOT_CONTAINS(field) \ + if (loadConfigSize < offsetof(T, field) + sizeof(T::field)) { \ + warn("'_load_config_used' structure too small to include " #field); \ + return; \ + } + +#define IF_CONTAINS(field) \ + if (loadConfigSize >= offsetof(T, field) + sizeof(T::field)) + +#define CHECK_VA(field, sym) \ + if (auto *s = dyn_cast(ctx.symtab.findUnderscore(sym))) \ + if (loadConfig->field != config->imageBase + s->getRVA()) \ + warn(#field " not set correctly in '_load_config_used'"); + +#define CHECK_ABSOLUTE(field, sym) \ + if (auto *s = dyn_cast(ctx.symtab.findUnderscore(sym))) \ + if (loadConfig->field != s->getVA()) \ + warn(#field " not set correctly in '_load_config_used'"); + + if (config->guardCF == GuardCFLevel::Off) + return; + RETURN_IF_NOT_CONTAINS(GuardFlags) + CHECK_VA(GuardCFFunctionTable, "__guard_fids_table") + CHECK_ABSOLUTE(GuardCFFunctionCount, "__guard_fids_count") + CHECK_ABSOLUTE(GuardFlags, "__guard_flags") + IF_CONTAINS(GuardAddressTakenIatEntryCount) { + CHECK_VA(GuardAddressTakenIatEntryTable, "__guard_iat_table") + CHECK_ABSOLUTE(GuardAddressTakenIatEntryCount, "__guard_iat_count") + } + + if (!(config->guardCF & GuardCFLevel::LongJmp)) + return; + RETURN_IF_NOT_CONTAINS(GuardLongJumpTargetCount) + CHECK_VA(GuardLongJumpTargetTable, "__guard_longjmp_table") + CHECK_ABSOLUTE(GuardLongJumpTargetCount, "__guard_longjmp_count") + + if (!(config->guardCF & GuardCFLevel::EHCont)) + return; + RETURN_IF_NOT_CONTAINS(GuardEHContinuationCount) + CHECK_VA(GuardEHContinuationTable, "__guard_eh_cont_table") + CHECK_ABSOLUTE(GuardEHContinuationCount, "__guard_eh_cont_count") + +#undef RETURN_IF_NOT_CONTAINS +#undef IF_CONTAINS +#undef CHECK_VA +#undef CHECK_ABSOLUTE +} diff --git a/lld/test/COFF/guard-warnings.s b/lld/test/COFF/guard-warnings.s new file mode 100644 --- /dev/null +++ b/lld/test/COFF/guard-warnings.s @@ -0,0 +1,226 @@ +# Make a DLL that exports exportfn1. +# RUN: yaml2obj %p/Inputs/export.yaml -o %basename_t-exp.obj +# RUN: lld-link /out:%basename_t-exp.dll /dll %basename_t-exp.obj /export:exportfn1 /implib:%basename_t-exp.lib +# RUN: split-file %s %t +# RUN: llvm-mc -triple x86_64-windows-msvc %t/main.s -filetype=obj -o %t/main.obj + +# 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 +# WARN_MISSING: warning: Control Flow Guard is enabled but '_load_config_used' is missing + +# RUN: llvm-mc -triple x86_64-windows-msvc %t/loadcfg-invalid.s -filetype=obj -o %t/loadcfg-invalid.obj +# 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 +# WARN_INVALID: warning: GuardCFFunctionTable not set correctly in '_load_config_used' +# WARN_INVALID-NEXT: warning: GuardCFFunctionCount not set correctly in '_load_config_used' +# WARN_INVALID-NEXT: warning: GuardFlags not set correctly in '_load_config_used' +# WARN_INVALID-NEXT: warning: GuardAddressTakenIatEntryTable not set correctly in '_load_config_used' +# WARN_INVALID-NEXT: warning: GuardAddressTakenIatEntryCount not set correctly in '_load_config_used' +# WARN_INVALID-NEXT: warning: GuardLongJumpTargetTable not set correctly in '_load_config_used' +# WARN_INVALID-NEXT: warning: GuardLongJumpTargetCount not set correctly in '_load_config_used' +# WARN_INVALID-NEXT: warning: GuardEHContinuationTable not set correctly in '_load_config_used' +# WARN_INVALID-NEXT: warning: GuardEHContinuationCount not set correctly in '_load_config_used' + +# RUN: llvm-mc -triple x86_64-windows-msvc %t/loadcfg-small112.s -filetype=obj -o %t/loadcfg-small112.obj +# 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 +# WARN_SMALL_112: warning: '_load_config_used' structure too small to include GuardFlags + +# RUN: llvm-mc -triple x86_64-windows-msvc %t/loadcfg-small148.s -filetype=obj -o %t/loadcfg-small148.obj +# 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 +# WARN_SMALL_148: warning: '_load_config_used' structure too small to include GuardLongJumpTargetCount + +# RUN: llvm-mc -triple x86_64-windows-msvc %t/loadcfg-small244.s -filetype=obj -o %t/loadcfg-small244.obj +# 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 +# WARN_SMALL_244: warning: '_load_config_used' structure too small to include GuardEHContinuationCount + +# RUN: llvm-mc -triple x86_64-windows-msvc %t/loadcfg-misaligned1.s -filetype=obj -o %t/loadcfg-misaligned1.obj +# 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 +# WARN_ALIGN1: warning: '_load_config_used' is misaligned (expected alignment to be 8 bytes, got 4 instead) + +# RUN: llvm-mc -triple x86_64-windows-msvc %t/loadcfg-misaligned2.s -filetype=obj -o %t/loadcfg-misaligned2.obj +# 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 +# WARN_ALIGN2: warning: '_load_config_used' is misaligned (RVA is 0x{{[0-9A-F]*}}2 not aligned to 8 bytes) + +# RUN: llvm-mc -triple x86_64-windows-msvc %t/loadcfg-full.s -filetype=obj -o %t/loadcfg-full.obj +# 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 +# NOWARN-NOT: warning + +# Sanity check to make sure the no-warn version has the expected data. +# RUN: llvm-readobj --file-headers --coff-load-config %t.exe | FileCheck --check-prefix=CHECK %s +# CHECK: ImageBase: 0x140000000 +# CHECK: LoadConfig [ +# CHECK: SEHandlerTable: 0x0 +# CHECK: SEHandlerCount: 0 +# CHECK: GuardCFCheckFunction: 0x0 +# CHECK: GuardCFCheckDispatch: 0x0 +# CHECK: GuardCFFunctionTable: 0x14000{{([0-9A-F]{4})}} +# CHECK: GuardCFFunctionCount: 1 +# CHECK: GuardFlags [ (0x410500) +# CHECK: CF_FUNCTION_TABLE_PRESENT (0x400) +# CHECK: CF_INSTRUMENTED (0x100) +# CHECK: CF_LONGJUMP_TABLE_PRESENT (0x10000) +# CHECK: EH_CONTINUATION_TABLE_PRESENT (0x400000) +# CHECK: ] +# CHECK: GuardAddressTakenIatEntryTable: 0x14000{{([0-9A-F]{4})}} +# CHECK: GuardAddressTakenIatEntryCount: 1 +# CHECK: GuardLongJumpTargetTable: 0x14000{{([0-9A-F]{4})}} +# CHECK: GuardLongJumpTargetCount: 1 +# CHECK: GuardEHContinuationTable: 0x14000{{([0-9A-F]{4})}} +# CHECK: GuardEHContinuationCount: 1 +# CHECK: ] +# CHECK-NEXT: GuardFidTable [ +# CHECK-NEXT: 0x14000{{([0-9A-F]{4})}} +# CHECK-NEXT: ] +# CHECK-NEXT: GuardIatTable [ +# CHECK-NEXT: 0x14000{{([0-9A-F]{4})}} +# CHECK-NEXT: ] +# CHECK-NEXT: GuardLJmpTable [ +# CHECK-NEXT: 0x14000{{([0-9A-F]{4})}} +# CHECK-NEXT: ] +# CHECK-NEXT: GuardEHContTable [ +# CHECK-NEXT: 0x14000{{([0-9A-F]{4})}} +# CHECK-NEXT: ] + + +#--- main.s + +# We need @feat.00 to have 0x4000 | 0x800 to indicate /guard:cf and /guard:ehcont. + .def @feat.00; + .scl 3; + .type 0; + .endef + .globl @feat.00 +@feat.00 = 0x4800 + .def main; .scl 2; .type 32; .endef + .globl main # -- Begin function main + .p2align 4, 0x90 +main: + mov %eax, %eax + movq __imp_exportfn1(%rip), %rax + callq *%rax + nop +# Fake setjmp target +$cfgsj_main0: + mov %ebx, %ebx + nop +# Fake ehcont target +$ehgcr_0_1: + mov %ecx, %ecx + nop + retq + # -- End function + .section .gfids$y,"dr" + .symidx main + .section .giats$y,"dr" + .symidx __imp_exportfn1 + .section .gljmp$y,"dr" + .symidx $cfgsj_main0 + .section .gehcont$y,"dr" + .symidx $ehgcr_0_1 + .addrsig_sym main + .addrsig_sym __imp_exportfn1 + .section .rdata,"dr" + +#--- loadcfg-invalid.s + +.globl _load_config_used + .p2align 3 +_load_config_used: + .long 312 + .fill 308, 1, 0 + +#--- loadcfg-small112.s + +.globl _load_config_used + .p2align 3 +_load_config_used: + .long 112 + .fill 108, 1, 0 + +#--- loadcfg-small148.s + +.globl _load_config_used + .p2align 3 +_load_config_used: + .long 148 + .fill 124, 1, 0 + .quad __guard_fids_table + .quad __guard_fids_count + .long __guard_flags + +#--- loadcfg-small244.s + +.globl _load_config_used + .p2align 3 +_load_config_used: + .long 244 + .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_longjmp_count + .fill 52, 1, 0 # Up to HotPatchTableOffset + +#--- loadcfg-misaligned1.s + +.globl _load_config_used + .fill 2, 1, 0 # offset by 2 + .p2align 2 # then align to 0x04 +_load_config_used: + .long 312 + .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_longjmp_count + .fill 72, 1, 0 + .quad __guard_eh_cont_table + .quad __guard_eh_cont_count + .fill 32, 1, 0 + +#--- loadcfg-misaligned2.s + +.globl _load_config_used + .p2align 4 # align to 0x10 + .fill 2, 1, 0 # then offset by 2 +_load_config_used: + .long 312 + .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_longjmp_count + .fill 72, 1, 0 + .quad __guard_eh_cont_table + .quad __guard_eh_cont_count + .fill 32, 1, 0 + +#--- loadcfg-full.s + +.globl _load_config_used + .p2align 3 +_load_config_used: + .long 312 + .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_longjmp_count + .fill 72, 1, 0 + .quad __guard_eh_cont_table + .quad __guard_eh_cont_count + .fill 32, 1, 0