This article provides additional details for Control Flow Guard (CFG) metadata in PE images. Familiarity with the structure for CFG metadata in PE images is assumed. See the PE Format topic for high-level documentation for CFG metadata in PE images.
Functions that are valid indirect call targets are listed in the GuardCFFunctionTable attached to the load configuration directory, sometimes termed the GFIDS table for brevity. This is a sorted list of relative virtual addresses (RVA) that contain information about valid CFG call targets. These are, generally speaking, address taken function symbols. An image that wants CFG enforcement must enumerate all address taken function symbols in its GFIDS table. The RVA list in the GFIDS table must be sorted properly or the image will not be loaded. The GFIDS table is an array of 4 + n bytes, where n is given by ((GuardFlags & IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK) >> IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT). “GuardFlags” is the GuardFlags field of the load configuration directory. This allows for extra metadata to be attached to CFG call targets in the future. The only currently defined metadata is an optional 1-byte extra flags field (“GFIDS flags”) that is attached to each GFIDS entry if any call targets have metadata. There are two GFIDS flags defined:
IMAGE_GUARD_FLAG_FID_SUPPRESSED/0x1 Call target is explicitly suppressed (do not treat it as valid for purposes of CFG) IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED/0x2 Call target is export suppressed. See Export suppression for more details
For future compatibility, tools should not set GFIDS flags that have not yet been defined and should not include additional GFIDS extra metadata bytes beyond the 1-byte currently defined since the meanings for other flags or additional metadata are not yet assigned. You can find examples of images that include extra metadata bytes by dumping the GFIDS table of binaries such as Ntdll.dll on a modern Windows 10 OS version.
Tools should only declare function symbols as valid call targets which may merit additional consideration for assembler code where labels might be address taken. For historical reasons, assembler code may rely on code labels other than PROC or .altentry as not being converted into CFG call targets by the linker.
Also for historical reasons, code may deliberately declare code as data to avoid inclusion in the GFIDS table. For example, one object file may implement a symbol as code while another may declare it as data in order to take the address of the symbol without generating a valid CFG target record. For compatibility, it is recommended that toolsets support this practice.
Images that support CFG and that want or perform CFG checks should set the IMAGE_GUARD_CF_INSTRUMENTED and IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT GuardFlags bits, and should set the IMAGE_DLLCHARACTERISTICS_GUARD_CF DllCharacteristics bit in the image headers.
The load configuration directory advertises two function pointers: GuardCFCheckFunctionPointer and GuardCFDispatchFunctionPointer (the latter is only supported for certain architectures such as AMD64). These function pointers should point to read only memory for CFG security to be effective; the operating system’s DLL loader will reprotect the memory transiently during image loading to store the function pointers. Typical usage might be to merge these into the same section that contains the Import Address Table (IAT). The GuardCFCheckFunctionPointer provides the address of an OS-loader provided symbol that can be called with a function pointer in the first integer argument register (ECX on x86) which will return on success or will abort the process if the call target is not a valid CFG target. The GuardCFDispatchFunctionPointer provides the address of an OS-loader provided symbol that takes a call target in register RAX and performs a combined CFG check and tail branch optimized call to the call target (registers R10/R11 are reserved for use by the GuardCFDispatchFunctionPointer and integer argument registers are reserved for use by the ultimate call target). The default address of the CFG symbols in an image should point to a function that just returns (GuardCFCheckFunctionPointer) or that returns a guard-suppressed symbol (or is preferably entirely omitted from the GFIDS table symbol) that executes a “jmp rax” instruction. For AMD64 GuardCFDispatchFunctionPointer, when an image is loaded on a CFG-aware operating system, and CFG is enabled, the OS DLL loader will install appropriate function pointers, which facilities backwards compatibility. An image can supply 0 for the GuardCFDispatchFunctionPointer in the load config if it does not intend to use the CFG dispatch facility. This should be done for non-AMD64 architectures for future compatibility, in case these architectures eventually support the CFG dispatch mechanism in some form. Note that Windows 8.1 AMD64 did not support CFG dispatch and would leave the default function pointer in place for GuardCFDispatchFunctionPointer. CFG dispatch is only supported on Windows 10 and later operating systems.
User mode CFG might only be enforced for images that are marked as address space layout randomization (ASLR) compatible (specified by the /DYNAMICBASE option with the Microsoft linker). This is due to how the OS internally handles CFG where it is essentially wired in to the ASLR infrastructure. In general, users of CFG should enable ASLR for their images as a first step. Tools should not assume that the OS will always ignore CFG without ASLR set but should generally set both at the same time.
Call targets can be marked as explicitly suppressed with the __declspec(guard(suppress)) modifier, or with the /guardsym:symname,S linker directive (for asm code for example). This causes the call target to be included in the GFIDS table but marked in such a way that the OS will treat the call target as not valid. Some non-production scenarios, such as with certain application verifier instrumentation enabled on some older operating systems, may enable suppressed call targets to be treated as valid, but in general these scenarios are not expected to be production scenarios. This directive is useful for annotating “dangerous” functions that should not be considered as valid call targets, even though the normal CFG rule would include them.
Code can indicate CFG checks are not wanted with the __declspec(guard(nocf)) modifier. This directs the compiler to not insert any CFG checks for the entire function. The compiler should take care to propagate this directive to any code contributed by an inlined function that is marked as not wanting CFG checks. This approach is typically used only sparingly in specific situations where the programmer has manually inserted “CFG-equivalent” protection. The programmer knows that they are calling through some read only function table whose address is obtained through read only memory references and for which the index is masked to the function table limit. This approach may also be applied to small wrapper functions that are not inlined and that do nothing more than make a call through a function pointer. Since incorrect usage of this directive can compromise the security of CFG, the programmer must be very careful using the directive. Typically, this usage is limited to very small functions that only call one function.
Calls through the IAT should not use CFG protection. The IAT is read only in modern images (assuming that the IAT is declared in the PE headers in which case it must be on its own pages). The IAT can be used to reach functions that are guard suppressed, so this is a correctness requirement. Read only memory protection through the IAT supersedes that of CFG since the call target binding is immutable after the image import snaps are resolved, and the binding resolution is fine grained.
Protected delay load: Calls through the delay load IAT should not use CFG protection, for the same reasons as the standard IAT. The delay load IAT should be in its own section and the image should set the IMAGE_GUARD_CF_PROTECT_DELAYLOAD_IAT GuardFlags bit. This indicates that the operating system’s DLL loader should change protections for the delay load IAT during export resolution if using the operating system’s delay load support native to Windows 8 and later operating systems. The synchronization of this step is managed by the operating system DLL loader if native operating system delay load support is in use (e.g. ResolveDelayLoadedAPI) so no other component should reprotect the pages spanning the declared delay load IAT. For backwards compatibility with older pre-CFG operating systems, tools may enable the option to move the delay load IAT into its own section (canonically “.didat”), protected as read/write in the image headers, and additionally set the IMAGE_GUARD_CF_DELAYLOAD_IAT_IN_ITS_OWN_SECTION flag. This setting will cause CFG-aware operating system DLL loaders to reprotect the entire section containing the IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT table to read only memory during image loading. The option to place the delay load IAT in its own section may not be required if you do not care about running an image on operating systems that predate CFG support, but tools should make that decision based on the minimum operating system support that an image needs.
If an image does not use the operating system’s native delay load support, it can still set the protected delay load related GuardFlags bits. In this configuration, the operating system loader will just provide support to protect the delay load IAT as read only at runtime if supported by the platform, and it becomes the responsibility of the image’s internal delay load resolution stubs to synchronize and manage protection of the delay load IAT. Provided that the load configuration table is stored in read only memory (which is recommended), the presence or absence of the protected delay load IAT bit in the image’s GuardFlags field might be useful as an internal hint to the image’s internal delay load resolution stubs to indicate whether or not it should protect the delay load IAT.
It is recommended that protected delay load be enabled by default if CFG is enabled. Images that run on older operating system versions and that use the operating system’s native delay load support, as noted, may use the delay load IAT in its own section support for backwards compatibility. This is opposed to marking the delay load IAT as read only and merging it with another section, which would break on older operating system’s that do not understand protected delay loads and which provide native delay load resolution support. All Windows 10 releases and the first Windows 8.1/Windows Server 2012 R2 builds that supported CFG (meaning the November 2014 update) introduce support for protected delay load in the operating system.
- Functions that are address taken and are therefore included in the GFIDS table should be made 16-byte aligned, if possible. This may not always be possible. For example, for non-COMDAT functions that are a part of object files assembled together as one unit by non-CFG aware tools, which some assemblers may produce, the user of the tool that produced the files must appropriately set the alignment. Tools may elect to issue a diagnostic warning in this situation so that the user can take appropriate corrective action. The reason for this is that CFG marks call targets as valid or not valid on 16-byte boundaries for efficiency of fast CFG checks. If a function is not 16-byte aligned, then the entire 16-byte slot must be marked as valid, which is not as secure since you can call misaligned into code that is not at the very start of a function. This scenario is supported for ease of interoperability when first bringing CFG up for a project. Non-CFG aware images are similarly marked as valid for any call target alignment for compatibility. As before, having misaligned call targets reduces the security benefits of CFG, so tools should automatically align to a 16-byte boundary for anything in the GFIDS table when CFG is desired for an image. Symbols that are not in the GFIDS table do not need to have particular alignments for CFG.
CFG export suppression (CFG ES) is an optional mode that enables a process to indicate that call targets which were only valid because they were dllexport symbols, and which have not yet been dynamically resolved by GetProcAddress, will be considered as not valid for purposes of CFG. This reduces the surface area of CFG from system DLL exports. Export suppression involves communicating eligible “export suppressed” dllexport call targets by marking them with the IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED GFIDS flags. Dllexport symbols and the PE image entry point should be implicitly considered address taken by tools for purposes of generating the GFIDS table. If an export symbol is 16-byte aligned and it is address taken for no other reason than being a dllexport, then it can be marked with the export suppressed GFIDS flag in the function table. Call targets that are not 16-byte aligned must not be marked with the IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED GFIDS flag and cannot be restricted to only being dynamically enabled as valid call targets at GetProcAddress time.
An image that supports CFG ES includes a GuardAddressTakenIatEntryTable whose count is provided by the GuardAddressTakenIatEntryCount as part of its load configuration directory. This table is structurally formatted the same as the GFIDS table. It uses the same GuardFlags IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK mechanism to encode extra optional metadata bytes in the address taken IAT table, though all metadata bytes must be zero for the address taken IAT table and are reserved. The address taken IAT table indicates a sorted array of RVAs of import thunks which have the imported as a symbol address taken call target. This construct supports address taken symbols that exist in a remote module, and which are dllexports, with CFG ES in use. An example of such a code construct would be:
mov rcx, [__imp_DefWindowProc] call foo ; where foo takes the actual address of DefWindowProc.
All such address taken import thunks must be enumerated so that the operating system loader can find them and make the appropriate call targets valid when loading an image and snapping its imports. The table and count can be 0 if there are no import thunks that were address taken.
A module sets the IMAGE_GUARD_CF_EXPORT_SUPPRESSION_INFO_PRESENT GuardFlags bit to indicate that it has enumerated all address taken thunks in its address taken IAT table and that all exports that are CFG ES eligible are marked with the IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED GFIDS flag. Note that there may be zero such thunks and that there may also be zero such dllexport symbols. Failure to maintain the address taken IAT table can be a correctness issue as some call targets might not be made valid when they should be at DLL load time.
A module sets the IMAGE_GUARD_CF_ENABLE_EXPORT_SUPPRESSION GuardFlags bit to indicate that it wants to enable CFG ES for the process. In practice, this is only meaningful for EXEs today. A process enabling CFG ES should not load DLLs not built with CFG ES or runtime failures may occur because of undesignated address taken IAT symbols. Support for enabling CFG ES should be a separate opt-in option from enabling CFG. Providing CFG ES metadata is safe and recommended by default with CFG, though toolsets must take care to ensure they produce correct metadata. If not, their generated images may not run properly in a CFG ES process. Such support should be thoroughly tested in a test process that enforces CFG ES. The operating system built-in system DLLs support CFG ES metadata for modern Windows 10 operating system versions that understand CFG ES. Operating system versions prior to this support do not understand CFG ES at all and will ignore any CFG ES related directives in the image. Such images are still backwards compatible to older operating system versions.
CFG ES support is optional from a toolset perspective, but it is recommended that toolsets at least include support to enumerate enough information for images to run in a process that desires CFG ES. As mentioned, it is critical that toolset support be thoroughly tested to ensure that it is compatible with CFG ES, as most processes don’t yet enable CFG ES.
Exception handling and unwinding
Language specific handlers like __C_specific_handler, as designated by the exception handler information in a .pdata registration, should not be marked as valid call targets in the GFIDS table. They are instead looked up by traversing read only memory. Similarly, the Microsoft C language specific handler uses read only memory searches to locate funclets for exception handlers and thus does not declare its funclets as valid call targets in the GFIDS table.
Long jump handling (for non-x86 targets like AMD64): Toolsets compiling with CFG and supporting setjmp()/longjmp() should implement long jump as “safe long jump” that interoperates with structured exception handling (SEH). This means long jump is implemented as a call to RtlUnwindEx with STATUS_LONGJUMP as the status code in the supplied exception record and a standard _JUMP_BUFFER pointed to by ExceptionInformation. The jump unwind target should be the TargetIp of the unwind. The jump buffer represents the register context that is restored by the operating system after the long jump has completed. RtlUnwind(Ex) when called with STATUS_LONGJUMP has special significance unique to CFG. The long jump target (_JUMP_BUFFER.Rip or _JUMP_BUFFER.Lr on ARM64) is looked up in the loaded module list maintained by the operating system in read only memory. If the containing module for the jump target (the “target module”) has the IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT flag set in its GuardFlags field, then the load configuration directory has a GuardLongJumpTargetTable whith an element count specified by the load configuration GuardLongJumpTargetCount field. This table is structurally formatted the same as the GFIDS table and uses the same GuardFlags IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK mechanism to encode optional extra metadata bytes in the long jump table. All metadata bytes must be zero for the long jump table and are reserved.
The long jump table represents a sorted array of RVAs that are valid long jump targets. If a long jump target module sets IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT in its GuardFlags field, then all long jump targets must be enumerated in the LongJumpTargetTable. Even if a module has zero long jump targets, it should still set the IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT flag if the toolset supports long jump hardening for CFG. This explicitly means that the image has no long jump targets and is not an old image that the operating system must assume could have valid long jump targets at unmarked locations for which it cannot perform long jump target checking.
Long jump hardening is recommended to be enabled by default if CFG is supported. This is the disposition of Microsoft compilers. Operating systems that do not understand long jump hardening (pre-Windows 10 or older Windows 10 versions) will not perform long jump hardening checks and ignore any long jump hardening metadata, so long jump hardening is backwards compatible with older operating system releases.
For kernel mode images, the guard long jump target table should not be included in a discardable section. The guard long jump target table should always be stored in read only memory for its security properties to be effective.
There are object file markings to declare whether an object file conforms to CFG or not. An object file that conforms to CFG will list the valid call targets that it produces, explicitly, as well as any address taken IAT metadata. An object file that does not conform to CFG must have call targets inferred by examining the COFF relocations of the obj file to find relocations that point to the start of a function symbol. This may overapproximate valid CFG call targets so it is desirable that tools mark their obj files that are CFG-aware and include the CFG obj file metadata if compiling with CFG.
There are object file markings to declare long jump targets for CFG hardened long jump which should be populated for CFG compilation mode.