Skip to main content

windows_spawn/
mitigation.rs

1//! Typed process-creation mitigation policy encoding.
2
3/// The ordinary two-bit mitigation states used by the Windows SDK.
4#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
5#[repr(u64)]
6pub enum Mitigation {
7    /// Let the child executable and operating system choose.
8    #[default]
9    Defer = 0,
10    /// Force the mitigation on.
11    AlwaysOn = 1,
12    /// Force the mitigation off.
13    AlwaysOff = 2,
14}
15
16/// Mandatory-ASLR modes.
17#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
18#[repr(u64)]
19pub enum RelocateImages {
20    /// Defer to the child.
21    #[default]
22    Defer = 0,
23    /// Relocate images even when they are not dynamic-base compatible.
24    AlwaysOn = 1,
25    /// Do not force relocation.
26    AlwaysOff = 2,
27    /// Relocate images and reject images without relocation data.
28    RequireRelocations = 3,
29}
30
31/// Dynamic-code policy modes.
32#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
33#[repr(u64)]
34pub enum DynamicCode {
35    /// Defer to the child.
36    #[default]
37    Defer = 0,
38    /// Prohibit dynamic code.
39    Prohibit = 1,
40    /// Do not prohibit dynamic code.
41    Allow = 2,
42    /// Prohibit dynamic code while allowing the child to opt out.
43    ProhibitWithOptOut = 3,
44}
45
46/// Control Flow Guard modes.
47#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
48#[repr(u64)]
49pub enum ControlFlowGuard {
50    /// Defer to the child.
51    #[default]
52    Defer = 0,
53    /// Enable Control Flow Guard.
54    AlwaysOn = 1,
55    /// Disable Control Flow Guard.
56    AlwaysOff = 2,
57    /// Enable Control Flow Guard with export suppression.
58    ExportSuppression = 3,
59}
60
61/// Microsoft-signed binary policy modes.
62#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
63#[repr(u64)]
64pub enum SignedBinaries {
65    /// Defer to the child.
66    #[default]
67    Defer = 0,
68    /// Permit only Microsoft-signed binaries.
69    MicrosoftOnly = 1,
70    /// Do not restrict binary signatures.
71    AlwaysOff = 2,
72    /// Permit Microsoft Store binaries in addition to Microsoft binaries.
73    MicrosoftAndStore = 3,
74}
75
76/// Non-system-font policy modes.
77#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
78#[repr(u64)]
79pub enum FontDisable {
80    /// Defer to the child.
81    #[default]
82    Defer = 0,
83    /// Block non-system fonts.
84    Block = 1,
85    /// Allow non-system fonts.
86    Allow = 2,
87    /// Audit non-system font loads.
88    Audit = 3,
89}
90
91/// Loader integrity continuity modes.
92#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
93#[repr(u64)]
94pub enum LoaderIntegrity {
95    /// Defer to the child.
96    #[default]
97    Defer = 0,
98    /// Enforce loader integrity continuity.
99    AlwaysOn = 1,
100    /// Disable enforcement.
101    AlwaysOff = 2,
102    /// Audit violations.
103    Audit = 3,
104}
105
106/// Module-tampering protection modes.
107#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
108#[repr(u64)]
109pub enum ModuleTampering {
110    /// Defer to the child.
111    #[default]
112    Defer = 0,
113    /// Enable module-tampering protection.
114    AlwaysOn = 1,
115    /// Disable protection.
116    AlwaysOff = 2,
117    /// Enable protection without inheriting it into descendants.
118    NoInherit = 3,
119}
120
121/// CET user shadow-stack modes.
122///
123/// Runtime support depends on the Windows release, processor architecture,
124/// hardware capabilities, and child executable. Representability here does
125/// not imply that the current host accepts the policy.
126#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
127#[repr(u64)]
128pub enum CetShadowStacks {
129    /// Defer to the child.
130    #[default]
131    Defer = 0,
132    /// Enable user shadow stacks.
133    AlwaysOn = 1,
134    /// Disable user shadow stacks.
135    AlwaysOff = 2,
136    /// Enable strict shadow-stack mode.
137    Strict = 3,
138}
139
140/// CET set-context instruction-pointer validation modes.
141///
142/// Runtime support depends on the Windows release, processor architecture,
143/// hardware capabilities, and child executable.
144#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
145#[repr(u64)]
146pub enum UserCetContextIpValidation {
147    /// Defer to the child.
148    #[default]
149    Defer = 0,
150    /// Enable validation.
151    AlwaysOn = 1,
152    /// Disable validation.
153    AlwaysOff = 2,
154    /// Enable relaxed validation.
155    Relaxed = 3,
156}
157
158/// Modes for blocking binaries without CET or EH continuation metadata.
159///
160/// Runtime support depends on the Windows release, processor architecture,
161/// and executable metadata.
162#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
163#[repr(u64)]
164pub enum BlockNonCetBinaries {
165    /// Defer to the child.
166    #[default]
167    Defer = 0,
168    /// Block binaries without CET metadata.
169    AlwaysOn = 1,
170    /// Disable blocking.
171    AlwaysOff = 2,
172    /// Block binaries without EH continuation metadata.
173    NonEhContinuation = 3,
174}
175
176/// A complete SDK 10.0.22621 process-creation mitigation policy.
177///
178/// Setters replace one field. Reserved values and combined raw policy words
179/// cannot be represented.
180///
181/// # Runtime support
182///
183/// This type mirrors the policy fields in Windows SDK 10.0.22621; it is not a
184/// claim that every field works on every supported Windows installation.
185/// Availability varies by individual policy, Windows release, processor
186/// architecture, hardware, and child executable. windows-spawn does not weaken
187/// a requested policy. Unsupported policies return the process-creation error.
188#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
189pub struct MitigationPolicy {
190    words: [u64; 2],
191}
192
193impl MitigationPolicy {
194    /// Creates a policy which defers every field.
195    #[must_use]
196    pub const fn new() -> Self {
197        Self { words: [0, 0] }
198    }
199
200    /// Returns the encoded words for diagnostics.
201    #[must_use]
202    pub const fn words(self) -> [u64; 2] {
203        self.words
204    }
205
206    /// Enables or removes the legacy DEP-enable bit.
207    #[must_use]
208    pub const fn dep(mut self, enable: bool) -> Self {
209        self.words[0] = set_bit(self.words[0], 0, enable);
210        self
211    }
212
213    /// Enables or removes the legacy DEP ATL-thunk-emulation bit.
214    #[must_use]
215    pub const fn dep_atl_thunk(mut self, enable: bool) -> Self {
216        self.words[0] = set_bit(self.words[0], 1, enable);
217        self
218    }
219
220    /// Enables or removes the legacy SEHOP bit.
221    #[must_use]
222    pub const fn sehop(mut self, enable: bool) -> Self {
223        self.words[0] = set_bit(self.words[0], 2, enable);
224        self
225    }
226
227    /// Sets mandatory image relocation.
228    #[must_use]
229    pub const fn relocate_images(mut self, value: RelocateImages) -> Self {
230        self.words[0] = replace(self.words[0], 8, value as u64);
231        self
232    }
233
234    /// Sets heap termination on corruption.
235    #[must_use]
236    pub const fn heap_terminate(mut self, value: Mitigation) -> Self {
237        self.words[0] = replace(self.words[0], 12, value as u64);
238        self
239    }
240
241    /// Sets bottom-up ASLR.
242    #[must_use]
243    pub const fn bottom_up_aslr(mut self, value: Mitigation) -> Self {
244        self.words[0] = replace(self.words[0], 16, value as u64);
245        self
246    }
247
248    /// Sets high-entropy ASLR.
249    #[must_use]
250    pub const fn high_entropy_aslr(mut self, value: Mitigation) -> Self {
251        self.words[0] = replace(self.words[0], 20, value as u64);
252        self
253    }
254
255    /// Sets strict invalid-handle checking.
256    #[must_use]
257    pub const fn strict_handle_checks(mut self, value: Mitigation) -> Self {
258        self.words[0] = replace(self.words[0], 24, value as u64);
259        self
260    }
261
262    /// Sets the Win32k system-call-disable mitigation.
263    #[must_use]
264    pub const fn disable_win32k_system_calls(mut self, value: Mitigation) -> Self {
265        self.words[0] = replace(self.words[0], 28, value as u64);
266        self
267    }
268
269    /// Sets extension-point disabling.
270    #[must_use]
271    pub const fn disable_extension_points(mut self, value: Mitigation) -> Self {
272        self.words[0] = replace(self.words[0], 32, value as u64);
273        self
274    }
275
276    /// Sets dynamic-code policy.
277    #[must_use]
278    pub const fn dynamic_code(mut self, value: DynamicCode) -> Self {
279        self.words[0] = replace(self.words[0], 36, value as u64);
280        self
281    }
282
283    /// Sets Control Flow Guard policy.
284    #[must_use]
285    pub const fn control_flow_guard(mut self, value: ControlFlowGuard) -> Self {
286        self.words[0] = replace(self.words[0], 40, value as u64);
287        self
288    }
289
290    /// Sets signed-binary loading policy.
291    #[must_use]
292    pub const fn signed_binaries(mut self, value: SignedBinaries) -> Self {
293        self.words[0] = replace(self.words[0], 44, value as u64);
294        self
295    }
296
297    /// Sets non-system-font policy.
298    #[must_use]
299    pub const fn font_disable(mut self, value: FontDisable) -> Self {
300        self.words[0] = replace(self.words[0], 48, value as u64);
301        self
302    }
303
304    /// Sets remote-image blocking.
305    #[must_use]
306    pub const fn block_remote_images(mut self, value: Mitigation) -> Self {
307        self.words[0] = replace(self.words[0], 52, value as u64);
308        self
309    }
310
311    /// Sets low-integrity-label image blocking.
312    #[must_use]
313    pub const fn block_low_label_images(mut self, value: Mitigation) -> Self {
314        self.words[0] = replace(self.words[0], 56, value as u64);
315        self
316    }
317
318    /// Sets System32 image preference.
319    #[must_use]
320    pub const fn prefer_system32_images(mut self, value: Mitigation) -> Self {
321        self.words[0] = replace(self.words[0], 60, value as u64);
322        self
323    }
324
325    /// Sets loader integrity continuity.
326    #[must_use]
327    pub const fn loader_integrity(mut self, value: LoaderIntegrity) -> Self {
328        self.words[1] = replace(self.words[1], 4, value as u64);
329        self
330    }
331
332    /// Sets strict Control Flow Guard.
333    #[must_use]
334    pub const fn strict_control_flow_guard(mut self, value: Mitigation) -> Self {
335        self.words[1] = replace(self.words[1], 8, value as u64);
336        self
337    }
338
339    /// Sets module-tampering protection.
340    #[must_use]
341    pub const fn module_tampering(mut self, value: ModuleTampering) -> Self {
342        self.words[1] = replace(self.words[1], 12, value as u64);
343        self
344    }
345
346    /// Sets restricted indirect branch prediction.
347    #[must_use]
348    pub const fn restrict_indirect_branch_prediction(mut self, value: Mitigation) -> Self {
349        self.words[1] = replace(self.words[1], 16, value as u64);
350        self
351    }
352
353    /// Sets permission for a broker to downgrade dynamic-code policy.
354    #[must_use]
355    pub const fn allow_downgrade_dynamic_code(mut self, value: Mitigation) -> Self {
356        self.words[1] = replace(self.words[1], 20, value as u64);
357        self
358    }
359
360    /// Sets speculative-store-bypass disabling.
361    #[must_use]
362    pub const fn disable_speculative_store_bypass(mut self, value: Mitigation) -> Self {
363        self.words[1] = replace(self.words[1], 24, value as u64);
364        self
365    }
366
367    /// Sets CET user shadow stacks.
368    #[must_use]
369    pub const fn cet_user_shadow_stacks(mut self, value: CetShadowStacks) -> Self {
370        self.words[1] = replace(self.words[1], 28, value as u64);
371        self
372    }
373
374    /// Sets CET set-context instruction-pointer validation.
375    #[must_use]
376    pub const fn user_cet_context_ip_validation(
377        mut self,
378        value: UserCetContextIpValidation,
379    ) -> Self {
380        self.words[1] = replace(self.words[1], 32, value as u64);
381        self
382    }
383
384    /// Sets blocking of binaries without CET metadata.
385    #[must_use]
386    pub const fn block_non_cet_binaries(mut self, value: BlockNonCetBinaries) -> Self {
387        self.words[1] = replace(self.words[1], 36, value as u64);
388        self
389    }
390
391    /// Sets extended Control Flow Guard.
392    #[must_use]
393    pub const fn extended_control_flow_guard(mut self, value: Mitigation) -> Self {
394        self.words[1] = replace(self.words[1], 40, value as u64);
395        self
396    }
397
398    /// Sets ARM64 user-mode instruction-pointer authentication.
399    #[must_use]
400    pub const fn pointer_authentication(mut self, value: Mitigation) -> Self {
401        self.words[1] = replace(self.words[1], 44, value as u64);
402        self
403    }
404
405    /// Sets CET dynamic APIs to out-of-process-only mode.
406    #[must_use]
407    pub const fn cet_dynamic_apis_out_of_process(mut self, value: Mitigation) -> Self {
408        self.words[1] = replace(self.words[1], 48, value as u64);
409        self
410    }
411
412    /// Sets restricted CPU-core sharing.
413    #[must_use]
414    pub const fn restrict_core_sharing(mut self, value: Mitigation) -> Self {
415        self.words[1] = replace(self.words[1], 52, value as u64);
416        self
417    }
418
419    /// Sets FSCTL system-call disabling.
420    #[must_use]
421    pub const fn disable_fsctl_system_calls(mut self, value: Mitigation) -> Self {
422        self.words[1] = replace(self.words[1], 56, value as u64);
423        self
424    }
425}
426
427const fn replace(word: u64, shift: u32, value: u64) -> u64 {
428    (word & !(3_u64 << shift)) | (value << shift)
429}
430
431const fn set_bit(word: u64, shift: u32, value: bool) -> u64 {
432    if value {
433        word | (1_u64 << shift)
434    } else {
435        word & !(1_u64 << shift)
436    }
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442
443    #[test]
444    fn setters_replace_only_their_field() {
445        let first = MitigationPolicy::new()
446            .dynamic_code(DynamicCode::ProhibitWithOptOut)
447            .font_disable(FontDisable::Audit)
448            .dynamic_code(DynamicCode::Allow);
449        assert_eq!(first.words(), [(2_u64 << 36) | (3_u64 << 48), 0]);
450
451        let second = MitigationPolicy::new()
452            .cet_user_shadow_stacks(CetShadowStacks::Strict)
453            .block_non_cet_binaries(BlockNonCetBinaries::NonEhContinuation);
454        assert_eq!(second.words(), [0, (3_u64 << 28) | (3_u64 << 36)]);
455    }
456
457    #[test]
458    fn legacy_bits_do_not_touch_two_bit_fields() {
459        let policy = MitigationPolicy::new()
460            .relocate_images(RelocateImages::RequireRelocations)
461            .dep(true)
462            .dep_atl_thunk(true)
463            .sehop(true);
464        assert_eq!(policy.words()[0], 7 | (3_u64 << 8));
465
466        let cleared = policy.dep(false).dep_atl_thunk(false).sehop(false);
467        assert_eq!(cleared.words()[0], 3_u64 << 8);
468        assert_eq!(MitigationPolicy::new().dep(true).dep(true).words()[0], 1);
469    }
470
471    #[test]
472    #[allow(clippy::too_many_lines)]
473    fn every_sdk_22621_field_has_the_expected_encoding() {
474        macro_rules! field {
475            ($policy:expr, $word:expr, $shift:expr, $value:expr) => {{
476                let mut expected = [0_u64; 2];
477                expected[$word] = ($value as u64) << $shift;
478                assert_eq!($policy.words(), expected);
479            }};
480        }
481
482        field!(MitigationPolicy::new().dep(true), 0, 0, 1);
483        field!(MitigationPolicy::new().dep_atl_thunk(true), 0, 1, 1);
484        field!(MitigationPolicy::new().sehop(true), 0, 2, 1);
485        field!(
486            MitigationPolicy::new().relocate_images(RelocateImages::RequireRelocations),
487            0,
488            8,
489            3
490        );
491        field!(
492            MitigationPolicy::new().heap_terminate(Mitigation::AlwaysOn),
493            0,
494            12,
495            1
496        );
497        field!(
498            MitigationPolicy::new().bottom_up_aslr(Mitigation::AlwaysOn),
499            0,
500            16,
501            1
502        );
503        field!(
504            MitigationPolicy::new().high_entropy_aslr(Mitigation::AlwaysOn),
505            0,
506            20,
507            1
508        );
509        field!(
510            MitigationPolicy::new().strict_handle_checks(Mitigation::AlwaysOn),
511            0,
512            24,
513            1
514        );
515        field!(
516            MitigationPolicy::new().disable_win32k_system_calls(Mitigation::AlwaysOn),
517            0,
518            28,
519            1
520        );
521        field!(
522            MitigationPolicy::new().disable_extension_points(Mitigation::AlwaysOn),
523            0,
524            32,
525            1
526        );
527        field!(
528            MitigationPolicy::new().dynamic_code(DynamicCode::ProhibitWithOptOut),
529            0,
530            36,
531            3
532        );
533        field!(
534            MitigationPolicy::new().control_flow_guard(ControlFlowGuard::ExportSuppression),
535            0,
536            40,
537            3
538        );
539        field!(
540            MitigationPolicy::new().signed_binaries(SignedBinaries::MicrosoftAndStore),
541            0,
542            44,
543            3
544        );
545        field!(
546            MitigationPolicy::new().font_disable(FontDisable::Audit),
547            0,
548            48,
549            3
550        );
551        field!(
552            MitigationPolicy::new().block_remote_images(Mitigation::AlwaysOn),
553            0,
554            52,
555            1
556        );
557        field!(
558            MitigationPolicy::new().block_low_label_images(Mitigation::AlwaysOn),
559            0,
560            56,
561            1
562        );
563        field!(
564            MitigationPolicy::new().prefer_system32_images(Mitigation::AlwaysOn),
565            0,
566            60,
567            1
568        );
569        field!(
570            MitigationPolicy::new().loader_integrity(LoaderIntegrity::Audit),
571            1,
572            4,
573            3
574        );
575        field!(
576            MitigationPolicy::new().strict_control_flow_guard(Mitigation::AlwaysOn),
577            1,
578            8,
579            1
580        );
581        field!(
582            MitigationPolicy::new().module_tampering(ModuleTampering::NoInherit),
583            1,
584            12,
585            3
586        );
587        field!(
588            MitigationPolicy::new().restrict_indirect_branch_prediction(Mitigation::AlwaysOn),
589            1,
590            16,
591            1
592        );
593        field!(
594            MitigationPolicy::new().allow_downgrade_dynamic_code(Mitigation::AlwaysOn),
595            1,
596            20,
597            1
598        );
599        field!(
600            MitigationPolicy::new().disable_speculative_store_bypass(Mitigation::AlwaysOn),
601            1,
602            24,
603            1
604        );
605        field!(
606            MitigationPolicy::new().cet_user_shadow_stacks(CetShadowStacks::Strict),
607            1,
608            28,
609            3
610        );
611        field!(
612            MitigationPolicy::new()
613                .user_cet_context_ip_validation(UserCetContextIpValidation::Relaxed),
614            1,
615            32,
616            3
617        );
618        field!(
619            MitigationPolicy::new().block_non_cet_binaries(BlockNonCetBinaries::NonEhContinuation),
620            1,
621            36,
622            3
623        );
624        field!(
625            MitigationPolicy::new().extended_control_flow_guard(Mitigation::AlwaysOn),
626            1,
627            40,
628            1
629        );
630        field!(
631            MitigationPolicy::new().pointer_authentication(Mitigation::AlwaysOn),
632            1,
633            44,
634            1
635        );
636        field!(
637            MitigationPolicy::new().cet_dynamic_apis_out_of_process(Mitigation::AlwaysOn),
638            1,
639            48,
640            1
641        );
642        field!(
643            MitigationPolicy::new().restrict_core_sharing(Mitigation::AlwaysOn),
644            1,
645            52,
646            1
647        );
648        field!(
649            MitigationPolicy::new().disable_fsctl_system_calls(Mitigation::AlwaysOn),
650            1,
651            56,
652            1
653        );
654    }
655}