This adds support to enable building mingw-w64-crt and user code with
Control Flow Guard using Clang, with the option `--enable-cfguard`. In
addition to adding the Clang 16 compiler option `-mguard=cf` to CFLAGS,
it also add two new objects to `libmingwex.a`:

- `mingw_cfguard_support.c` contains the guard check and dispatch
  function pointers and their backward compatible no-op.
- `mingw_cfguard_loadcfg.S` contains the definition of a load config
  directory structure with the symbol name `_load_config_used`.

This change alone is not enough to fully enable CFGuard in user-built
binaries. All other libraries shipped with the toolchain will also need
to be built with `-mguard=cf` to include CFGuard checks. Same goes for
building user code. If the user builds and links an executable without
`-mguard=cf`, it will still run normally but without any of the CFGuard
checks being operational.

(An exception is the sanitizer libraries -- their API hooking mechanisms
may result in indirect calls that fails CFGuard checks.)

User code may use the function attribute `__declspec(guard(nocf))`
(supported by Clang 16) to stop CFGuard checks from being added to
indirect calls within one function.

===

CFGuard works by having the compiler insert a check before performing
any indirect calls (which terminates the process if the call target is
invalid). The compiler also has to keep track of all the address-taken
functions, which the linker will then use to assemble a
`GuardCFFunctionTable` to list all valid indirect call target addresses.

To maintain backward compatibile, the guard check function is referenced
via a function pointer which points to a no-op by default. On systems
supporting CFGuard, the image loader replaces the function pointer with
the address of the actual guard check function.

For this to work, the metadata is stored in the load config directory
struct. MSVC supplies this struct in vcruntime as a symbol with the name
`_load_config_used`, which the linker takes as the load config
directory. In addition, the linker generates synthetic symbols to be
included in the load config struct. This also works with LLD since it
has implemented this feature.

The load config directory can be inspected using `llvm-readobj
--coff-load-config file.exe` or `dumpbin.exe /loadconfig file.exe`
(dumpbin.exe is included in MSVC build tools.)

Further reading:
- Control Flow Guard: 
https://docs.microsoft.com/en-us/windows/win32/secbp/control-flow-guard
- Load config structure: 
https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#the-load-configuration-structure-image-only
- Control Flow Guard metadata: 
https://docs.microsoft.com/en-us/windows/win32/secbp/pe-metadata
- My personal notes on CFGuard: 
https://gist.github.com/alvinhochun/a65e4177e2b34d551d7ecb02b55a4b0a

Signed-off-by: Alvin Wong <al...@alvinhc.com>
---
 mingw-w64-crt/Makefile.am                     |  10 +-
 mingw-w64-crt/cfguard/mingw_cfguard_loadcfg.S | 124 ++++++++++++++++++
 mingw-w64-crt/cfguard/mingw_cfguard_support.c |  58 ++++++++
 mingw-w64-crt/configure.ac                    |  16 +++
 4 files changed, 207 insertions(+), 1 deletion(-)
 create mode 100644 mingw-w64-crt/cfguard/mingw_cfguard_loadcfg.S
 create mode 100644 mingw-w64-crt/cfguard/mingw_cfguard_support.c

diff --git a/mingw-w64-crt/Makefile.am b/mingw-w64-crt/Makefile.am
index 23b1ab9f..c8a510c0 100644
--- a/mingw-w64-crt/Makefile.am
+++ b/mingw-w64-crt/Makefile.am
@@ -20,7 +20,7 @@ else
 endif
 
 AM_CPPFLAGS=$(sysincludes)
-AM_CFLAGS=-pipe -std=gnu99 -D_CRTBLD -D_WIN32_WINNT=0x0f00 
-D__MSVCRT_VERSION__=0x700 -D__USE_MINGW_ANSI_STDIO=0 @ADD_C_CXX_WARNING_FLAGS@ 
@ADD_C_ONLY_WARNING_FLAGS@
+AM_CFLAGS=-pipe -std=gnu99 -D_CRTBLD -D_WIN32_WINNT=0x0f00 
-D__MSVCRT_VERSION__=0x700 -D__USE_MINGW_ANSI_STDIO=0 @CFGUARD_CFLAGS@ 
@ADD_C_CXX_WARNING_FLAGS@ @ADD_C_ONLY_WARNING_FLAGS@
 AM_CXXFLAGS=@ADD_C_CXX_WARNING_FLAGS@ @ADD_CXX_ONLY_WARNING_FLAGS@
 CPPFLAGSARM32=-mfpu=vfpv3
 CPPFLAGS32=-m32
@@ -490,6 +490,8 @@ src_msvcr120_app=\
 
 # These mingwex sources are target independent:
 src_libmingwex=\
+  cfguard/mingw_cfguard_support.c \
+  \
   crt/dllentry.c        crt/dllmain.c \
   \
   complex/_cabs.c    complex/cabs.c   complex/cabsf.c   complex/cabsl.c   
complex/cacos.c   complex/cacosf.c   \
@@ -582,6 +584,12 @@ src_libmingwex=\
   stdio/vscanf.c           stdio/vsnprintf.c         stdio/vsnwprintf.c        
stdio/vsscanf.c         \
   stdio/vswscanf.c         stdio/vwscanf.c           stdio/wtoll.c            
stdio/mingw_asprintf.c    stdio/mingw_vasprintf.c
 
+# Include the default load config struct only for Control Flow Guard support.
+if CFGUARD
+src_libmingwex+=\
+  cfguard/mingw_cfguard_loadcfg.S
+endif
+
 # these go into both 32 and 64 bit x86 versions:
 src_libmingwex_x86=\
   math/x86/_chgsignl.S      math/x86/acosf.c          math/x86/acosh.c         
 math/x86/acosl.c          math/x86/acosh.def.h      \
diff --git a/mingw-w64-crt/cfguard/mingw_cfguard_loadcfg.S 
b/mingw-w64-crt/cfguard/mingw_cfguard_loadcfg.S
new file mode 100644
index 00000000..a7028b1a
--- /dev/null
+++ b/mingw-w64-crt/cfguard/mingw_cfguard_loadcfg.S
@@ -0,0 +1,124 @@
+/**
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the mingw-w64 runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
+ */
+
+/*
+This assembly source file defines the `_load_config_used` structure, which is
+purely data. The main purpose is to include the metadata necessary for enabling
+Control Flow Guard (CFGuard).
+
+Many of the symbols referenced here are supplied by the linker. This file has
+been constructed with LLVM/LLD in mind.
+*/
+
+#if defined(__x86_64__) || defined(__aarch64__)
+#  define PTR .8byte
+#  define ALIGN 8
+#elif defined(__i386__) || defined(__arm__)
+#  define PTR .4byte
+#  define ALIGN 4
+#else
+#  error "Load config structure is unimplemented for the current architecture."
+#endif
+
+#if defined(__x86_64__)
+/*
+GuardCFCheckDispatch is only used on x86_64. For other platforms it should be 
0.
+Ref: https://docs.microsoft.com/en-us/windows/win32/secbp/pe-metadata
+*/
+#  define CFGUARD_USE_DISPATCH
+#endif
+
+#if defined(__i386__)
+#  define SYM(x) _##x
+#else
+#  define SYM(x) x
+#endif
+
+#if defined(INCLUDE_MSVC_FEATURES)
+/*
+These features are specific to MSVC as far as I am aware.
+*/
+#  define HAS_GS_SECURITY_COOKIE
+#  if defined(__i386__)
+#    define HAS_SAFE_SE_HANDLER
+#  endif
+#  define HAS_ENCLAVE_CONFIG
+#endif
+
+       .section        .rdata,"dr"
+       .globl  SYM(_load_config_used)
+       .balign ALIGN
+SYM(_load_config_used):
+       .4byte  SYM(_load_config_used__end) - SYM(_load_config_used) /* Size */
+       .4byte  0 /* TimeDateStamp */
+       .2byte  0 /* MajorVersion */
+       .2byte  0 /* MinorVersion */
+       .4byte  0 /* GLobalFlagsClear */
+       .4byte  0 /* GlobalFlagsSet */
+       .4byte  0 /* CriticalSectionDefaultTimeout */
+       PTR     0 /* DeCommitFreeBlockThreshold */
+       PTR     0 /* DeCommitTotalFreeThreshold */
+       PTR     0 /* LockPrefixTable */
+       PTR     0 /* MaximumAllocationSize */
+       PTR     0 /* VirtualMemoryThreshold */
+       PTR     0 /* ProcessAffinityMask */
+       .4byte  0 /* ProcessHeapFlags */
+       .2byte  0 /* CSDVersion */
+       .2byte  0 /* DependentLoadFlags */
+       PTR     0 /* EditList */
+#if defined(HAS_GS_SECURITY_COOKIE)
+       PTR     SYM(__security_cookie) /* SecurityCookie */
+#else
+       PTR     0 /* SecurityCookie */
+#endif
+#if defined(HAS_SAFE_SE_HANDLER)
+       PTR     SYM(__safe_se_handler_table) /* SEHandlerTable */
+       PTR     SYM(__safe_se_handler_count) /* SEHandlerCount */
+#else
+       PTR     0 /* SEHandlerTable */
+       PTR     0 /* SEHandlerCount */
+#endif
+       PTR     SYM(__guard_check_icall_fptr) /* GuardCFCheckFunction */
+#if defined(CFGUARD_USE_DISPATCH)
+       PTR     SYM(__guard_dispatch_icall_fptr) /* GuardCFCheckDispatch */
+#else
+       PTR     0 /* GuardCFCheckDispatch */
+#endif
+       PTR     SYM(__guard_fids_table) /* GuardCFFunctionTable */
+       PTR     SYM(__guard_fids_count) /* GuardCFFunctionCount */
+       .4byte  SYM(__guard_flags) /* GuardFlags */
+       .2byte  0 /* CodeIntegrity_Flags */
+       .2byte  0 /* CodeIntegrity_Catalog */
+       .4byte  0 /* CodeIntegrity_CatalogOffset */
+       .4byte  0 /* CodeIntegrity_Reserved */
+       PTR     SYM(__guard_iat_table) /* GuardAddressTakenIatEntryTable */
+       PTR     SYM(__guard_iat_count) /* GuardAddressTakenIatEntryCount */
+       PTR     SYM(__guard_longjmp_table) /* GuardLongJumpTargetTable */
+       PTR     SYM(__guard_longjmp_count) /* GuardLongJumpTargetCount */
+       PTR     0 /* DynamicValueRelocTable */
+       PTR     0 /* CHPEMetadataPointer */
+       PTR     0 /* GuardRFFailureRoutine */
+       PTR     0 /* GuardRFFailureRoutineFunctionPointer */
+       .4byte  0 /* DynamicValueRelocTableOffset */
+       .2byte  0 /* DynamicValueRelocTableSection */
+       .2byte  0 /* Reserved2 */
+       PTR     0 /* GuardRFVerifyStackPointerFunctionPointer */
+       .4byte  0 /* HotPatchTableOffset */
+       .4byte  0 /* Reserved3 */
+#if defined(HAS_ENCLAVE_CONFIG)
+       PTR     SYM(__enclave_config) /* EnclaveConfigurationPointer */
+#else
+       PTR     0 /* EnclaveConfigurationPointer */
+#endif
+       PTR     0 /* VolatileMetadataPointer */
+       PTR     SYM(__guard_eh_cont_table) /* GuardEHContinuationTable */
+       PTR     SYM(__guard_eh_cont_count) /* GuardEHContinuationCount */
+       PTR     0 /* GuardXFGCheckFunctionPointer */
+       PTR     0 /* GuardXFGDispatchFunctionPointer */
+       PTR     0 /* GuardXFGTableDispatchFunctionPointer */
+       PTR     0 /* CastGuardOsDeterminedFailureMode */
+
+SYM(_load_config_used__end):
diff --git a/mingw-w64-crt/cfguard/mingw_cfguard_support.c 
b/mingw-w64-crt/cfguard/mingw_cfguard_support.c
new file mode 100644
index 00000000..cf4535af
--- /dev/null
+++ b/mingw-w64-crt/cfguard/mingw_cfguard_support.c
@@ -0,0 +1,58 @@
+/**
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the mingw-w64 runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
+ */
+
+// This source file defines the function pointers required to support Control
+// Flow Guard. They shall be included even if CFGuard is disabled when building
+// mingw-w64-crt itself, to allow linking to objects/libraries compiled with
+// CFGuard.
+
+#if defined(__x86_64__)
+
+// The target address is passed as a parameter in an arch-specific manner,
+// however it is not specified how on x86_64 because 
__guard_dispatch_icall_fptr
+// is used instead. My guess would be that it's passed on %rcx, but it doesn't
+// really matter here because this is a no-op anyway.
+static void __guard_check_icall_dummy(void) {}
+
+// When CFGuard is not active, directly tail-call the target address, which
+// is passed via %rax.
+__asm__(
+    "__guard_dispatch_icall_dummy:\n"
+    "    jmp *%rax\n"
+);
+
+// This is intentionally declared as _not_ a function pointer, so that the
+// jmp instruction is not included as a valid call target for CFGuard.
+extern void *__guard_dispatch_icall_dummy;
+
+#elif defined(__i386__) || defined(__aarch64__) || defined(__arm__)
+
+// The target address is passed via %ecx (x86), X15 (aarch64) or R0 (arm),
+// but it doesn't really matter here because this is a no-op anyway.
+static void __guard_check_icall_dummy(void) {}
+
+#else
+#   error "CFGuard support is unimplemented for the current architecture."
+#endif
+
+// I am not sure about the `.00cfg` section. This is just an attempt to follow
+// what VC runtime defines -- it places all the guard check function pointers
+// inside this section. The MSVC linker appears to merge this section into
+// `.rdata`, but LLD does not do this at the time of writing.
+// This section should be readonly data. The only thing that modifies these
+// pointers is the PE image loader.
+
+__asm__(".section .00cfg,\"dr\"");
+
+__attribute__(( section (".00cfg") ))
+void *__guard_check_icall_fptr = &__guard_check_icall_dummy;
+
+#if defined(__x86_64__)
+
+__attribute__(( section (".00cfg") ))
+void *__guard_dispatch_icall_fptr = &__guard_dispatch_icall_dummy;
+
+#endif
diff --git a/mingw-w64-crt/configure.ac b/mingw-w64-crt/configure.ac
index ad509531..5c68ff96 100644
--- a/mingw-w64-crt/configure.ac
+++ b/mingw-w64-crt/configure.ac
@@ -277,6 +277,21 @@ AC_MSG_RESULT([$with_default_msvcrt])
 AS_VAR_SET([MSVCRT_LIB],[lib${with_default_msvcrt}.a])
 AC_SUBST([MSVCRT_LIB])
 
+AC_MSG_CHECKING([whether to include support for Control Flow Guard])
+AC_ARG_ENABLE([cfguard],
+  [AS_HELP_STRING([--enable-cfguard],
+    [Enable building with Control Flow Guard support (Clang)])],
+  [],
+  [AS_VAR_SET([enable_cfguard],[no])])
+AC_MSG_RESULT([$enable_cfguard])
+AS_CASE([$enable_cfguard],
+  [no],[],
+  [yes],[AS_VAR_SET([CFGUARD])
+        AS_VAR_SET([CFGUARD_CFLAGS],[-mguard=cf])],
+  [AC_MSG_ERROR([invalid argument.  Must be either yes or no.])])
+AM_CONDITIONAL([CFGUARD], [AS_VAR_TEST_SET([CFGUARD])])
+AC_SUBST([CFGUARD_CFLAGS])
+
 AC_MSG_CHECKING([whether to enable experimental features])
 AC_ARG_ENABLE([experimental],
   [AS_HELP_STRING([--enable-experimental],
@@ -413,6 +428,7 @@ AC_MSG_NOTICE([  C Warning Flags......: 
$ADD_C_ONLY_WARNING_FLAGS])
 AC_MSG_NOTICE([  C++ Warning Flags....: $ADD_CXX_ONLY_WARNING_FLAGS])
 AC_MSG_NOTICE([  Common Warning Flags.: $ADD_C_CXX_WARNING_FLAGS])
 AC_MSG_NOTICE([  Default msvcrt.......: $with_default_msvcrt])
+AC_MSG_NOTICE([  Control Flow Guard...: $enable_cfguard])
 AC_MSG_NOTICE([])
 AC_MSG_NOTICE([  DFP printf...........: $enable_dfp])
 AC_MSG_NOTICE([  128-bit printf.......: $enable_printf128])
-- 
2.37.1



_______________________________________________
Mingw-w64-public mailing list
Mingw-w64-public@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public

Reply via email to