Skip to content

Commit

Permalink
[libwin32common] secoptions_init(): Added a high-security mode.
Browse files Browse the repository at this point in the history
Setting high-security mode enables the following mitigations:

- Disable direct Win32k system call access. This breaks NTUser/GDI,
  so we can't set it in GUI applications.

- Disable loading non-system fonts.

FIXME: On Win10 LTSC 1809, the system call disable policy is failing
with ERROR_WRITE_PROTECT...

Removed the union of PROCESS_MITIGATION_* structs and use individual
zero-initialized structs, similar to Chromium's process_mitigations.cc.

Control Flow Guard: Only set this if _CONTROL_FLOW_GUARD is defined.
MSVC defines this macro when compiling with /guard:cf. If not compiling
with /guard:cf, enabling Control Flow Guard will cause the program to
crash.

Delay-load shell32.dll in some places. This improves performance with
command-line programs that don't need GDI, and it prevents GDI from
interfering with system call disabling (once I get that working).

References:
- https://msdn.microsoft.com/en-us/library/bb430720.aspx
- https://chromium.googlesource.com/chromium/src/+/441d852dbcb7b9b31328393c7e31562b1e268399/sandbox/win/src/process_mitigations.cc
- https://chromium.googlesource.com/chromium/src/+/refs/heads/master/sandbox/win/src/process_mitigations.cc
- https://github.com/chromium/chromium/blob/master/sandbox/win/src/process_mitigations.cc
  • Loading branch information
GerbilSoft committed Jan 17, 2020
1 parent c71f977 commit 00aac94
Show file tree
Hide file tree
Showing 12 changed files with 111 additions and 81 deletions.
7 changes: 7 additions & 0 deletions src/libcachecommon/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,10 @@ TARGET_LINK_LIBRARIES(FilterCacheKeyTest PRIVATE gtest)
DO_SPLIT_DEBUG(FilterCacheKeyTest)
SET_WINDOWS_SUBSYSTEM(FilterCacheKeyTest CONSOLE)
ADD_TEST(NAME FilterCacheKeyTest COMMAND FilterCacheKeyTest)

# Delay-load shell32.dll to prevent a performance penalty due to gdi32.dll.
# Reference: https://randomascii.wordpress.com/2018/12/03/a-not-called-function-can-cause-a-5x-slowdown/
# This is also needed when disabling direct Win32k syscalls,
# since loading gdi32.dll will crash in that case.
INCLUDE(../../libwin32common/DelayLoadHelper.cmake)
ADD_DELAYLOAD_FLAGS(FilterCacheKeyTest shell32.dll)
6 changes: 6 additions & 0 deletions src/libromdata/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,9 @@ TARGET_COMPILE_DEFINITIONS(SuperMagicDriveTest PRIVATE ${ZLIB_DEFINITIONS})
DO_SPLIT_DEBUG(SuperMagicDriveTest)
SET_WINDOWS_SUBSYSTEM(SuperMagicDriveTest CONSOLE)
ADD_TEST(NAME SuperMagicDriveTest COMMAND SuperMagicDriveTest "--gtest_filter=-*benchmark*")

# Delay-load shell32.dll to prevent a performance penalty due to gdi32.dll.
# Reference: https://randomascii.wordpress.com/2018/12/03/a-not-called-function-can-cause-a-5x-slowdown/
# This is also needed when disabling direct Win32k syscalls,
# since loading gdi32.dll will crash in that case.
ADD_DELAYLOAD_FLAGS(SuperMagicDriveTest shell32.dll)
2 changes: 1 addition & 1 deletion src/libromdata/tests/disc/GcnFstPrint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ int RP_C_API main(int argc, char *argv[])
{
#ifdef _WIN32
// Set Win32 security options.
secoptions_init();
secoptions_init(TRUE);
#endif /* _WIN32 */

// Set the C and C++ locales.
Expand Down
2 changes: 1 addition & 1 deletion src/librpbase/tests/gtest_init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ int RP_C_API main(int argc, char *argv[])
{
#ifdef _WIN32
// Set Win32 security options.
secoptions_init();
secoptions_init(FALSE);

// Register RpGdiplusBackend.
// TODO: Static initializer somewhere?
Expand Down
149 changes: 76 additions & 73 deletions src/libwin32common/secoptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,19 @@ typedef BOOL (WINAPI *PFNSETPROCESSMITIGATIONPOLICY)(_In_ PROCESS_MITIGATION_POL
#endif /* !INLINE */

/**
* libromdata Windows executable initialization.
* rom-properties Windows executable initialization.
* This sets various security options.
* Reference: http://msdn.microsoft.com/en-us/library/bb430720.aspx
* References:
* - https://msdn.microsoft.com/en-us/library/bb430720.aspx
* - https://chromium.googlesource.com/chromium/src/+/441d852dbcb7b9b31328393c7e31562b1e268399/sandbox/win/src/process_mitigations.cc
* - https://chromium.googlesource.com/chromium/src/+/refs/heads/master/sandbox/win/src/process_mitigations.cc
* - https://github.com/chromium/chromium/blob/master/sandbox/win/src/process_mitigations.cc
*
* @param bHighSec If non-zero, enable high security for unprivileged processes.
* @return 0 on success; non-zero on error.
*/
static INLINE int secoptions_init(void)
static INLINE int secoptions_init(BOOL bHighSec)
{
BOOL bHighSec = FALSE; // TODO: Move back to a parameter.
HMODULE hKernel32;
PFNSETPROCESSMITIGATIONPOLICY pfnSetProcessMitigationPolicy;
PFNSETDLLDIRECTORYW pfnSetDllDirectoryW;
Expand All @@ -108,86 +111,81 @@ static INLINE int secoptions_init(void)
// If available, it supercedes many of these.
pfnSetProcessMitigationPolicy = (PFNSETPROCESSMITIGATIONPOLICY)GetProcAddress(hKernel32, "SetProcessMitigationPolicy");
if (pfnSetProcessMitigationPolicy) {
union {
// TODO: Some of these need further investigation.
DWORD flags;
PROCESS_MITIGATION_DEP_POLICY dep;
PROCESS_MITIGATION_ASLR_POLICY aslr;
PROCESS_MITIGATION_DYNAMIC_CODE_POLICY dynamic_code;
PROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY strict_handle_check;
PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY system_call_disable;
PROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY extension_point_disable;
PROCESS_MITIGATION_CONTROL_FLOW_GUARD_POLICY control_flow_guard; // MSVC 2015+: /guard:cf
//PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY binary_signature;
PROCESS_MITIGATION_FONT_DISABLE_POLICY font_disable;
PROCESS_MITIGATION_IMAGE_LOAD_POLICY image_load;
} policy;
// Most of these are 4 bytes, except for
// PROCESS_MITIGATION_DEP_POLICY, which is 8.
static_assert(sizeof(policy) == 8, "sizeof(policy) != 8");

// Set DEP policy.
policy.flags = 0;
policy.dep.Enable = 1;
policy.dep.DisableAtlThunkEmulation = 1;
policy.dep.Permanent = TRUE;
pfnSetProcessMitigationPolicy(ProcessDEPPolicy,
&policy.dep, sizeof(policy.dep));
{
PROCESS_MITIGATION_DEP_POLICY dep = { 0 };
dep.Enable = TRUE;
dep.DisableAtlThunkEmulation = TRUE;
dep.Permanent = TRUE;
pfnSetProcessMitigationPolicy(ProcessDEPPolicy, &dep, sizeof(dep));
}

// Set ASLR policy.
policy.flags = 0;
policy.aslr.EnableBottomUpRandomization = 1;
policy.aslr.EnableForceRelocateImages = 1;
policy.aslr.EnableHighEntropy = 1;
policy.aslr.DisallowStrippedImages = 1;
pfnSetProcessMitigationPolicy(ProcessASLRPolicy,
&policy.aslr, sizeof(policy.aslr));
{
PROCESS_MITIGATION_ASLR_POLICY aslr = { 0 };
aslr.EnableBottomUpRandomization = TRUE;
aslr.EnableForceRelocateImages = TRUE;
aslr.EnableHighEntropy = TRUE;
aslr.DisallowStrippedImages = TRUE;
pfnSetProcessMitigationPolicy(ProcessASLRPolicy, &aslr, sizeof(aslr));
}

// Set dynamic code policy.
policy.flags = 0;
policy.dynamic_code.ProhibitDynamicCode = 1;
{
PROCESS_MITIGATION_DYNAMIC_CODE_POLICY dynamic_code = { 0 };
dynamic_code.ProhibitDynamicCode = TRUE;
#if 0
// Added in Windows 10.0.14393 (v1607)
// TODO: Figure out how to detect the SDK build version.
policy.dynamic_code.AllowThreadOptOut = 0; // Win10
policy.dynamic_code.AllowRemoteDowngrade = 0; // Win10
// Added in Windows 10.0.14393 (v1607)
// TODO: Figure out how to detect the SDK build version.
dynamic_code.AllowThreadOptOut = 0; // Win10
dynamic_code.AllowRemoteDowngrade = 0; // Win10
#endif
pfnSetProcessMitigationPolicy(ProcessDynamicCodePolicy,
&policy.dynamic_code, sizeof(policy.dynamic_code));
pfnSetProcessMitigationPolicy(ProcessDynamicCodePolicy,
&dynamic_code, sizeof(dynamic_code));
}

// Set strict handle check policy.
policy.flags = 0;
policy.strict_handle_check.RaiseExceptionOnInvalidHandleReference = 1;
policy.strict_handle_check.HandleExceptionsPermanentlyEnabled = 1;
pfnSetProcessMitigationPolicy(ProcessStrictHandleCheckPolicy,
&policy.strict_handle_check, sizeof(policy.strict_handle_check));
{
PROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY strict_handle_check = { 0 };
strict_handle_check.RaiseExceptionOnInvalidHandleReference = TRUE;
strict_handle_check.HandleExceptionsPermanentlyEnabled = TRUE;
pfnSetProcessMitigationPolicy(ProcessStrictHandleCheckPolicy,
&strict_handle_check, sizeof(strict_handle_check));
}

// Set extension point disable policy.
// Extension point DLLs are some weird MFC-specific thing.
// https://msdn.microsoft.com/en-us/library/h5f7ck28.aspx
policy.flags = 0;
policy.extension_point_disable.DisableExtensionPoints = 1;
pfnSetProcessMitigationPolicy(ProcessExtensionPointDisablePolicy,
&policy.extension_point_disable, sizeof(policy.extension_point_disable));
{
PROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY extension_point_disable = { 0 };
extension_point_disable.DisableExtensionPoints = TRUE;
pfnSetProcessMitigationPolicy(ProcessExtensionPointDisablePolicy,
&extension_point_disable, sizeof(extension_point_disable));
}

// Set image load policy.
policy.flags = 0;
policy.image_load.NoRemoteImages = 0; // TODO
policy.image_load.NoLowMandatoryLabelImages = 1;
policy.image_load.PreferSystem32Images = 1;
pfnSetProcessMitigationPolicy(ProcessImageLoadPolicy,
&policy.image_load, sizeof(policy.image_load));
{
PROCESS_MITIGATION_IMAGE_LOAD_POLICY image_load = { 0 };
image_load.NoRemoteImages = 0; // TODO
image_load.NoLowMandatoryLabelImages = 1;
image_load.PreferSystem32Images = 1;
pfnSetProcessMitigationPolicy(ProcessImageLoadPolicy,
&image_load, sizeof(image_load));
}

#if defined(_MSC_VER) && _MSC_VER >= 1900
#if defined(_MSC_VER) && _MSC_VER >= 1900 && defined(_CONTROL_FLOW_GUARD)
// Set control flow guard policy.
// Requires MSVC 2015+ and /guard:cf.
// TODO: Enable export suppression? May not be available on
// certain Windows versions, so if we enable it, fall back
// to not-enabled if it didn't work.
policy.flags = 0;
policy.control_flow_guard.EnableControlFlowGuard = 1;
pfnSetProcessMitigationPolicy(ProcessControlFlowGuardPolicy,
&policy.control_flow_guard, sizeof(policy.control_flow_guard));
#endif /* defined(_MSC_VER) && _MSC_VER >= 1900 */
{
PROCESS_MITIGATION_CONTROL_FLOW_GUARD_POLICY control_flow_guard = { 0 };
control_flow_guard.EnableControlFlowGuard = TRUE;
pfnSetProcessMitigationPolicy(ProcessControlFlowGuardPolicy,
&control_flow_guard, sizeof(control_flow_guard));
}
#endif /* defined(_MSC_VER) && _MSC_VER >= 1900 && defined(_CONTROL_FLOW_GUARD) */

if (bHighSec) {
// High-security options that are useful for
Expand All @@ -196,17 +194,22 @@ static INLINE int secoptions_init(void)
// Disable direct Win32k system call access.
// This prevents direct access to NTUser/GDI system calls.
// This is NOT usable in GUI applications.
policy.flags = 0;
policy.system_call_disable.DisallowWin32kSystemCalls = 1;
pfnSetProcessMitigationPolicy(ProcessSystemCallDisablePolicy,
&policy.system_call_disable, sizeof(policy.system_call_disable));
// FIXME: On Win10 LTSC 1809, this is failing with ERROR_WRITE_PROTECT...
{
PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY system_call_disable = { 0 };
system_call_disable.DisallowWin32kSystemCalls = TRUE;
pfnSetProcessMitigationPolicy(ProcessSystemCallDisablePolicy,
&system_call_disable, sizeof(system_call_disable));
}

// Disable loading non-system fonts.
policy.flags = 0;
policy.font_disable.DisableNonSystemFonts = 1;
policy.font_disable.AuditNonSystemFontLoading = 0;
pfnSetProcessMitigationPolicy(ProcessFontDisablePolicy,
&policy.font_disable, sizeof(policy.font_disable));
{
PROCESS_MITIGATION_FONT_DISABLE_POLICY font_disable = { 0 };
font_disable.DisableNonSystemFonts = TRUE;
font_disable.AuditNonSystemFontLoading = FALSE;
pfnSetProcessMitigationPolicy(ProcessFontDisablePolicy,
&font_disable, sizeof(font_disable));
}
}
} else {
// Use the old functions if they're available.
Expand Down
6 changes: 4 additions & 2 deletions src/libwin32common/wmain.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ int RP_C_API wmain(int argc, wchar_t *argv[])
char **u8argv;
int i, ret;

// Set Win32 security options.
secoptions_init();
// Win32 security options will be set by main(), since
// some programs will want to enable high-security mode
// while others won't.
//secoptions_init(FALSE);

// Convert the UTF-16 arguments to UTF-8.
// NOTE: Using WideCharToMultiByte() directly in order to
Expand Down
6 changes: 6 additions & 0 deletions src/rp-download/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ TARGET_INCLUDE_DIRECTORIES(rp-download
IF(WIN32)
TARGET_LINK_LIBRARIES(rp-download PRIVATE win32common)
TARGET_LINK_LIBRARIES(rp-download PRIVATE wininet advapi32)
# Delay-load shell32.dll to prevent a performance penalty due to gdi32.dll.
# Reference: https://randomascii.wordpress.com/2018/12/03/a-not-called-function-can-cause-a-5x-slowdown/
# This is also needed when disabling direct Win32k syscalls,
# since loading gdi32.dll will crash in that case.
INCLUDE(../libwin32common/DelayLoadHelper.cmake)
ADD_DELAYLOAD_FLAGS(rp-download shell32.dll)
ELSE(WIN32)
TARGET_LINK_LIBRARIES(rp-download PRIVATE ${CURL_LIBRARIES})
ENDIF(WIN32)
Expand Down
2 changes: 1 addition & 1 deletion src/rp-download/os-secure_win32.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ int rp_download_os_secure(void)
IntegrityLevel level;

// Set Win32 security options.
secoptions_init();
secoptions_init(TRUE);

// Check the process integrity level.
// TODO: If it's higher than low, relaunch the program with low integrity if supported.
Expand Down
6 changes: 6 additions & 0 deletions src/rpcli/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ IF(MINGW)
SET(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -I \"${CMAKE_CURRENT_BINARY_DIR}\"")
ENDIF(MINGW)

# Delay-load shell32.dll to prevent a performance penalty due to gdi32.dll.
# Reference: https://randomascii.wordpress.com/2018/12/03/a-not-called-function-can-cause-a-5x-slowdown/
# This is also needed when disabling direct Win32k syscalls,
# since loading gdi32.dll will crash in that case.
# FIXME: gdiplus.dll isn't linked in, though I'm pretty sure it's used...
ADD_DELAYLOAD_FLAGS(rpcli shell32.dll)
IF(MSVC)
# Extended delay-load functionality.
TARGET_LINK_LIBRARIES(rpcli PRIVATE delayimp)
Expand Down
2 changes: 1 addition & 1 deletion src/rpcli/rpcli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ int RP_C_API main(int argc, char *argv[])
{
#ifdef _WIN32
// Set Win32 security options.
secoptions_init();
secoptions_init(FALSE);
#endif /* _WIN32 */

// Set the C and C++ locales.
Expand Down
2 changes: 1 addition & 1 deletion src/svrplus/svrplus.c
Original file line number Diff line number Diff line change
Expand Up @@ -864,7 +864,7 @@ int CALLBACK wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
((void)hPrevInstance);

// Set Win32 security options.
secoptions_init();
secoptions_init(FALSE);

// Check if another instance of svrplus is already running.
// References:
Expand Down
2 changes: 1 addition & 1 deletion src/win32/config/rp-config.c
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ int CALLBACK WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
RP_UNUSED(hPrevInstance);

// Set Win32 security options.
secoptions_init();
secoptions_init(TRUE);

// Check if another instance of rp-config is already running.
// References:
Expand Down

0 comments on commit 00aac94

Please sign in to comment.