Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(21)

Side by Side Diff: sandbox/win/src/process_mitigations_extensionpoints_unittest.cc

Issue 2944493002: [Windows Sandbox Tests] Process Mitigations. (Closed)
Patch Set: Code review fixes, part 1. Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "sandbox/win/src/process_mitigations.h"
6
7 #include <windows.h>
8
9 #include <psapi.h>
10
11 #include "base/scoped_native_library.h"
12 #include "base/win/registry.h"
13 #include "base/win/startup_information.h"
14 #include "base/win/win_util.h"
15 #include "base/win/windows_version.h"
16 #include "sandbox/win/tests/common/controller.h"
17 #include "sandbox/win/tests/integration_tests/hooking_dll.h"
18 #include "sandbox/win/tests/integration_tests/hooking_win_proc.h"
19 #include "sandbox/win/tests/integration_tests/integration_tests_common.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21
22 namespace {
23
24 //------------------------------------------------------------------------------
25 // Internal Defines & Functions
26 //------------------------------------------------------------------------------
27
28 using WasHookCalledFunction = decltype(&hooking_dll::WasHookCalled);
29 using SetHookFunction = decltype(&hooking_dll::SetHook);
30
31 const wchar_t g_extension_point_test_mutex[] = L"ChromeExtensionTestMutex";
grt (UTC plus 2) 2017/06/28 12:16:55 nit: use constexpr throughout where possible
32 const wchar_t g_hook_dll_file[] = L"sbox_integration_test_hook_dll.dll";
33
34 //------------------------------------------------------------------------------
35 // ExtensionPoint test helper function.
36 //
37 // Spawn Windows process (with or without mitigation enabled).
38 //------------------------------------------------------------------------------
39 bool SpawnWinProc(PROCESS_INFORMATION* pi, bool success_test, HANDLE* event) {
40 base::win::StartupInformation startup_info;
41 DWORD creation_flags = 0;
42
43 if (!success_test) {
44 DWORD64 flags =
45 PROCESS_CREATION_MITIGATION_POLICY_EXTENSION_POINT_DISABLE_ALWAYS_ON;
46 // This test only runs on >= Win8, so don't have to handle
47 // illegal 64-bit flags on 32-bit <= Win7.
48 size_t flags_size = sizeof(flags);
49
50 if (!startup_info.InitializeProcThreadAttributeList(1) ||
51 !startup_info.UpdateProcThreadAttribute(
52 PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &flags, flags_size)) {
53 ADD_FAILURE();
54 return false;
55 }
56 creation_flags = EXTENDED_STARTUPINFO_PRESENT;
57 }
58
59 // Command line must be writable.
60 base::string16 cmd_writeable(hooking_win_proc::g_winproc_file);
61
62 if (!::CreateProcessW(NULL, &cmd_writeable[0], NULL, NULL, FALSE,
63 creation_flags, NULL, NULL, startup_info.startup_info(),
64 pi)) {
65 ADD_FAILURE();
66 return false;
67 }
68 EXPECT_EQ(WAIT_OBJECT_0,
69 ::WaitForSingleObject(*event, sandbox::SboxTestEventTimeout()));
70
71 return true;
72 }
73
74 //------------------------------------------------------------------------------
75 // ExtensionPoint test helper function.
76 //
77 // 1. Spawn a Windows process (with or without mitigation enabled).
78 // 2. Load the hook Dll locally.
79 // 3. Create a global named event for the hook to trigger.
80 // 4. Start the hook (for the specific WinProc or globally).
81 // 5. Send a keystroke event.
82 // 6. Ask the hook Dll if it received a hook callback.
83 // 7. Cleanup the hooking.
84 // 8. Signal the Windows process to shutdown.
85 //
86 // Do NOT use any ASSERTs in this function. Cleanup required.
87 //------------------------------------------------------------------------------
88 void TestWin8ExtensionPointHookWrapper(bool is_success_test, bool global_hook) {
89 // Set up a couple global events that this test will use.
90 HANDLE winproc_event =
91 ::CreateEventW(NULL, FALSE, FALSE, hooking_win_proc::g_winproc_event);
92 if (winproc_event == NULL || winproc_event == INVALID_HANDLE_VALUE) {
93 ADD_FAILURE();
94 return;
95 }
96 base::win::ScopedHandle scoped_winproc_event(winproc_event);
97
98 HANDLE hook_event =
99 ::CreateEventW(NULL, FALSE, FALSE, hooking_dll::g_hook_event);
100 if (hook_event == NULL || hook_event == INVALID_HANDLE_VALUE) {
101 ADD_FAILURE();
102 return;
103 }
104 base::win::ScopedHandle scoped_hook_event(hook_event);
105
106 // 1. Spawn WinProc.
107 PROCESS_INFORMATION proc_info = {};
108 if (!SpawnWinProc(&proc_info, is_success_test, &winproc_event))
109 return;
110
111 // From this point on, no return on failure. Cleanup required.
112 bool all_good = true;
113
114 // 2. Load the hook DLL.
115 base::FilePath hook_dll_path(g_hook_dll_file);
116 base::ScopedNativeLibrary dll(hook_dll_path);
117 EXPECT_TRUE(dll.is_valid());
118
119 HOOKPROC hook_proc = reinterpret_cast<HOOKPROC>(
120 dll.GetFunctionPointer(hooking_dll::g_hook_handler_func));
121 WasHookCalledFunction was_hook_called =
122 reinterpret_cast<WasHookCalledFunction>(
123 dll.GetFunctionPointer(hooking_dll::g_was_hook_called_func));
124 SetHookFunction set_hook = reinterpret_cast<SetHookFunction>(
125 dll.GetFunctionPointer(hooking_dll::g_set_hook_func));
126 if (!hook_proc || !was_hook_called || !set_hook) {
127 ADD_FAILURE();
128 all_good = false;
129 }
130
131 // 3. Try installing the hook (either on a remote target thread,
132 // or globally).
133 HHOOK hook = nullptr;
134 if (all_good) {
135 DWORD target = 0;
136 if (!global_hook)
137 target = proc_info.dwThreadId;
138 hook = ::SetWindowsHookExW(WH_KEYBOARD, hook_proc, dll.get(), target);
139 if (!hook) {
140 ADD_FAILURE();
141 all_good = false;
142 } else
143 // Pass the hook DLL the hook handle.
144 set_hook(hook);
145 }
146
147 // 4. Inject a keyboard event.
148 if (all_good) {
149 // Note: that PostThreadMessage and SendMessage APIs will not deliver
150 // a keystroke in such a way that triggers a "legitimate" hook.
151 // Have to use targetless SendInput or keybd_event. The latter is
152 // less code and easier to work with.
153 keybd_event(VkKeyScan(L'A'), 0, 0, 0);
154 keybd_event(VkKeyScan(L'A'), 0, KEYEVENTF_KEYUP, 0);
155 // Give it a chance to hit the hook handler...
156 ::WaitForSingleObject(hook_event, sandbox::SboxTestEventTimeout());
157
158 // 5. Did the hook get hit? Was it expected to?
159 if (global_hook)
160 EXPECT_EQ((is_success_test ? true : false), was_hook_called());
161 else
162 // ***IMPORTANT: when targeting a specific thread id, the
163 // PROCESS_CREATION_MITIGATION_POLICY_EXTENSION_POINT_DISABLE
164 // mitigation does NOT disable the hook API. It ONLY
165 // stops global hooks from running in a process. Hence,
166 // the hook will hit (TRUE) even in the "failure"
167 // case for a non-global/targeted hook.
168 EXPECT_EQ((is_success_test ? true : true), was_hook_called());
169 }
170
171 // 6. Disable hook.
172 if (hook)
173 EXPECT_TRUE(::UnhookWindowsHookEx(hook));
174
175 // 7. Trigger shutdown of WinProc.
176 if (proc_info.hProcess) {
177 if (::PostThreadMessageW(proc_info.dwThreadId, WM_QUIT, 0, 0)) {
178 // Note: The combination/perfect-storm of a Global Hook, in a
179 // WinProc that has the EXTENSION_POINT_DISABLE mitigation ON, and the
180 // use of the SendInput or keybd_event API to inject a keystroke,
181 // results in the target becoming unresponsive. If any one of these
182 // states are changed, the problem does not occur. This means the WM_QUIT
183 // message is not handled and the call to WaitForSingleObject times out.
184 // Therefore not checking the return val.
185 ::WaitForSingleObject(winproc_event, sandbox::SboxTestEventTimeout());
186 } else {
187 // Ensure no strays.
188 ::TerminateProcess(proc_info.hProcess, 0);
189 ADD_FAILURE();
190 }
191 EXPECT_TRUE(::CloseHandle(proc_info.hThread));
192 EXPECT_TRUE(::CloseHandle(proc_info.hProcess));
193 }
194 }
195
196 //------------------------------------------------------------------------------
197 // ExtensionPoint test helper function.
198 //
199 // 1. Set up the AppInit Dll in registry settings. (Enable)
200 // 2. Spawn a Windows process (with or without mitigation enabled).
201 // 3. Check if the AppInit Dll got loaded in the Windows process or not.
202 // 4. Signal the Windows process to shutdown.
203 // 5. Restore original reg settings.
204 //
205 // Do NOT use any ASSERTs in this function. Cleanup required.
206 //------------------------------------------------------------------------------
207 void TestWin8ExtensionPointAppInitWrapper(bool is_success_test) {
208 // 0.5 Get path of current module. The appropriate build of the
209 // AppInit DLL will be in the same directory (and the
210 // full path is needed for reg).
211 wchar_t path[MAX_PATH];
212 if (!::GetModuleFileNameW(NULL, path, MAX_PATH)) {
213 ADD_FAILURE();
214 return;
215 }
216 // Only want the directory. Switch file name for the AppInit DLL.
217 base::FilePath full_dll_path(path);
218 full_dll_path = full_dll_path.DirName();
219 full_dll_path = full_dll_path.Append(g_hook_dll_file);
220 wchar_t* non_const = const_cast<wchar_t*>(full_dll_path.value().c_str());
grt (UTC plus 2) 2017/06/28 12:16:55 i don't think you need to cast away constness to c
221 // Now make sure the path is in "short-name" form for registry.
222 DWORD length = ::GetShortPathNameW(non_const, NULL, 0);
223 std::vector<wchar_t> short_name(length);
224 if (!::GetShortPathNameW(non_const, &short_name[0], length)) {
225 ADD_FAILURE();
226 return;
227 }
228
229 // 1. Reg setup.
230 const wchar_t* app_init_reg_path =
231 L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows";
232 const wchar_t* dlls_value_name = L"AppInit_DLLs";
233 const wchar_t* enabled_value_name = L"LoadAppInit_DLLs";
234 const wchar_t* signing_value_name = L"RequireSignedAppInit_DLLs";
235 std::wstring orig_dlls;
236 std::wstring new_dlls;
237 DWORD orig_enabled_value = 0;
238 DWORD orig_signing_value = 0;
239 base::win::RegKey app_init_key(HKEY_LOCAL_MACHINE, app_init_reg_path,
240 KEY_QUERY_VALUE | KEY_SET_VALUE);
241 // Backup the existing settings.
242 if (!app_init_key.Valid() || !app_init_key.HasValue(dlls_value_name) ||
243 !app_init_key.HasValue(enabled_value_name) ||
244 ERROR_SUCCESS != app_init_key.ReadValue(dlls_value_name, &orig_dlls) ||
245 ERROR_SUCCESS !=
246 app_init_key.ReadValueDW(enabled_value_name, &orig_enabled_value)) {
247 ADD_FAILURE();
248 return;
249 }
250 if (app_init_key.HasValue(signing_value_name)) {
251 if (ERROR_SUCCESS !=
252 app_init_key.ReadValueDW(signing_value_name, &orig_signing_value)) {
253 ADD_FAILURE();
254 return;
255 }
256 }
257
258 // Set the new settings (obviously requires local admin privileges).
259 new_dlls = orig_dlls;
260 if (!orig_dlls.empty())
261 new_dlls.append(L",");
262 new_dlls.append(short_name.data());
263
264 // From this point on, no return on failure. Cleanup required.
265 bool all_good = true;
266
267 if (app_init_key.HasValue(signing_value_name)) {
268 if (ERROR_SUCCESS !=
269 app_init_key.WriteValue(signing_value_name, static_cast<DWORD>(0))) {
270 ADD_FAILURE();
271 all_good = false;
272 }
273 }
274 if (ERROR_SUCCESS !=
275 app_init_key.WriteValue(dlls_value_name, new_dlls.c_str()) ||
276 ERROR_SUCCESS !=
277 app_init_key.WriteValue(enabled_value_name, static_cast<DWORD>(1))) {
278 ADD_FAILURE();
279 all_good = false;
280 }
281
282 // 2. Spawn WinProc.
283 HANDLE winproc_event = INVALID_HANDLE_VALUE;
284 base::win::ScopedHandle scoped_event;
285 PROCESS_INFORMATION proc_info = {};
286 if (all_good) {
287 winproc_event =
288 ::CreateEventW(NULL, FALSE, FALSE, hooking_win_proc::g_winproc_event);
289 if (winproc_event == NULL || winproc_event == INVALID_HANDLE_VALUE) {
290 ADD_FAILURE();
291 all_good = false;
292 } else {
293 scoped_event.Set(winproc_event);
294 if (!SpawnWinProc(&proc_info, is_success_test, &winproc_event))
295 all_good = false;
296 }
297 }
298
299 // 3. Check loaded modules in WinProc to see if the AppInit dll is loaded.
300 bool dll_loaded = false;
301 if (all_good) {
302 std::vector<HMODULE>(modules);
303 if (!base::win::GetLoadedModulesSnapshot(proc_info.hProcess, &modules)) {
304 ADD_FAILURE();
305 all_good = false;
306 } else {
307 for (HMODULE module : modules) {
308 wchar_t name[MAX_PATH] = {};
309 if (::GetModuleFileNameExW(proc_info.hProcess, module, name,
310 MAX_PATH) &&
311 ::wcsstr(name, g_hook_dll_file)) {
312 // Found it.
313 dll_loaded = true;
314 break;
315 }
316 }
317 }
318 }
319
320 // Was the test result as expected?
321 if (all_good)
322 EXPECT_EQ((is_success_test ? true : false), dll_loaded);
323
324 // 4. Trigger shutdown of WinProc.
325 if (proc_info.hProcess) {
326 if (::PostThreadMessageW(proc_info.dwThreadId, WM_QUIT, 0, 0)) {
327 ::WaitForSingleObject(winproc_event, sandbox::SboxTestEventTimeout());
328 } else {
329 // Ensure no strays.
330 ::TerminateProcess(proc_info.hProcess, 0);
331 ADD_FAILURE();
332 }
333 EXPECT_TRUE(::CloseHandle(proc_info.hThread));
334 EXPECT_TRUE(::CloseHandle(proc_info.hProcess));
335 }
336
337 // 5. Reg Restore
338 EXPECT_EQ(ERROR_SUCCESS,
339 app_init_key.WriteValue(enabled_value_name, orig_enabled_value));
340 if (app_init_key.HasValue(signing_value_name))
341 EXPECT_EQ(ERROR_SUCCESS,
342 app_init_key.WriteValue(signing_value_name, orig_signing_value));
343 EXPECT_EQ(ERROR_SUCCESS,
344 app_init_key.WriteValue(dlls_value_name, orig_dlls.c_str()));
345 }
346
347 } // namespace
348
349 namespace sandbox {
350
351 //------------------------------------------------------------------------------
352 // Exported Extension Point Tests
353 //------------------------------------------------------------------------------
354
355 //------------------------------------------------------------------------------
356 // Disable extension points (MITIGATION_EXTENSION_POINT_DISABLE).
357 // >= Win8
358 //------------------------------------------------------------------------------
359
360 // This test validates that setting the MITIGATION_EXTENSION_POINT_DISABLE
361 // mitigation enables the setting on a process.
362 TEST(ProcessMitigationsTest, CheckWin8ExtensionPointPolicySuccess) {
363 if (base::win::GetVersion() < base::win::VERSION_WIN8)
364 return;
365
366 std::wstring test_command = L"CheckPolicy ";
367 test_command += std::to_wstring(sandbox::TESTPOLICY_EXTENSIONPOINT);
368
369 //---------------------------------
370 // 1) Test setting pre-startup.
371 //---------------------------------
372 TestRunner runner;
373 sandbox::TargetPolicy* policy = runner.GetPolicy();
374
375 EXPECT_EQ(policy->SetProcessMitigations(MITIGATION_EXTENSION_POINT_DISABLE),
376 SBOX_ALL_OK);
377 EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(test_command.c_str()));
378
379 //---------------------------------
380 // 2) Test setting post-startup.
381 //---------------------------------
382 TestRunner runner2;
383 sandbox::TargetPolicy* policy2 = runner2.GetPolicy();
384
385 EXPECT_EQ(
386 policy2->SetDelayedProcessMitigations(MITIGATION_EXTENSION_POINT_DISABLE),
387 SBOX_ALL_OK);
388 EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner2.RunTest(test_command.c_str()));
389 }
390
391 // This test validates that a "legitimate" global hook CAN be set on the
392 // sandboxed proc/thread if the MITIGATION_EXTENSION_POINT_DISABLE
393 // mitigation is not set.
394 //
395 // MANUAL testing only.
396 TEST(ProcessMitigationsTest,
397 DISABLED_CheckWin8ExtensionPoint_GlobalHook_Success) {
398 if (base::win::GetVersion() < base::win::VERSION_WIN8)
399 return;
400
401 HANDLE mutex = ::CreateMutexW(NULL, FALSE, g_extension_point_test_mutex);
402 EXPECT_TRUE(mutex != NULL);
403 EXPECT_EQ(WAIT_OBJECT_0,
404 ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
405
406 // (is_success_test, global_hook)
407 TestWin8ExtensionPointHookWrapper(true, true);
408
409 EXPECT_TRUE(::ReleaseMutex(mutex));
410 EXPECT_TRUE(::CloseHandle(mutex));
411 }
412
413 // This test validates that setting the MITIGATION_EXTENSION_POINT_DISABLE
414 // mitigation prevents a global hook on WinProc.
415 //
416 // MANUAL testing only.
417 TEST(ProcessMitigationsTest,
418 DISABLED_CheckWin8ExtensionPoint_GlobalHook_Failure) {
419 if (base::win::GetVersion() < base::win::VERSION_WIN8)
420 return;
421
422 HANDLE mutex = ::CreateMutexW(NULL, FALSE, g_extension_point_test_mutex);
423 EXPECT_TRUE(mutex != NULL);
424 EXPECT_EQ(WAIT_OBJECT_0,
425 ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
426
427 // (is_success_test, global_hook)
428 TestWin8ExtensionPointHookWrapper(false, true);
429
430 EXPECT_TRUE(::ReleaseMutex(mutex));
431 EXPECT_TRUE(::CloseHandle(mutex));
432 }
433
434 // This test validates that a "legitimate" hook CAN be set on the sandboxed
435 // proc/thread if the MITIGATION_EXTENSION_POINT_DISABLE mitigation is not set.
436 //
437 // MANUAL testing only.
438 TEST(ProcessMitigationsTest, DISABLED_CheckWin8ExtensionPoint_Hook_Success) {
439 if (base::win::GetVersion() < base::win::VERSION_WIN8)
440 return;
441
442 HANDLE mutex = ::CreateMutexW(NULL, FALSE, g_extension_point_test_mutex);
443 EXPECT_TRUE(mutex != NULL);
444 EXPECT_EQ(WAIT_OBJECT_0,
445 ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
446
447 // (is_success_test, global_hook)
448 TestWin8ExtensionPointHookWrapper(true, false);
449
450 EXPECT_TRUE(::ReleaseMutex(mutex));
451 EXPECT_TRUE(::CloseHandle(mutex));
452 }
453
454 // *** Important: MITIGATION_EXTENSION_POINT_DISABLE does NOT prevent
455 // hooks targetted at a specific thread id. It only prevents
456 // global hooks. So this test does NOT actually expect the hook
457 // to fail (see TestWin8ExtensionPointHookWrapper function) even
458 // with the mitigation on.
459 //
460 // MANUAL testing only.
461 TEST(ProcessMitigationsTest, DISABLED_CheckWin8ExtensionPoint_Hook_Failure) {
462 if (base::win::GetVersion() < base::win::VERSION_WIN8)
463 return;
464
465 HANDLE mutex = ::CreateMutexW(NULL, FALSE, g_extension_point_test_mutex);
466 EXPECT_TRUE(mutex != NULL);
467 EXPECT_EQ(WAIT_OBJECT_0,
468 ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
469
470 // (is_success_test, global_hook)
471 TestWin8ExtensionPointHookWrapper(false, false);
472
473 EXPECT_TRUE(::ReleaseMutex(mutex));
474 EXPECT_TRUE(::CloseHandle(mutex));
475 }
476
477 // This test validates that an AppInit Dll CAN be added to a target
478 // WinProc if the MITIGATION_EXTENSION_POINT_DISABLE mitigation is not set.
479 //
480 // MANUAL testing only.
481 // Must run this test as admin/elevated.
482 TEST(ProcessMitigationsTest, DISABLED_CheckWin8ExtensionPoint_AppInit_Success) {
483 if (base::win::GetVersion() < base::win::VERSION_WIN8)
484 return;
485
486 HANDLE mutex = ::CreateMutexW(NULL, FALSE, g_extension_point_test_mutex);
487 EXPECT_TRUE(mutex != NULL);
488 EXPECT_EQ(WAIT_OBJECT_0,
489 ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
490
491 TestWin8ExtensionPointAppInitWrapper(true);
492
493 EXPECT_TRUE(::ReleaseMutex(mutex));
494 EXPECT_TRUE(::CloseHandle(mutex));
495 }
496
497 // This test validates that setting the MITIGATION_EXTENSION_POINT_DISABLE
498 // mitigation prevents the loading of any AppInit Dll into WinProc.
499 //
500 // MANUAL testing only.
501 // Must run this test as admin/elevated.
502 TEST(ProcessMitigationsTest, DISABLED_CheckWin8ExtensionPoint_AppInit_Failure) {
503 if (base::win::GetVersion() < base::win::VERSION_WIN8)
504 return;
505
506 HANDLE mutex = ::CreateMutexW(NULL, FALSE, g_extension_point_test_mutex);
507 EXPECT_TRUE(mutex != NULL);
508 EXPECT_EQ(WAIT_OBJECT_0,
509 ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
510
511 TestWin8ExtensionPointAppInitWrapper(false);
512
513 EXPECT_TRUE(::ReleaseMutex(mutex));
514 EXPECT_TRUE(::CloseHandle(mutex));
515 }
516
517 } // namespace sandbox
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698