Windows Process Mitigation Policies: Set Early, Query, and Stage Safely
Use the Win32 process-mitigation APIs to apply and verify exploit protections, distinguish process policy from Exploit Protection, and stage changes safely.
Windows exposes process mitigation policies as a set of controls that can restrict behaviors used in exploitation, such as dynamic code generation, image loading, or indirect control flow. They are not a single “secure process” switch. Each policy has its own compatibility trade-offs, supported versions, initialization requirements, and verification path.
The most common operational mistake is mixing three different things: a mitigation set by an application in its own process, a mitigation supplied when creating a child process, and an administrator-managed Exploit Protection configuration. These interfaces can describe related defenses, but they are not interchangeable. A trustworthy rollout identifies the source of the policy, checks the effective state for the relevant executable, and tests the application’s real startup and plug-in behavior.
Select the policy according to the threat and workload
Microsoft’s PROCESS_MITIGATION_POLICY enumeration includes distinct policy classes such as DEP, ASLR, dynamic code, control-flow guard, signature restrictions, image-load restrictions, and user-mode hardware-enforced stack protection. A policy structure contains flags for one class; the API’s buffer length must match that structure. Applications should select only mitigations that fit their process model and operating-system baseline.
For example, dynamic-code policy can prevent a process from generating dynamic code or modifying existing executable code. That can be appropriate for a process that has no JIT compiler or runtime code patching, but it can break a browser engine, managed runtime, shader compiler, instrumentation agent, or other component that deliberately produces executable memory. Image-load restrictions can reduce where executable images may be mapped from, but may conflict with applications that load plug-ins from user-writable or remote locations. CFG and other mitigations also depend on how the executable and its dependencies were built.
There is no universal policy bundle that is safe to turn on for every executable. Begin with the assets and trust boundaries: which code is shipped, which plug-ins are allowed, whether the process JITs, which helper processes it starts, and whether it must load third-party modules. Then choose a small set of controls tied to a concrete risk.
Apply in the right process and at the right time
SetProcessMitigationPolicy sets a policy for the calling process. Microsoft’s API guidance says to apply policies before or during process initialization for maximum effectiveness. In particular, forced image relocation under ASLR is effective only before images have been loaded, and ASLR policies cannot be made less restrictive after application. A call made late in startup may report a policy value without retroactively changing every code image already mapped.
A minimal in-process example sets the dynamic-code restriction and immediately queries it back:
#include <windows.h>
#include <stdio.h>
int main() {
PROCESS_MITIGATION_DYNAMIC_CODE_POLICY requested = {};
requested.ProhibitDynamicCode = 1;
if (!SetProcessMitigationPolicy(
ProcessDynamicCodePolicy,
&requested,
sizeof(requested))) {
fprintf(stderr, "SetProcessMitigationPolicy failed: %lu\n",
GetLastError());
return 1;
}
PROCESS_MITIGATION_DYNAMIC_CODE_POLICY effective = {};
if (!GetProcessMitigationPolicy(
GetCurrentProcess(),
ProcessDynamicCodePolicy,
&effective,
sizeof(effective))) {
fprintf(stderr, "GetProcessMitigationPolicy failed: %lu\n",
GetLastError());
return 1;
}
printf("ProhibitDynamicCode=%u\n", effective.ProhibitDynamicCode);
return effective.ProhibitDynamicCode ? 0 : 2;
}
This example is deliberately process-scoped and should be tested in a disposable executable before applying it to a production application. SetProcessMitigationPolicy itself is available starting with Windows 8 for desktop apps, and Microsoft documents _WIN32_WINNT >= 0x0602 for compiling applications that use the function. However, this sample’s PROCESS_MITIGATION_DYNAMIC_CODE_POLICY structure (used with ProcessDynamicCodePolicy) requires Windows 8.1 for desktop apps, so target Windows 8.1 or later for this specific example. Compile against the SDK structures for the targeted build and treat GetLastError() as diagnostic evidence; do not silently ignore a failed set call.
If a parent process needs to start a child under a mitigation from its first instruction, use process-creation attributes instead of asking the child to harden itself after startup. UpdateProcThreadAttribute documents PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY as a value applied during process creation; the child policy overrides app/system settings and cannot be changed after that child begins running. That path belongs in the process creation design and should be tested with the actual parent/child architecture, not inferred from a later query in the parent.
Query the process rather than guessing from registry state
GetProcessMitigationPolicy retrieves policy settings for a process handle. The handle needs PROCESS_QUERY_INFORMATION, and the output buffer must have the size of the selected policy structure. A monitoring utility should query only policy structures it understands, handle API failures, and report unsupported or unavailable results separately from “disabled.”
For a process that is instrumented by its own code, query GetCurrentProcess() during startup and log the relevant structure fields. For an external process, obtain a handle with the required access and query the target. Record the process image, process identifier, Windows build, policy class, API result, and query time. Process IDs are reused; do not treat a PID-only record as durable evidence without a process creation time or equivalent identity.
The Win32 query is not the only administrative view. Microsoft documents Get-ProcessMitigation -Name app.exe to inspect application-level mitigation configuration, and Set-ProcessMitigation to configure selected settings. Exploit Protection can also be configured through the Windows Security app, PowerShell, MDM, Group Policy, or management tooling. A policy package or registry snapshot tells you what was configured; a query in the running process tells you what the process reports at runtime. Keep both views when diagnosing a discrepancy.
NOTSET in the administrative view is not automatically the same as “mitigation off.” Microsoft explains that system-level NOTSET uses the Windows default for that mitigation and app-level NOTSET inherits the system-level setting. Read the specific mitigation’s documented default and current Windows version. Avoid scripts that convert every NOTSET row to false and then claim they have measured the effective process state.
Stage changes through representative compatibility tests
An enterprise rollout should begin with a baseline inventory. Group executables by role, runtime behavior, update channel, and plug-in exposure. Test on the oldest supported Windows build as well as the newest one; policy structures and hardware-backed options evolve. Include a clean start, plug-in load, update, recovery, background helper, and least-privileged user path.
Where the management surface supports audit mode for a mitigation, begin with audit and collect compatibility evidence before enforcing. Audit mode is not a universal state for every API structure, so do not assume that setting an in-process policy bit has a parallel “audit” mode. For system or application Exploit Protection, retain the exported configuration XML, identify its scope, and verify whether Group Policy or MDM is the authoritative source. Microsoft documents that Group Policy can override local configuration and that some system-level changes require a restart.
Promote a validated configuration in rings: a lab with representative hardware, a small canary cohort, a broader pilot, then production. For each ring, define a rollback route that does not rely on disabling unrelated security controls. Monitor application crashes, blocked-image events, JIT failures, plug-in load errors, and child-process startup. If a mitigation breaks a necessary capability, narrow its scope or redesign that component rather than disabling all process protections globally.
Diagnose failures at the policy boundary
When an application fails after a policy change, preserve the exact Windows build, executable hash and signer, policy XML or deployment object, mitigation flags, event logs, and startup sequence. Compare a known-good process with the affected process using the same method. An exception during dynamic-code generation points to a different compatibility boundary from a blocked image load or a CFG violation.
For a process you own, add an early self-check that requests and reads back only the policies the program relies on. Fail closed only when the application’s security design genuinely requires that policy; otherwise produce an explicit diagnostic and stop before exposing a partially protected service. Do not continue initialization after a failed policy call while reporting that the process is hardened.
Use debugging tools carefully. A debugger or instrumentation framework can alter process behavior or the mitigation state being measured. Reproduce the production launch path, including parent process, environment, code-signing identity, and management policy, before concluding that a local debug run represents the deployed process. On a managed endpoint, gather policy from the controlling deployment system as well as from Windows itself.
Keep the control plane and application contract separate
Process-mitigation APIs are useful when an application owns its own process policy or a launcher creates a child with an explicit startup contract. Exploit Protection is the administrative route for configuring system-wide or per-application compatibility settings. App Control for Business governs whether code is authorized to execute and is not a substitute for memory-protection mitigations. Treating those layers as one feature makes both audit and recovery harder.
The production-grade pattern is narrow and measurable: select a mitigation for a specific threat, apply it early or at process creation as appropriate, query the running state, exercise the application’s actual code paths, and retain the management configuration that explains the result. More restrictions are not automatically more security if they fail open during rollout, encourage blanket exclusions, or prevent the application from receiving updates.
Related:
- Windows VirtualAlloc: Reserve, Commit, Decommit, and Release Correctly
- How to Build an App Control for Business Policy in Audit Mode
Sources:
- Microsoft Learn: SetProcessMitigationPolicy
- Microsoft Learn:
PROCESS_MITIGATION_DYNAMIC_CODE_POLICYstructure - Microsoft Learn: GetProcessMitigationPolicy
- Microsoft Learn: UpdateProcThreadAttribute and process-creation mitigation policy
- Microsoft Learn: Customize exploit protection
- Microsoft Learn: Enable exploit protection
- Microsoft Learn: PROCESS_MITIGATION_CONTROL_FLOW_GUARD_POLICY