Index: sandbox/win/tests/integration_tests/cfi_unittest_exe.cc |
diff --git a/sandbox/win/tests/integration_tests/cfi_unittest_exe.cc b/sandbox/win/tests/integration_tests/cfi_unittest_exe.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c4c1e40b5accacf453c684e7f36964b03f79b275 |
--- /dev/null |
+++ b/sandbox/win/tests/integration_tests/cfi_unittest_exe.cc |
@@ -0,0 +1,295 @@ |
+// Copyright 2017 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 <windows.h> |
+ |
+#include "base/files/file_path.h" |
+#include "base/scoped_native_library.h" |
+ |
+namespace { |
+ |
+//------------------------------------------------------------------------------ |
+// PRIVATE: Control Flow Guard test things |
+// - MS binary indirect call tests. |
+//------------------------------------------------------------------------------ |
+ |
+// These structures hold chunks of binary. No padding wanted. |
+#pragma pack(push, 1) |
+ |
+#if defined(_WIN64) |
+ |
+const USHORT kCmpRsi = 0x3B48; |
+const BYTE kRax = 0xF0; |
+const USHORT kJzLoc = 0x0074; |
+const ULONG kMovRcxRsiCall = 0xFFCE8B48; |
+const BYTE kPadding = 0x00; |
+const ULONG kMovEcxEbxCallRsi = 0xD6FFCB8B; |
+ |
+const ULONG kMovRcxRsiAdd = 0x48CE8B48; |
+const ULONG kRcx10hNop = 0x9010C183; |
+ |
+// Sequence of bytes in GetSystemMetrics. |
+struct Signature { |
+ // This struct contains roughly the following code: |
+ // cmp rsi, rax |
+ // jz jmp_addr |
+ // mov rcx, rsi |
+ // call ULONG_addr <-- CFG check function |
+ // mov ecx, ebx |
+ // call rsi <-- Actual call to GetSystemMetrics |
+ |
+ // Patch will start here and be 8 bytes. |
+ USHORT cmp_rsi; // = 48 3B |
+ BYTE rax; // = F0 |
+ USHORT jz_loc; // = 74 XX |
+ ULONG mov_rcx_rsi_call; // = 48 8B CE FF |
+ ULONG guard_check_icall_fptr; // = XX XX XX XX |
+ BYTE padding; // = 00 |
+ ULONG mov_ecx_ebx_call_rsi; // = 8B CB FF D6 |
+}; |
+ |
+struct Patch { |
+ // Just add 16 to the existing function address. |
+ // This ensures a 16-byte aligned address that is |
+ // definitely not a valid function address. |
+ // mov rcx, rsi = 48 4B CE |
+ // add rcx, 10h = 48 83 C1 10 |
+ // nop = 90 |
+ ULONG first_four_bytes; // = 48 4B CE 48 |
+ ULONG second_four_bytes; // = 83 C1 10 90 |
+}; |
+ |
+#else // x86 |
+ |
+const USHORT kCmpEbx = 0x0081; |
+const USHORT kJzLoc = 0x0074; |
+const ULONG kMovEcxEbxCall = 0x15FFCB8B; |
+const USHORT kCallEbx = 0xD3FF; |
+ |
+const ULONG kMovEcxEbxAddEcx = 0xC183CB8B; |
+const ULONG k16Nops = 0x90909010; |
+const USHORT kTwoNops = 0x9090; |
+ |
+// Sequence of bytes in GetSystemMetrics, x86. |
+struct Signature { |
+ // This struct contains roughly the following code: |
+ // cmp ebx, offset |
+ // jz jmp_addr |
+ // mov ecx, ebx |
+ // call ULONG_addr <-- CFG check function |
+ // call ebx <-- Actual call to GetSystemMetrics |
+ |
+ // Patch will start here and be 10 bytes. |
+ USHORT cmp_ebx; // = 81 XX |
+ ULONG addr; // = XX XX XX XX |
+ USHORT jz_loc; // = 74 XX |
+ ULONG mov_ecx_ebx_call; // = 8B CB FF 15 |
+ ULONG guard_check_icall_fptr; // = XX XX XX XX |
+ USHORT call_ebx; // = FF D3 |
+}; |
+ |
+struct Patch { |
+ // Just add 16 to the existing function address. |
+ // This ensures a 16-byte aligned address that is |
+ // definitely not a valid function address. |
+ // mov ecx, ebx = 8B CB |
+ // add ecx, 10h = 83 C1 10 90 |
+ // nop = 90 90 90 90 |
+ ULONG first_four_bytes; // = 8B CB 83 C1 |
+ ULONG second_four_bytes; // = 10 90 90 90 |
+ USHORT last_two_bytes; // = 90 90 |
+}; |
+ |
+#endif // _WIN64 |
+ |
+#pragma pack(pop) |
+//------------------------------------------------------------------------------ |
+ |
+// - Search binary starting at |address_start| for a matching chunk of |
+// |binary_to_find|, of size |size_to_match|. |
+// - A byte of value 0 in |binary_to_find|, is a wildcard for anything. |
+// - Give a |max_distance| to find the chunk within, before failing. |
+// - If return value is true, |out_offset| will hold the offset from |
+// |address_start| that the match starts. |
+bool FindBinary(BYTE* binary_to_find, |
+ DWORD size_to_match, |
+ BYTE* address_start, |
+ DWORD max_distance, |
+ DWORD* out_offset) { |
+ assert(size_to_match <= max_distance); |
+ assert(size_to_match > 0); |
+ |
+ BYTE* max_byte = address_start + max_distance - size_to_match; |
+ BYTE* temp_ptr = address_start; |
+ // Yes, it's a double while loop. |
+ while (temp_ptr <= max_byte) { |
+ size_t i = 0; |
+ // 0 is a wildcard match. |
+ while (binary_to_find[i] == 0 || temp_ptr[i] == binary_to_find[i]) { |
+ // Check if this is the last byte. |
+ if (i == size_to_match - 1) { |
+ *out_offset = temp_ptr - address_start; |
+ return true; |
+ } |
+ ++i; |
+ } |
+ ++temp_ptr; |
+ } |
+ |
+ return false; |
+} |
+ |
+// - Will write patch starting at |start_addr| with the patch for x86 |
+// or x64. |
+// - This function is only for writes within this process. |
+bool DoPatch(BYTE* start_addr) { |
+ Patch patch = {}; |
+#if defined(_WIN64) |
+ patch.first_four_bytes = kMovRcxRsiAdd; |
+ patch.second_four_bytes = kRcx10hNop; |
+#else // x86 |
+ patch.first_four_bytes = kMovEcxEbxAddEcx; |
+ patch.second_four_bytes = k16Nops; |
+ patch.last_two_bytes = kTwoNops; |
+#endif // _WIN64 |
+ |
+ DWORD old_protection; |
+ // PAGE_WRITECOPY explicitly allows "copy-on-write" behaviour for |
+ // system DLL patches. |
+ if (!::VirtualProtect(start_addr, sizeof(patch), PAGE_WRITECOPY, |
+ &old_protection)) |
+ return false; |
+ |
+ ::memcpy(start_addr, &patch, sizeof(patch)); |
+ ::VirtualProtect(start_addr, sizeof(patch), old_protection, &old_protection); |
+ |
+ return true; |
+} |
+ |
+// - Find the offset from |start| that the x86 or x64 signature starts. |
+bool FindSignature(BYTE* start, DWORD* offset_found) { |
+ Signature signature = {}; |
+#if defined(_WIN64) |
+ signature.cmp_rsi = kCmpRsi; |
+ signature.rax = kRax; |
+ signature.jz_loc = kJzLoc; |
+ signature.mov_rcx_rsi_call = kMovRcxRsiCall; |
+ signature.padding = kPadding; |
+ signature.mov_ecx_ebx_call_rsi = kMovEcxEbxCallRsi; |
+ |
+ // This is far enough into GetSystemMetrics that the signature should |
+ // have been found. See disassembly. |
+ DWORD max_area = 0xB0; |
+#else // x86 |
+ signature.cmp_ebx = kCmpEbx; |
+ signature.jz_loc = kJzLoc; |
+ signature.mov_ecx_ebx_call = kMovEcxEbxCall; |
+ signature.call_ebx = kCallEbx; |
+ |
+ // This is far enough into GetSystemMetrics x86 that the signature should |
+ // have been found. See disassembly. |
+ DWORD max_area = 0xD9; |
+#endif // _WIN64 |
+ if (!FindBinary(reinterpret_cast<BYTE*>(&signature), sizeof(signature), start, |
+ max_area, offset_found)) |
+ return false; |
+ |
+ return true; |
+} |
+ |
+// - This function tests for CFG in MS system binaries, in process. |
+// |
+// A few words about the patching for this test: |
+// |
+// - In both x86 and x64, the function FindSignature() will scan |
+// GetSystemMetrics in user32.dll, to find the ideal place chosen |
+// for this test. It's a spot where a CFG check was compiled into |
+// a Microsoft system DLL. For more visualization, open user32.dll |
+// in IDA to follow along (especially if planning to change this test). |
+// |
+// - The CFG security check basically calls __guard_check_icall_fptr, with |
+// the function address about to be called as the argument in EAX/RAX reg. |
+// If the address is not in the process' "valid indirect call bitmap", a |
+// CFG exception will be thrown. |
+// |
+// - The DoPatch() function then overwrites with a small, custom change. This |
+// change will simply add 16 (0x10) to the real function address in EAX/RAX |
+// about to be checked. This will maintain the 16-byte alignment required in |
+// a target address by CFG, but also ensure that it fails the check. |
+// |
+// The whole purpose of this unittest is to ensure that a failed CFG check in |
+// a Microsoft binary results in an exception. If CFG is not properly |
+// enabled for a process, no exception will be thrown. |
+// This test EXE is built with |
+// configs += [ "//build/config/win:win_msvc_cfg" ] |
+// which should result in CFG enabled on the process. |
+// |
+// - The patches (x86 or x64) were carefully constructed to be valid and not |
+// mess up the executing instructions. Need to ensure that the CFG check |
+// fully happens, and that nothing else goes wrong before OR AFTER that |
+// point. The only exception expected is a very intentional one. |
+// **The patches also allow the call to GetSystemMetrics to SUCCEED if CFG is |
+// NOT enabled for the process! This makes for very clear behaviour. |
+void TestMsIndirect() { |
+ base::ScopedNativeLibrary user32(base::FilePath(L"user32.dll")); |
+ if (!user32.is_valid()) |
+ _exit(1); |
+ |
+ using GetSystemMetricsFunction = decltype(&::GetSystemMetrics); |
+ GetSystemMetricsFunction get_system_metrics = |
+ reinterpret_cast<GetSystemMetricsFunction>( |
+ user32.GetFunctionPointer("GetSystemMetrics")); |
+ if (!get_system_metrics) |
+ _exit(2); |
+ |
+ // Sanity check the function works fine pre-patch. Tests should only be |
+ // running from normal boot (0). |
+ if (0 != get_system_metrics(SM_CLEANBOOT)) |
+ _exit(3); |
+ |
+ BYTE* target = reinterpret_cast<BYTE*>(get_system_metrics); |
+ DWORD offset = 0; |
+ if (!FindSignature(target, &offset)) |
+ _exit(4); |
+ |
+ // Now patch the function. Don't bother saving original code, |
+ // as this process will end very soon. |
+ if (!DoPatch(target + offset)) |
+ _exit(5); |
+ |
+ // Call the patched function! |
+ get_system_metrics(SM_CLEANBOOT); |
+} |
+ |
+} // namespace |
+ |
+//------------------------------------------------------------------------------ |
+// PUBLIC |
+//------------------------------------------------------------------------------ |
+ |
+// Good ol' main. |
+// - Exe exits with non-zero return codes for unexpected errors. |
+// - Return code of zero indicates no issues at all. |
+// - Else, a CFG exception will result in the process being destroyed. |
+int main(int argc, char** argv) { |
+ if (argc != 2) |
+ _exit(-1); |
+ |
+ const char* arg = argv[1]; |
+ |
+ int iarg = ::atoi(arg); |
+ if (!iarg) |
+ _exit(-1); |
+ |
+ switch (iarg) { |
+ // kSysDllTest |
+ case 1: |
+ TestMsIndirect(); |
+ break; |
+ // Unsupported argument. |
+ default: |
+ _exit(-1); |
+ } |
+ |
+ return 0; |
+} |