| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "sandbox/win/src/process_mitigations_win32k_policy.h" |
| |
| #include <stddef.h> |
| |
| #include "sandbox/win/src/process_mitigations_win32k_interception.h" |
| |
| namespace sandbox { |
| |
| namespace { |
| |
| // Define GUIDs for OPM APIs |
| const GUID DXGKMDT_OPM_GET_CONNECTOR_TYPE = { |
| 0x81d0bfd5, |
| 0x6afe, |
| 0x48c2, |
| {0x99, 0xc0, 0x95, 0xa0, 0x8f, 0x97, 0xc5, 0xda}}; |
| const GUID DXGKMDT_OPM_GET_SUPPORTED_PROTECTION_TYPES = { |
| 0x38f2a801, |
| 0x9a6c, |
| 0x48bb, |
| {0x91, 0x07, 0xb6, 0x69, 0x6e, 0x6f, 0x17, 0x97}}; |
| const GUID DXGKMDT_OPM_GET_VIRTUAL_PROTECTION_LEVEL = { |
| 0xb2075857, |
| 0x3eda, |
| 0x4d5d, |
| {0x88, 0xdb, 0x74, 0x8f, 0x8c, 0x1a, 0x05, 0x49}}; |
| const GUID DXGKMDT_OPM_GET_ACTUAL_PROTECTION_LEVEL = { |
| 0x1957210a, |
| 0x7766, |
| 0x452a, |
| {0xb9, 0x9a, 0xd2, 0x7a, 0xed, 0x54, 0xf0, 0x3a}}; |
| const GUID DXGKMDT_OPM_SET_PROTECTION_LEVEL = { |
| 0x9bb9327c, |
| 0x4eb5, |
| 0x4727, |
| {0x9f, 0x00, 0xb4, 0x2b, 0x09, 0x19, 0xc0, 0xda}}; |
| |
| void StringToUnicodeString(PUNICODE_STRING unicode_string, |
| const base::string16& device_name) { |
| static RtlInitUnicodeStringFunction RtlInitUnicodeString; |
| if (!RtlInitUnicodeString) { |
| HMODULE ntdll = ::GetModuleHandle(kNtdllName); |
| RtlInitUnicodeString = reinterpret_cast<RtlInitUnicodeStringFunction>( |
| GetProcAddress(ntdll, "RtlInitUnicodeString")); |
| } |
| RtlInitUnicodeString(unicode_string, device_name.c_str()); |
| } |
| |
| struct MonitorListState { |
| HMONITOR* monitor_list; |
| uint32_t monitor_list_size; |
| uint32_t monitor_list_pos; |
| }; |
| |
| BOOL CALLBACK DisplayMonitorEnumProc(HMONITOR monitor, |
| HDC hdc_monitor, |
| LPRECT rect_monitor, |
| LPARAM data) { |
| MonitorListState* state = reinterpret_cast<MonitorListState*>(data); |
| if (state->monitor_list_pos >= state->monitor_list_size) |
| return false; |
| state->monitor_list[state->monitor_list_pos++] = monitor; |
| return true; |
| } |
| |
| template <typename T> |
| T GetExportedFunc(const wchar_t* libname, const char* name) { |
| OverrideForTestFunction test_override = |
| ProcessMitigationsWin32KLockdownPolicy::GetOverrideForTestCallback(); |
| if (test_override) |
| return reinterpret_cast<T>(test_override(name)); |
| |
| static T func = nullptr; |
| if (!func) { |
| func = |
| reinterpret_cast<T>(::GetProcAddress(::GetModuleHandle(libname), name)); |
| DCHECK(!!func); |
| } |
| return func; |
| } |
| |
| #define GDIFUNC(name) GetExportedFunc<name##Function>(L"gdi32.dll", #name) |
| #define USERFUNC(name) GetExportedFunc<name##Function>(L"user32.dll", #name) |
| |
| struct ValidateMonitorParams { |
| HMONITOR monitor; |
| base::string16 device_name; |
| bool result; |
| }; |
| |
| bool GetMonitorDeviceName(HMONITOR monitor, base::string16* device_name) { |
| MONITORINFOEXW monitor_info = {}; |
| monitor_info.cbSize = sizeof(monitor_info); |
| if (!USERFUNC(GetMonitorInfoW)(monitor, &monitor_info)) |
| return false; |
| if (monitor_info.szDevice[CCHDEVICENAME - 1] != 0) |
| return false; |
| *device_name = monitor_info.szDevice; |
| return true; |
| } |
| |
| BOOL CALLBACK ValidateMonitorEnumProc(HMONITOR monitor, |
| HDC, |
| LPRECT, |
| LPARAM data) { |
| ValidateMonitorParams* valid_params = |
| reinterpret_cast<ValidateMonitorParams*>(data); |
| base::string16 device_name; |
| bool result = false; |
| if (valid_params->device_name.empty()) { |
| result = monitor == valid_params->monitor; |
| } else if (GetMonitorDeviceName(monitor, &device_name)) { |
| result = device_name == valid_params->device_name; |
| } |
| valid_params->result = result; |
| if (!result) |
| return true; |
| return false; |
| } |
| |
| bool IsValidMonitorOrDeviceName(HMONITOR monitor, const wchar_t* device_name) { |
| ValidateMonitorParams params = {}; |
| params.monitor = monitor; |
| if (device_name) |
| params.device_name = device_name; |
| USERFUNC(EnumDisplayMonitors) |
| (nullptr, nullptr, ValidateMonitorEnumProc, |
| reinterpret_cast<LPARAM>(¶ms)); |
| return params.result; |
| } |
| |
| } // namespace |
| |
| OverrideForTestFunction |
| ProcessMitigationsWin32KLockdownPolicy::override_callback_; |
| |
| bool ProcessMitigationsWin32KLockdownPolicy::GenerateRules( |
| const wchar_t* name, |
| TargetPolicy::Semantics semantics, |
| LowLevelPolicy* policy) { |
| PolicyRule rule(FAKE_SUCCESS); |
| if (!policy->AddRule(IPC_GDI_GDIDLLINITIALIZE_TAG, &rule)) |
| return false; |
| if (!policy->AddRule(IPC_GDI_GETSTOCKOBJECT_TAG, &rule)) |
| return false; |
| if (!policy->AddRule(IPC_USER_REGISTERCLASSW_TAG, &rule)) |
| return false; |
| if (semantics != TargetPolicy::IMPLEMENT_OPM_APIS) |
| return true; |
| if (!policy->AddRule(IPC_USER_ENUMDISPLAYMONITORS_TAG, &rule)) |
| return false; |
| if (!policy->AddRule(IPC_USER_ENUMDISPLAYDEVICES_TAG, &rule)) |
| return false; |
| if (!policy->AddRule(IPC_USER_GETMONITORINFO_TAG, &rule)) |
| return false; |
| if (!policy->AddRule(IPC_GDI_CREATEOPMPROTECTEDOUTPUTS_TAG, &rule)) |
| return false; |
| if (!policy->AddRule(IPC_GDI_GETCERTIFICATE_TAG, &rule)) |
| return false; |
| if (!policy->AddRule(IPC_GDI_GETCERTIFICATESIZE_TAG, &rule)) |
| return false; |
| if (!policy->AddRule(IPC_GDI_DESTROYOPMPROTECTEDOUTPUT_TAG, &rule)) |
| return false; |
| if (!policy->AddRule(IPC_GDI_CONFIGUREOPMPROTECTEDOUTPUT_TAG, &rule)) |
| return false; |
| if (!policy->AddRule(IPC_GDI_GETOPMINFORMATION_TAG, &rule)) |
| return false; |
| if (!policy->AddRule(IPC_GDI_GETOPMRANDOMNUMBER_TAG, &rule)) |
| return false; |
| if (!policy->AddRule(IPC_GDI_GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE_TAG, |
| &rule)) |
| return false; |
| if (!policy->AddRule(IPC_GDI_SETOPMSIGNINGKEYANDSEQUENCENUMBERS_TAG, &rule)) |
| return false; |
| return true; |
| } |
| |
| uint32_t ProcessMitigationsWin32KLockdownPolicy::EnumDisplayMonitorsAction( |
| const ClientInfo& client_info, |
| HMONITOR* monitor_list, |
| uint32_t monitor_list_size) { |
| MonitorListState state = {monitor_list, monitor_list_size, 0}; |
| USERFUNC(EnumDisplayMonitors) |
| (nullptr, nullptr, DisplayMonitorEnumProc, reinterpret_cast<LPARAM>(&state)); |
| return state.monitor_list_pos; |
| } |
| |
| bool ProcessMitigationsWin32KLockdownPolicy::GetMonitorInfoAction( |
| const ClientInfo& client_info, |
| HMONITOR monitor, |
| MONITORINFO* monitor_info_ptr) { |
| if (!IsValidMonitorOrDeviceName(monitor, nullptr)) |
| return false; |
| MONITORINFOEXW monitor_info = {}; |
| monitor_info.cbSize = sizeof(MONITORINFOEXW); |
| |
| bool success = USERFUNC(GetMonitorInfoW)( |
| monitor, reinterpret_cast<MONITORINFO*>(&monitor_info)); |
| if (success) |
| memcpy(monitor_info_ptr, &monitor_info, sizeof(monitor_info)); |
| return success; |
| } |
| |
| NTSTATUS ProcessMitigationsWin32KLockdownPolicy:: |
| GetSuggestedOPMProtectedOutputArraySizeAction( |
| const ClientInfo& client_info, |
| const base::string16& device_name, |
| uint32_t* suggested_array_size) { |
| if (!IsValidMonitorOrDeviceName(nullptr, device_name.c_str())) { |
| return STATUS_ACCESS_DENIED; |
| } |
| UNICODE_STRING unicode_device_name; |
| StringToUnicodeString(&unicode_device_name, device_name); |
| DWORD suggested_array_size_dword = 0; |
| NTSTATUS status = GDIFUNC(GetSuggestedOPMProtectedOutputArraySize)( |
| &unicode_device_name, &suggested_array_size_dword); |
| if (!status) |
| *suggested_array_size = suggested_array_size_dword; |
| return status; |
| } |
| |
| NTSTATUS |
| ProcessMitigationsWin32KLockdownPolicy::CreateOPMProtectedOutputsAction( |
| const ClientInfo& client_info, |
| const base::string16& device_name, |
| HANDLE* protected_outputs, |
| uint32_t array_input_size, |
| uint32_t* array_output_size) { |
| if (!IsValidMonitorOrDeviceName(nullptr, device_name.c_str())) { |
| return STATUS_ACCESS_DENIED; |
| } |
| |
| UNICODE_STRING unicode_device_name; |
| StringToUnicodeString(&unicode_device_name, device_name); |
| DWORD output_size = 0; |
| |
| NTSTATUS status = GDIFUNC(CreateOPMProtectedOutputs)( |
| &unicode_device_name, DXGKMDT_OPM_VOS_OPM_SEMANTICS, array_input_size, |
| &output_size, |
| reinterpret_cast<OPM_PROTECTED_OUTPUT_HANDLE*>(protected_outputs)); |
| if (!status) |
| *array_output_size = output_size; |
| return status; |
| } |
| |
| NTSTATUS ProcessMitigationsWin32KLockdownPolicy::GetCertificateSizeAction( |
| const ClientInfo& client_info, |
| const base::string16& device_name, |
| uint32_t* cert_size) { |
| if (!IsValidMonitorOrDeviceName(nullptr, device_name.c_str())) { |
| return STATUS_ACCESS_DENIED; |
| } |
| UNICODE_STRING unicode_device_name; |
| StringToUnicodeString(&unicode_device_name, device_name); |
| |
| return GDIFUNC(GetCertificateSize)(&unicode_device_name, |
| DXGKMDT_OPM_CERTIFICATE, |
| reinterpret_cast<DWORD*>(cert_size)); |
| } |
| |
| NTSTATUS ProcessMitigationsWin32KLockdownPolicy::GetCertificateAction( |
| const ClientInfo& client_info, |
| const base::string16& device_name, |
| BYTE* cert_data, |
| uint32_t cert_size) { |
| if (!IsValidMonitorOrDeviceName(nullptr, device_name.c_str())) { |
| return STATUS_ACCESS_DENIED; |
| } |
| UNICODE_STRING unicode_device_name; |
| StringToUnicodeString(&unicode_device_name, device_name); |
| |
| return GDIFUNC(GetCertificate)(&unicode_device_name, DXGKMDT_OPM_CERTIFICATE, |
| cert_data, cert_size); |
| } |
| |
| NTSTATUS |
| ProcessMitigationsWin32KLockdownPolicy::GetCertificateSizeByHandleAction( |
| const ClientInfo& client_info, |
| HANDLE protected_output, |
| uint32_t* cert_size) { |
| auto get_certificate_size_func = GDIFUNC(GetCertificateSizeByHandle); |
| if (get_certificate_size_func) { |
| return get_certificate_size_func(protected_output, DXGKMDT_OPM_CERTIFICATE, |
| reinterpret_cast<DWORD*>(cert_size)); |
| } |
| return STATUS_NOT_IMPLEMENTED; |
| } |
| |
| NTSTATUS ProcessMitigationsWin32KLockdownPolicy::GetCertificateByHandleAction( |
| const ClientInfo& client_info, |
| HANDLE protected_output, |
| BYTE* cert_data, |
| uint32_t cert_size) { |
| auto get_certificate_func = GDIFUNC(GetCertificateByHandle); |
| if (get_certificate_func) { |
| return get_certificate_func(protected_output, DXGKMDT_OPM_CERTIFICATE, |
| cert_data, cert_size); |
| } |
| return STATUS_NOT_IMPLEMENTED; |
| } |
| |
| NTSTATUS ProcessMitigationsWin32KLockdownPolicy::GetOPMRandomNumberAction( |
| const ClientInfo& client_info, |
| HANDLE protected_output, |
| void* random_number) { |
| return GDIFUNC(GetOPMRandomNumber)( |
| protected_output, static_cast<DXGKMDT_OPM_RANDOM_NUMBER*>(random_number)); |
| } |
| |
| NTSTATUS ProcessMitigationsWin32KLockdownPolicy:: |
| SetOPMSigningKeyAndSequenceNumbersAction(const ClientInfo& client_info, |
| HANDLE protected_output, |
| void* parameters) { |
| return GDIFUNC(SetOPMSigningKeyAndSequenceNumbers)( |
| protected_output, |
| static_cast<DXGKMDT_OPM_ENCRYPTED_PARAMETERS*>(parameters)); |
| } |
| |
| NTSTATUS |
| ProcessMitigationsWin32KLockdownPolicy::ConfigureOPMProtectedOutputAction( |
| const ClientInfo& client_info, |
| HANDLE protected_output, |
| void* parameters_ptr) { |
| DXGKMDT_OPM_CONFIGURE_PARAMETERS parameters; |
| memcpy(¶meters, parameters_ptr, sizeof(parameters)); |
| if (parameters.guidSetting != DXGKMDT_OPM_SET_PROTECTION_LEVEL || |
| parameters.cbParametersSize != |
| sizeof(DXGKMDT_OPM_SET_PROTECTION_LEVEL_PARAMETERS)) { |
| return STATUS_INVALID_PARAMETER; |
| } |
| |
| DXGKMDT_OPM_SET_PROTECTION_LEVEL_PARAMETERS prot_level; |
| memcpy(&prot_level, parameters.abParameters, sizeof(prot_level)); |
| if (prot_level.Reserved || prot_level.Reserved2) |
| return STATUS_INVALID_PARAMETER; |
| |
| if (prot_level.ulProtectionType != DXGKMDT_OPM_PROTECTION_TYPE_HDCP && |
| prot_level.ulProtectionType != DXGKMDT_OPM_PROTECTION_TYPE_DPCP) { |
| return STATUS_INVALID_PARAMETER; |
| } |
| |
| // Protection levels are same for HDCP and DPCP. |
| if (prot_level.ulProtectionLevel != DXGKMDT_OPM_HDCP_OFF && |
| prot_level.ulProtectionLevel != DXGKMDT_OPM_HDCP_ON) { |
| return STATUS_INVALID_PARAMETER; |
| } |
| |
| return GDIFUNC(ConfigureOPMProtectedOutput)(protected_output, ¶meters, 0, |
| nullptr); |
| } |
| |
| NTSTATUS ProcessMitigationsWin32KLockdownPolicy::GetOPMInformationAction( |
| const ClientInfo& client_info, |
| HANDLE protected_output, |
| void* parameters_ptr, |
| void* requested_info_ptr) { |
| DXGKMDT_OPM_GET_INFO_PARAMETERS parameters; |
| memcpy(¶meters, parameters_ptr, sizeof(parameters)); |
| |
| bool valid_parameters = false; |
| // Validate sizes based on the type being requested. |
| if ((parameters.guidInformation == DXGKMDT_OPM_GET_CONNECTOR_TYPE || |
| parameters.guidInformation == |
| DXGKMDT_OPM_GET_SUPPORTED_PROTECTION_TYPES) && |
| parameters.cbParametersSize == 0) { |
| valid_parameters = true; |
| } else if ((parameters.guidInformation == |
| DXGKMDT_OPM_GET_VIRTUAL_PROTECTION_LEVEL || |
| parameters.guidInformation == |
| DXGKMDT_OPM_GET_ACTUAL_PROTECTION_LEVEL) && |
| parameters.cbParametersSize == sizeof(uint32_t)) { |
| uint32_t param_value; |
| memcpy(¶m_value, parameters.abParameters, sizeof(param_value)); |
| if (param_value == DXGKMDT_OPM_PROTECTION_TYPE_HDCP || |
| param_value == DXGKMDT_OPM_PROTECTION_TYPE_DPCP) { |
| valid_parameters = true; |
| } |
| } |
| if (!valid_parameters) |
| return STATUS_INVALID_PARAMETER; |
| DXGKMDT_OPM_REQUESTED_INFORMATION requested_info = {}; |
| NTSTATUS status = GDIFUNC(GetOPMInformation)(protected_output, ¶meters, |
| &requested_info); |
| if (!status) |
| memcpy(requested_info_ptr, &requested_info, sizeof(requested_info)); |
| |
| return status; |
| } |
| |
| NTSTATUS |
| ProcessMitigationsWin32KLockdownPolicy::DestroyOPMProtectedOutputAction( |
| HANDLE protected_output) { |
| return GDIFUNC(DestroyOPMProtectedOutput)(protected_output); |
| } |
| |
| void ProcessMitigationsWin32KLockdownPolicy::SetOverrideForTestCallback( |
| OverrideForTestFunction callback) { |
| override_callback_ = callback; |
| } |
| |
| OverrideForTestFunction |
| ProcessMitigationsWin32KLockdownPolicy::GetOverrideForTestCallback() { |
| return override_callback_; |
| } |
| |
| } // namespace sandbox |