Skip to content

Instantly share code, notes, and snippets.

@EKl40
Forked from alvinhochun/cfguard-for-mingw-w64.md
Created October 13, 2025 02:25
Show Gist options
  • Save EKl40/f89e32acde20fca84140b14646d1cc7f to your computer and use it in GitHub Desktop.
Save EKl40/f89e32acde20fca84140b14646d1cc7f to your computer and use it in GitHub Desktop.

Revisions

  1. @alvinhochun alvinhochun revised this gist Feb 10, 2023. 1 changed file with 7 additions and 2 deletions.
    9 changes: 7 additions & 2 deletions cfguard-for-mingw-w64.md
    Original file line number Diff line number Diff line change
    @@ -189,7 +189,12 @@ https://reviews.llvm.org/D132810 adds `-mguard=cf` and `-mguard=cf-nochecks`
    to Clang's GNU driver.

    https://reviews.llvm.org/D132808 adds `--guard-cf` and `--guard-longjmp` to
    LLD's MinGW driver.
    LLD's MinGW driver. These flags are set automatically if linking with the
    clang or clang++ driver with the proper `-mguard=xxx` flags.

    These flags are available since LLVM 16.

    #### Legacy info for LLVM 15 or earlier

    Before these, Clang does not map any of the CFGuard flags from the GCC-style
    driver frontend, therefore we need to use the cc1 flags by prepending them
    @@ -215,7 +220,7 @@ prepending them with `-Xlink`.

    **Note**: For the MSVC linker `/guard:cf` actually implies `/guard:cf,longjmp`.
    It seems that LLD not generating the long jump table for `/guard:cf` would be
    a bug.
    a bug. Addressed in https://reviews.llvm.org/D132901.

    When compiling and linking in a single step, the command line may look like
    this:
  2. @alvinhochun alvinhochun revised this gist Sep 3, 2022. 1 changed file with 12 additions and 3 deletions.
    15 changes: 12 additions & 3 deletions cfguard-for-mingw-w64.md
    Original file line number Diff line number Diff line change
    @@ -48,6 +48,9 @@ names used by MSVC and LLVM to hold the pointer to the CFGuard check functions.
    There needs to be a way for users to disable CFGuard checks for a specific
    function. MSVC and Clang supports `__declspec(guard(nocf))` for this.

    Since https://reviews.llvm.org/D132661 has landed, this attribute also works
    when compiling for MinGW target.

    ### 2. Collecting all valid call targets into object file

    CFGuard has to know what addresses are valid call targets, which means the
    @@ -182,9 +185,15 @@ hooking mechanism can trip off the CFGuard checks).

    ### Compiler and linker flags

    Clang currently does not map any of the CFGuard flags from the GCC-style driver
    frontend, therefore we need to use the cc1 flags by prepending them with
    `-Xclang`.
    https://reviews.llvm.org/D132810 adds `-mguard=cf` and `-mguard=cf-nochecks`
    to Clang's GNU driver.

    https://reviews.llvm.org/D132808 adds `--guard-cf` and `--guard-longjmp` to
    LLD's MinGW driver.

    Before these, Clang does not map any of the CFGuard flags from the GCC-style
    driver frontend, therefore we need to use the cc1 flags by prepending them
    with `-Xclang`.

    | `clang-cl` option | `cc1` option |
    |----------------------|----------------------|
  3. @alvinhochun alvinhochun revised this gist Aug 27, 2022. 1 changed file with 5 additions and 1 deletion.
    6 changes: 5 additions & 1 deletion cfguard-for-mingw-w64.md
    Original file line number Diff line number Diff line change
    @@ -200,10 +200,14 @@ prepending them with `-Xlink`.
    - `-guard:longjmp`: needed to link the "Guard Long Jump Target" table into
    the final image.
    - `-guard:cf,longjmp`: this doesn't work via `-Wl` because it splits arguments
    by comma. Use `-Wl,-Xlink,-guard:cf,-Xlink,-guard:longjmp` instead. Also,
    by comma. Use `-Xlinker -Xlink -Xlinker -guard:cf,longjmp` instead. Also,
    `-guard:longjmp` already implies `-guard:cf` so it is not really necessary
    to specify both.

    **Note**: For the MSVC linker `/guard:cf` actually implies `/guard:cf,longjmp`.
    It seems that LLD not generating the long jump table for `/guard:cf` would be
    a bug.

    When compiling and linking in a single step, the command line may look like
    this:

  4. @alvinhochun alvinhochun revised this gist Aug 26, 2022. 1 changed file with 11 additions and 1 deletion.
    12 changes: 11 additions & 1 deletion cfguard-for-mingw-w64.md
    Original file line number Diff line number Diff line change
    @@ -122,7 +122,9 @@ supplied symbols as absolute VA (at least for LLD). The definition of
    - `__guard_fids_count`
    - `__guard_flags` - CFGuard flags for `_load_config_used.GuardFlags`, should
    be `IMAGE_GUARD_CF_INSTRUMENTED | IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT`
    (`0x500`) for CFGuard-enabled images. This value is 32-bit.
    (`0x500`) for CFGuard-enabled images. In addition, if the image includes
    a "Guard Long Jump Target" table, the `IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT`
    bit should be set. This value is 32-bit.
    - `__guard_iat_table`
    - `__guard_iat_count`
    - `__guard_longjmp_table`
    @@ -194,6 +196,14 @@ Similarly, the MinGW driver of LLD currently does not map the CFGuard flags to
    its COFF linker, therefore we need to use the link.exe-style flags by
    prepending them with `-Xlink`.

    - `-guard:cf`
    - `-guard:longjmp`: needed to link the "Guard Long Jump Target" table into
    the final image.
    - `-guard:cf,longjmp`: this doesn't work via `-Wl` because it splits arguments
    by comma. Use `-Wl,-Xlink,-guard:cf,-Xlink,-guard:longjmp` instead. Also,
    `-guard:longjmp` already implies `-guard:cf` so it is not really necessary
    to specify both.

    When compiling and linking in a single step, the command line may look like
    this:

  5. @alvinhochun alvinhochun revised this gist Aug 24, 2022. 1 changed file with 5 additions and 5 deletions.
    10 changes: 5 additions & 5 deletions cfguard-for-mingw-w64.md
    Original file line number Diff line number Diff line change
    @@ -65,12 +65,12 @@ uses these sections:
    targets.
    - `.gljmp$y` - "Guard Long Jump Target": These are locations which shall be
    considered valid long jump targets.
    - `.gehcont$y` - "Guard EH Continuation": These are locations which shall
    - ~~`.gehcont$y` - "Guard EH Continuation": These are locations which shall
    be considered valid exception handling continuation targets. Note: Guard EH
    Continuation is a later addition in VS 2019 16.7 and only supported on
    64-bit. It is only enabled when `/guard:ehcont` is passed to the compiler.
    Technically this is not part of CFGuard, but a separate feature called
    Control-flow Enforcement Technology (CET).
    Control-flow Enforcement Technology (CET).~~ Let's ignore this for now.

    Clang uses the assembly directive `.symidx` to build these sections. Each
    entry is a 4-byte index of the symbol in the symbols table.
    @@ -91,9 +91,9 @@ Windows that does not support CFGuard.
    ### 5. Link the guard tables

    The linker shall create the tables (GuardCFFunctionTable,
    GuardAddressTakenIatEntryTable, GuardLongJumpTargetTable and
    GuardEHContinuationTable) by collecting all symbols referenced from the guard
    sections (`.gfids$y`, `.giats$y`, `.gljmp$y` and `.gehcont$y` respectively)
    GuardAddressTakenIatEntryTable, GuardLongJumpTargetTable ~~and
    GuardEHContinuationTable~~) by collecting all symbols referenced from the guard
    sections (`.gfids$y`, `.giats$y`, `.gljmp$y` ~~and `.gehcont$y`~~ respectively)
    from all object files.

    The format of the tables are described in [MS docs][cfg_metadata].
  6. @alvinhochun alvinhochun revised this gist Aug 23, 2022. 1 changed file with 7 additions and 3 deletions.
    10 changes: 7 additions & 3 deletions cfguard-for-mingw-w64.md
    Original file line number Diff line number Diff line change
    @@ -66,7 +66,11 @@ uses these sections:
    - `.gljmp$y` - "Guard Long Jump Target": These are locations which shall be
    considered valid long jump targets.
    - `.gehcont$y` - "Guard EH Continuation": These are locations which shall
    be considered valid exception handling continuation targets.
    be considered valid exception handling continuation targets. Note: Guard EH
    Continuation is a later addition in VS 2019 16.7 and only supported on
    64-bit. It is only enabled when `/guard:ehcont` is passed to the compiler.
    Technically this is not part of CFGuard, but a separate feature called
    Control-flow Enforcement Technology (CET).

    Clang uses the assembly directive `.symidx` to build these sections. Each
    entry is a 4-byte index of the symbol in the symbols table.
    @@ -123,8 +127,8 @@ supplied symbols as absolute VA (at least for LLD). The definition of
    - `__guard_iat_count`
    - `__guard_longjmp_table`
    - `__guard_longjmp_count`
    - `__guard_eh_cont_table`
    - `__guard_eh_cont_count`
    - `__guard_eh_cont_table`: TODO
    - `__guard_eh_cont_count`: TODO

    In addition, these symbols are also to be referenced in `_load_config_used`:

  7. @alvinhochun alvinhochun created this gist Aug 23, 2022.
    220 changes: 220 additions & 0 deletions cfguard-for-mingw-w64.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,220 @@
    # Control Flow Guard (CFG/CFGuard) for mingw-w64

    [Control Flow Guard][cfguard] is a security mitigation that verifies the
    target address of indirect calls. It works by having the compiler insert
    a check at indirect call sites to verify the validity of the call target,
    and also the linker write the necessary data and flags into the PE/COFF image
    to enable the feature on Windows' end.

    [cfguard]: https://docs.microsoft.com/en-us/windows/win32/secbp/control-flow-guard

    This feature was first introduced on Windows 8.1 and came with VS 2015. Existing
    mingw-w64 toolchains do not support it. As far as I can tell, GCC does not
    know about this, neither does ld.bfd. LLVM does have it implemented for the
    MSVC target and it can be used with mingw-w64 through some hacks.


    ## Technical details

    These are needed to use control flow guard:

    ### 1. Adding checks to indirect calls

    The checks are basically a transformation of this:

    ```
    leaq target_func(%rip), %rax
    callq *%rax
    ```

    ...into this:

    ```
    movl $target_func, %ecx
    calll *___guard_check_icall_fptr
    calll *%ecx
    ```

    ...or in the case of x86_64:

    ```
    leaq target_func(%rip), %rax
    callq *__guard_dispatch_icall_fptr(%rip)
    ```

    `__guard_check_icall_fptr` and `__guard_dispatch_icall_fptr` are the symbol
    names used by MSVC and LLVM to hold the pointer to the CFGuard check functions.

    There needs to be a way for users to disable CFGuard checks for a specific
    function. MSVC and Clang supports `__declspec(guard(nocf))` for this.

    ### 2. Collecting all valid call targets into object file

    CFGuard has to know what addresses are valid call targets, which means the
    compiler needs to keep track of them and write them into the object file.
    Any functions that has its address taken shall be considered, because chances
    are they will be used in an indirect call somewhere else.

    MSVC (technically I'm just describing what LLVM/Clang does for the MSVC target)
    uses these sections:

    - `.gfids$y` - "Guard Function ID": These are symbols within this object file
    which shall be considered valid call targets.
    - `.giats$y` - "Guard Address-Taken IAT Entry": These are functions from the
    IAT which has had its address taken, which shall be considered valid call
    targets.
    - `.gljmp$y` - "Guard Long Jump Target": These are locations which shall be
    considered valid long jump targets.
    - `.gehcont$y` - "Guard EH Continuation": These are locations which shall
    be considered valid exception handling continuation targets.

    Clang uses the assembly directive `.symidx` to build these sections. Each
    entry is a 4-byte index of the symbol in the symbols table.

    ### 3. Set `@feat.00` symbol

    The `0x800` bit shall be set in this value to indicate that this object has
    been compiled with CFGuard.

    ### 4. Supply placeholder CFGuard check/dispatch functions and pointers

    VC runtime supplies `void *__guard_check_icall_fptr`, and also
    `void *__guard_dispatch_icall_fptr` for x86_64. They are initialized with
    the addresses of default placeholder CFGuard check/dispatch functions which
    does no checking. This makes the executables backward-compatible with older
    Windows that does not support CFGuard.

    ### 5. Link the guard tables

    The linker shall create the tables (GuardCFFunctionTable,
    GuardAddressTakenIatEntryTable, GuardLongJumpTargetTable and
    GuardEHContinuationTable) by collecting all symbols referenced from the guard
    sections (`.gfids$y`, `.giats$y`, `.gljmp$y` and `.gehcont$y` respectively)
    from all object files.

    The format of the tables are described in [MS docs][cfg_metadata].

    [cfg_metadata]: https://docs.microsoft.com/en-us/windows/win32/secbp/pe-metadata

    ### 6. Include the Load Config directory

    The load config directory contains fields essential to the operation of CFG.
    LINK.exe takes the `_load_config_used` symbol and uses it as the load config
    directory. LLD does the same thing, though it considers this to be optional.
    Ld.bfd likely does not handle this at the moment.

    VC runtime appears to supply a default `_load_config_used` symbol. It
    apparently can be overridden from user code, though I don't see this in
    official docs. LINK.exe does have checks in place to attempt to verify the
    validity of the structure of this symbol (it needs to contain certain values).
    Mingw-w64 does not currently supply this symbol, but user code can supply
    it and LLD will take that.

    Specific data for the `_load_config_used` structure is provided by linker-
    supplied symbols as absolute VA (at least for LLD). The definition of
    `_load_config_used` shall include them.

    - `__guard_fids_table`
    - `__guard_fids_count`
    - `__guard_flags` - CFGuard flags for `_load_config_used.GuardFlags`, should
    be `IMAGE_GUARD_CF_INSTRUMENTED | IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT`
    (`0x500`) for CFGuard-enabled images. This value is 32-bit.
    - `__guard_iat_table`
    - `__guard_iat_count`
    - `__guard_longjmp_table`
    - `__guard_longjmp_count`
    - `__guard_eh_cont_table`
    - `__guard_eh_cont_count`

    In addition, these symbols are also to be referenced in `_load_config_used`:

    - `__guard_check_icall_fptr` - location that stores a pointer to the CFGuard
    check function. `_load_config_used.GuardCFCheckFunction` shall point to this
    location.
    - `__guard_dispatch_icall_fptr` - location that stores a pointer to the CFGuard
    dispatch function (only for x86_64). `_load_config_used.GuardCFCheckDispatch`
    shall point to this location for x86_64 images.

    There are also these symbols that are not applicable for mingw-w64:

    - `__security_cookie`: used by MSVC for `/GS` buffer security checks (it may
    also be reused for other hardening features); GCC and Clang supports
    `-fstack-protector`, but `__stack_chk_guard` is used instead (Clang uses
    `__security_cookie` only with the MSVC target).
    - `__safe_se_handler_table`, `__safe_se_handler_count`: this table is only
    for i686, and mingw-w64 does not support SafeSEH on i686. (TODO: needs verification)
    - `__enclave_config`: ???

    ### 7. Set the `DLLCharacteristics` field in the image optional header

    The `IMAGE_DLL_CHARACTERISTICS_GUARD_CF` (`0x4000`) bit shall be set.


    ## Enabling with Clang

    See also https://github.com/mstorsjo/llvm-mingw/issues/301.

    ### Additions needed in mingw-w64

    To support the use case of enabling CFGuard with Clang, mingw-w64 should:

    - Supply `__guard_check_icall_fptr` and its placeholder implementation.
    - Supply `__guard_dispatch_icall_fptr` and its placeholder implementation
    (only for x86_64).
    - Supply a default `_load_config_used` structure.

    All these can be included in `libmingwex.a` within `mingw-w64-crt`.

    Alternatively, the user can supply these symbols themselves as a stopgap
    measure.

    ### Toolchain distribution changes

    When compiling the toolchain, everything needs to be compiled and linked with
    the CFGuard-enabling flags, with the exception of sanitizer runtimes (the API
    hooking mechanism can trip off the CFGuard checks).

    ### Compiler and linker flags

    Clang currently does not map any of the CFGuard flags from the GCC-style driver
    frontend, therefore we need to use the cc1 flags by prepending them with
    `-Xclang`.

    | `clang-cl` option | `cc1` option |
    |----------------------|----------------------|
    | `/guard:cf` | `-cfguard` |
    | `/guard:cf,nochecks` | `-cfguard-no-checks` |
    | `/guard:ehcont` | `-ehcontguard` |

    Similarly, the MinGW driver of LLD currently does not map the CFGuard flags to
    its COFF linker, therefore we need to use the link.exe-style flags by
    prepending them with `-Xlink`.

    When compiling and linking in a single step, the command line may look like
    this:

    ```
    clang++ main.cpp -o main.exe -Xclang -cfguard -Wl,-Xlink,-guard:cf
    ```

    ### Additional attributes

    Clang supports the function attribute `__declspec(guard(nocf))` to disable
    CFGuard checks inside a specific function. This will be usable in MinGW mode
    without needing hacks after [D132302] has landed.

    [D132302]: https://reviews.llvm.org/D132302


    ## For GCC and ld.bfd

    Here is what I think should be done:

    1. Ld.bfd needs to be taught how to link the guard tables and the load config
    directory. An option should be added to enable this.
    2. As a starting point, GCC should be taught how to generate the guard table
    sections, equivalent to the `-cfguard-no-checks` option of Clang.
    3. After gaining support for the above, GCC should be taught to implement
    CFGuard checks by transforming indirect calls as required, with the support
    of a new function attribute `guard(nocf)` to disable these checks for the
    specific function.