Chromium Code Reviews| Index: sandbox/win/src/cfi_unittest_exe.cc |
| diff --git a/sandbox/win/src/cfi_unittest_exe.cc b/sandbox/win/src/cfi_unittest_exe.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..61e781ae02b769b10944d7911156ae64d88c3449 |
| --- /dev/null |
| +++ b/sandbox/win/src/cfi_unittest_exe.cc |
| @@ -0,0 +1,264 @@ |
| +// 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) |
| + |
| +// Tests don't run in a wow64 environment, so fine to compile based on ifdef. |
|
Will Harris
2017/02/06 22:33:36
I'm not sure I understand this comment. We have 32
penny
2017/02/07 23:12:56
Done.
|
| +#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. |
| +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; |
| +} |