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

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

Powered by Google App Engine
This is Rietveld 408576698