summaryrefslogtreecommitdiffstats
path: root/toolkit/xre/detect_win32k_conflicts/src/detect.rs
blob: e21c80e1571e31556672b3aceb3395644116d2ec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! This module contains the actual functions that explore the registry for the status
//! of the conflicting mitigations.

use super::error::DetectConflictError;
use super::registry::{RegKey, RegValue};

use std::ffi::OsStr;

/// Subkey that the Windows Exploit Protection status is located in
const EXPLOIT_PROTECTION_SUBKEY: &str =
    "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options";

/// Name of the value within that subkey that contains the exploit protection status
const MITIGATION_VALUE_NAME: &str = "MitigationOptions";

/// The bit number within the value with the StackPivot status
const STACK_PIVOT_BIT: usize = 80;

/// The bit number within the value with the CallerCheck status
const CALLER_CHECK_BIT: usize = 84;

/// The bit number within the value with the SimExec status
const SIM_EXEC_BIT: usize = 88;

/// Printable, FFI-safe structure containing the status of the conflicting mitigations
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ConflictingMitigationStatus {
    /// The status of the CallerCheck feature. 0 = disabled, 1 = enabled
    caller_check: bool,
    /// The status of the SimExec feature. 0 = disabled, 1 = enabled
    sim_exec: bool,
    /// The status of the StackPivot feature. 0 = disabled, 1 = enabled
    stack_pivot: bool,
}

impl std::fmt::Display for ConflictingMitigationStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        use std::fmt::Write;
        write_status(f, "CallerCheck", self.caller_check)?;
        f.write_char(' ')?;
        write_status(f, "SimExec", self.sim_exec)?;
        f.write_char(' ')?;
        write_status(f, "StackPivot", self.stack_pivot)
    }
}

/// Search the registry for win32k-conflicting mitigations for the given process
///
/// `process_name` is the name of the process that will be checked for conflicting mitigations
/// It uses the same case-insensitive algorithm that the Windows Registry uses itself to detect
/// whether a process needs mitigations or not
///
/// (This will usually be `firefox.exe`)
///
/// # Caveat
///
/// Windows allows an arbitrary number of "filters", which are subkeys of the main key
/// that also have a `MitigationOptions` value. They are used to allow filtering by path.
///
/// For simplicity, we "flatten" the path filters when we figure this out, and so the
/// returned value will be the logical "OR" of all mitigations enabled with any filter
///
/// So if there are two entries for "C:\Firefox\firefox.exe" with mitigations `A` and `B` enabled
/// and another entry for "C:\Program Files\Mozilla Firefox\firefox.exe" with mitigation `C`
/// enabled, we will return that `A`, `B`, and `C` are all enabled
pub fn get_conflicting_mitigations(
    process_name: impl AsRef<OsStr>,
) -> Result<ConflictingMitigationStatus, DetectConflictError> {
    let process_name = process_name.as_ref();

    let key = RegKey::root_local_machine()
        .try_open_subkey(EXPLOIT_PROTECTION_SUBKEY)?
        .ok_or(DetectConflictError::ExploitProtectionKeyMissing)?;

    let process_key = match key.try_open_subkey(process_name)? {
        Some(key) => key,
        None => {
            log::info!(
                "process name {:?} not found in exploit protection",
                process_name
            );
            return Ok(ConflictingMitigationStatus::default());
        }
    };

    // First get mitigation status for the root key
    let mut status = get_conflicting_mitigations_for_key(&process_key)?;

    // Then get mitigation status for each subkey and logical-or them all together
    for subkey_name in process_key.subkey_names() {
        let subkey_name = subkey_name?;

        let subkey = process_key
            .try_open_subkey(subkey_name)?
            .expect("a subkey somehow doesn't exist after enumerating it");

        let subkey_status = get_conflicting_mitigations_for_key(&subkey)?;
        status.caller_check |= subkey_status.caller_check;
        status.sim_exec |= subkey_status.sim_exec;
        status.stack_pivot |= subkey_status.stack_pivot;
    }

    log::info!(
        "process name {:?} has mitigation status {:?}",
        process_name,
        status
    );

    Ok(status)
}

/// Check a key to see if it has a "MitigationOptions" value with any conflicting mitigations
/// enabled
fn get_conflicting_mitigations_for_key(
    key: &RegKey,
) -> Result<ConflictingMitigationStatus, DetectConflictError> {
    let value = match key.try_get_value(MITIGATION_VALUE_NAME)? {
        Some(value) => value,
        None => return Ok(ConflictingMitigationStatus::default()),
    };

    let bits = match value {
        RegValue::Binary(bits) => bits,
        _ => return Ok(ConflictingMitigationStatus::default()),
    };

    Ok(ConflictingMitigationStatus {
        caller_check: get_bit(&bits, CALLER_CHECK_BIT)?,
        sim_exec: get_bit(&bits, SIM_EXEC_BIT)?,
        stack_pivot: get_bit(&bits, STACK_PIVOT_BIT)?,
    })
}

/// Retrieve a bit from a vector-of-bitflags, which is what will be returned by the registry
/// entry for exploit protection. The bits are numbered first by increasing value within a byte,
/// and then by increasing index within the slice.
///
/// Ex: bit 0 = (index 0, mask 0x01), bit 7 = (index 0, mask 0x80), bit 8 = (index 1, mask 0x01)
fn get_bit(bytes: &[u8], bit_idx: usize) -> Result<bool, DetectConflictError> {
    let byte = bytes
        .get(bit_idx / 8)
        .ok_or(DetectConflictError::RegValueTooShort)?;

    Ok(if byte & (1 << (bit_idx % 8)) != 0 {
        true
    } else {
        false
    })
}

/// Write the status of a feature to the given formatter. `bit == 0` means feature is disabled,
/// `bit == 1` means enabled
fn write_status(
    f: &mut std::fmt::Formatter<'_>,
    name: &str,
    bit: bool,
) -> Result<(), std::fmt::Error> {
    let status_str = if bit { "enabled" } else { "disabled" };
    write!(f, "{} is {}.", name, status_str)
}