Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / sandbox / win / sandbox_poc / main_ui_window.cc
blob1e32b25954d9e42741d9501eeaa3ec9f1b530a4f
1 // Copyright (c) 2014 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.
5 #include <windows.h>
6 #include <CommCtrl.h>
7 #include <commdlg.h>
8 #include <stdarg.h>
9 #include <time.h>
10 #include <windowsx.h>
11 #include <atlbase.h>
12 #include <atlsecurity.h>
13 #include <algorithm>
14 #include <sstream>
16 #include "sandbox/win/sandbox_poc/main_ui_window.h"
17 #include "base/logging.h"
18 #include "sandbox/win/sandbox_poc/resource.h"
19 #include "sandbox/win/src/acl.h"
20 #include "sandbox/win/src/sandbox.h"
21 #include "sandbox/win/src/win_utils.h"
23 HWND MainUIWindow::list_view_ = NULL;
25 const wchar_t MainUIWindow::kDefaultDll_[] = L"\\POCDLL.dll";
26 const wchar_t MainUIWindow::kDefaultEntryPoint_[] = L"Run";
27 const wchar_t MainUIWindow::kDefaultLogFile_[] = L"";
29 MainUIWindow::MainUIWindow()
30 : broker_(NULL),
31 spawn_target_(L""),
32 instance_handle_(NULL),
33 dll_path_(L""),
34 entry_point_(L"") {
37 MainUIWindow::~MainUIWindow() {
40 unsigned int MainUIWindow::CreateMainWindowAndLoop(
41 HINSTANCE instance,
42 wchar_t* command_line,
43 int show_command,
44 sandbox::BrokerServices* broker) {
45 DCHECK(instance);
46 DCHECK(command_line);
47 DCHECK(broker);
49 instance_handle_ = instance;
50 spawn_target_ = command_line;
51 broker_ = broker;
53 // We'll use spawn_target_ later for creating a child process, but
54 // CreateProcess doesn't like double quotes, so we remove them along with
55 // tabs and spaces from the start and end of the string
56 const wchar_t *trim_removal = L" \r\t\"";
57 spawn_target_.erase(0, spawn_target_.find_first_not_of(trim_removal));
58 spawn_target_.erase(spawn_target_.find_last_not_of(trim_removal) + 1);
60 WNDCLASSEX window_class = {0};
61 window_class.cbSize = sizeof(WNDCLASSEX);
62 window_class.style = CS_HREDRAW | CS_VREDRAW;
63 window_class.lpfnWndProc = MainUIWindow::WndProc;
64 window_class.cbClsExtra = 0;
65 window_class.cbWndExtra = 0;
66 window_class.hInstance = instance;
67 window_class.hIcon =
68 ::LoadIcon(instance, MAKEINTRESOURCE(IDI_SANDBOX));
69 window_class.hCursor = ::LoadCursor(NULL, IDC_ARROW);
70 window_class.hbrBackground = GetStockBrush(WHITE_BRUSH);
71 window_class.lpszMenuName = MAKEINTRESOURCE(IDR_MENU_MAIN_UI);
72 window_class.lpszClassName = L"sandbox_ui_1";
73 window_class.hIconSm = NULL;
75 INITCOMMONCONTROLSEX controls = {
76 sizeof(INITCOMMONCONTROLSEX),
77 ICC_STANDARD_CLASSES | ICC_LISTVIEW_CLASSES
79 ::InitCommonControlsEx(&controls);
81 if (!::RegisterClassEx(&window_class))
82 return ::GetLastError();
84 // Create a main window of size 600x400
85 HWND window = ::CreateWindowW(window_class.lpszClassName,
86 L"", // window name
87 WS_OVERLAPPEDWINDOW,
88 CW_USEDEFAULT, // x
89 CW_USEDEFAULT, // y
90 600, // width
91 400, // height
92 NULL, // parent
93 NULL, // NULL = use class menu
94 instance,
95 0); // lpParam
97 if (NULL == window)
98 return ::GetLastError();
100 ::SetWindowLongPtr(window,
101 GWLP_USERDATA,
102 reinterpret_cast<LONG_PTR>(this));
104 ::SetWindowText(window, L"Sandbox Proof of Concept");
106 ::ShowWindow(window, show_command);
108 MSG message;
109 // Now lets start the message pump retrieving messages for any window that
110 // belongs to the current thread
111 while (::GetMessage(&message, NULL, 0, 0)) {
112 ::TranslateMessage(&message);
113 ::DispatchMessage(&message);
116 return 0;
119 LRESULT CALLBACK MainUIWindow::WndProc(HWND window,
120 UINT message_id,
121 WPARAM wparam,
122 LPARAM lparam) {
123 MainUIWindow* host = FromWindow(window);
125 #define HANDLE_MSG(hwnd, message, fn) \
126 case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
128 switch (message_id) {
129 case WM_CREATE:
130 // 'host' is not yet available when we get the WM_CREATE message
131 return HANDLE_WM_CREATE(window, wparam, lparam, OnCreate);
132 case WM_DESTROY:
133 return HANDLE_WM_DESTROY(window, wparam, lparam, host->OnDestroy);
134 case WM_SIZE:
135 return HANDLE_WM_SIZE(window, wparam, lparam, host->OnSize);
136 case WM_COMMAND: {
137 // Look at which menu item was clicked on (or which accelerator)
138 int id = LOWORD(wparam);
139 switch (id) {
140 case ID_FILE_EXIT:
141 host->OnFileExit();
142 break;
143 case ID_COMMANDS_SPAWNTARGET:
144 host->OnCommandsLaunch(window);
145 break;
146 default:
147 // Some other menu item or accelerator
148 break;
151 return ERROR_SUCCESS;
154 default:
155 // Some other WM_message, let it pass to DefWndProc
156 break;
159 return DefWindowProc(window, message_id, wparam, lparam);
162 INT_PTR CALLBACK MainUIWindow::SpawnTargetWndProc(HWND dialog,
163 UINT message_id,
164 WPARAM wparam,
165 LPARAM lparam) {
166 UNREFERENCED_PARAMETER(lparam);
168 // Grab a reference to the main UI window (from the window handle)
169 MainUIWindow* host = FromWindow(GetParent(dialog));
170 DCHECK(host);
172 switch (message_id) {
173 case WM_INITDIALOG: {
174 // Initialize the window text for DLL name edit box
175 HWND edit_box_dll_name = ::GetDlgItem(dialog, IDC_DLL_NAME);
176 wchar_t current_dir[MAX_PATH];
177 if (GetCurrentDirectory(MAX_PATH, current_dir)) {
178 base::string16 dll_path = base::string16(current_dir) +
179 base::string16(kDefaultDll_);
180 ::SetWindowText(edit_box_dll_name, dll_path.c_str());
183 // Initialize the window text for Entry Point edit box
184 HWND edit_box_entry_point = ::GetDlgItem(dialog, IDC_ENTRY_POINT);
185 ::SetWindowText(edit_box_entry_point, kDefaultEntryPoint_);
187 // Initialize the window text for Log File edit box
188 HWND edit_box_log_file = ::GetDlgItem(dialog, IDC_LOG_FILE);
189 ::SetWindowText(edit_box_log_file, kDefaultLogFile_);
191 return static_cast<INT_PTR>(TRUE);
193 case WM_COMMAND:
194 // If the user presses the OK button (Launch)
195 if (LOWORD(wparam) == IDOK) {
196 if (host->OnLaunchDll(dialog)) {
197 if (host->SpawnTarget()) {
198 ::EndDialog(dialog, LOWORD(wparam));
201 return static_cast<INT_PTR>(TRUE);
202 } else if (LOWORD(wparam) == IDCANCEL) {
203 // If the user presses the Cancel button
204 ::EndDialog(dialog, LOWORD(wparam));
205 return static_cast<INT_PTR>(TRUE);
206 } else if (LOWORD(wparam) == IDC_BROWSE_DLL) {
207 // If the user presses the Browse button to look for a DLL
208 base::string16 dll_path = host->OnShowBrowseForDllDlg(dialog);
209 if (dll_path.length() > 0) {
210 // Initialize the window text for Log File edit box
211 HWND edit_box_dll_path = ::GetDlgItem(dialog, IDC_DLL_NAME);
212 ::SetWindowText(edit_box_dll_path, dll_path.c_str());
214 return static_cast<INT_PTR>(TRUE);
215 } else if (LOWORD(wparam) == IDC_BROWSE_LOG) {
216 // If the user presses the Browse button to look for a log file
217 base::string16 log_path = host->OnShowBrowseForLogFileDlg(dialog);
218 if (log_path.length() > 0) {
219 // Initialize the window text for Log File edit box
220 HWND edit_box_log_file = ::GetDlgItem(dialog, IDC_LOG_FILE);
221 ::SetWindowText(edit_box_log_file, log_path.c_str());
223 return static_cast<INT_PTR>(TRUE);
226 break;
229 return static_cast<INT_PTR>(FALSE);
232 MainUIWindow* MainUIWindow::FromWindow(HWND main_window) {
233 // We store a 'this' pointer using SetWindowLong in CreateMainWindowAndLoop
234 // so that we can retrieve it with this function later. This prevents us
235 // from having to define all the message handling functions (that we refer to
236 // in the window proc) as static
237 ::GetWindowLongPtr(main_window, GWLP_USERDATA);
238 return reinterpret_cast<MainUIWindow*>(
239 ::GetWindowLongPtr(main_window, GWLP_USERDATA));
242 BOOL MainUIWindow::OnCreate(HWND parent_window, LPCREATESTRUCT) {
243 // Create the listview that will the main app UI
244 list_view_ = ::CreateWindow(WC_LISTVIEW, // Class name
245 L"", // Window name
246 WS_CHILD | WS_VISIBLE | LVS_REPORT |
247 LVS_NOCOLUMNHEADER | WS_BORDER,
248 0, // x
249 0, // y
250 0, // width
251 0, // height
252 parent_window, // parent
253 NULL, // menu
254 ::GetModuleHandle(NULL),
255 0); // lpParam
257 DCHECK(list_view_);
258 if (!list_view_)
259 return FALSE;
261 LVCOLUMN list_view_column = {0};
262 list_view_column.mask = LVCF_FMT | LVCF_WIDTH ;
263 list_view_column.fmt = LVCFMT_LEFT;
264 list_view_column.cx = 10000; // Maximum size of an entry in the list view.
265 ListView_InsertColumn(list_view_, 0, &list_view_column);
267 // Set list view to show green font on black background
268 ListView_SetBkColor(list_view_, CLR_NONE);
269 ListView_SetTextColor(list_view_, RGB(0x0, 0x0, 0x0));
270 ListView_SetTextBkColor(list_view_, CLR_NONE);
272 return TRUE;
275 void MainUIWindow::OnDestroy(HWND window) {
276 UNREFERENCED_PARAMETER(window);
278 // Post a quit message because our application is over when the
279 // user closes this window.
280 ::PostQuitMessage(0);
283 void MainUIWindow::OnSize(HWND window, UINT state, int cx, int cy) {
284 UNREFERENCED_PARAMETER(window);
285 UNREFERENCED_PARAMETER(state);
287 // If we have a valid inner child, resize it to cover the entire
288 // client area of the main UI window.
289 if (list_view_) {
290 ::MoveWindow(list_view_,
291 0, // x
292 0, // y
293 cx, // width
294 cy, // height
295 TRUE); // repaint
299 void MainUIWindow::OnPaint(HWND window) {
300 PAINTSTRUCT paintstruct;
301 ::BeginPaint(window, &paintstruct);
302 // add painting code here if required
303 ::EndPaint(window, &paintstruct);
306 void MainUIWindow::OnFileExit() {
307 ::PostQuitMessage(0);
310 void MainUIWindow::OnCommandsLaunch(HWND window) {
311 // User wants to see the Select DLL dialog box
312 ::DialogBox(instance_handle_,
313 MAKEINTRESOURCE(IDD_LAUNCH_DLL),
314 window,
315 SpawnTargetWndProc);
318 bool MainUIWindow::OnLaunchDll(HWND dialog) {
319 HWND edit_box_dll_name = ::GetDlgItem(dialog, IDC_DLL_NAME);
320 HWND edit_box_entry_point = ::GetDlgItem(dialog, IDC_ENTRY_POINT);
321 HWND edit_log_file = ::GetDlgItem(dialog, IDC_LOG_FILE);
323 wchar_t dll_path[MAX_PATH];
324 wchar_t entry_point[MAX_PATH];
325 wchar_t log_file[MAX_PATH];
327 int dll_name_len = ::GetWindowText(edit_box_dll_name, dll_path, MAX_PATH);
328 int entry_point_len = ::GetWindowText(edit_box_entry_point,
329 entry_point, MAX_PATH);
330 // Log file is optional (can be blank)
331 ::GetWindowText(edit_log_file, log_file, MAX_PATH);
333 if (0 >= dll_name_len) {
334 ::MessageBox(dialog,
335 L"Please specify a DLL for the target to load",
336 L"No DLL specified",
337 MB_ICONERROR);
338 return false;
341 if (GetFileAttributes(dll_path) == INVALID_FILE_ATTRIBUTES) {
342 ::MessageBox(dialog,
343 L"DLL specified was not found",
344 L"DLL not found",
345 MB_ICONERROR);
346 return false;
349 if (0 >= entry_point_len) {
350 ::MessageBox(dialog,
351 L"Please specify an entry point for the DLL",
352 L"No entry point specified",
353 MB_ICONERROR);
354 return false;
357 // store these values in the member variables for use in SpawnTarget
358 log_file_ = base::string16(L"\"") + log_file + base::string16(L"\"");
359 dll_path_ = dll_path;
360 entry_point_ = entry_point;
362 return true;
365 DWORD WINAPI MainUIWindow::ListenPipeThunk(void *param) {
366 return reinterpret_cast<MainUIWindow*>(param)->ListenPipe();
369 DWORD WINAPI MainUIWindow::WaitForTargetThunk(void *param) {
370 return reinterpret_cast<MainUIWindow*>(param)->WaitForTarget();
373 // Thread waiting for the target application to die. It displays
374 // a message in the list view when it happens.
375 DWORD MainUIWindow::WaitForTarget() {
376 WaitForSingleObject(target_.hProcess, INFINITE);
378 DWORD exit_code = 0;
379 if (!GetExitCodeProcess(target_.hProcess, &exit_code)) {
380 exit_code = 0xFFFF; // Default exit code
383 ::CloseHandle(target_.hProcess);
384 ::CloseHandle(target_.hThread);
386 AddDebugMessage(L"Targed exited with return code %d", exit_code);
387 return 0;
390 // Thread waiting for messages on the log pipe. It displays the messages
391 // in the listview.
392 DWORD MainUIWindow::ListenPipe() {
393 HANDLE logfile_handle = NULL;
394 ATL::CString file_to_open = log_file_.c_str();
395 file_to_open.Remove(L'\"');
396 if (file_to_open.GetLength()) {
397 logfile_handle = ::CreateFile(file_to_open.GetBuffer(),
398 GENERIC_WRITE,
399 FILE_SHARE_READ | FILE_SHARE_WRITE,
400 NULL, // Default security attributes
401 CREATE_ALWAYS,
402 FILE_ATTRIBUTE_NORMAL,
403 NULL); // No template
404 if (INVALID_HANDLE_VALUE == logfile_handle) {
405 AddDebugMessage(L"Failed to open \"%ls\" for logging. Error %d",
406 file_to_open.GetBuffer(), ::GetLastError());
407 logfile_handle = NULL;
411 const int kSizeBuffer = 1024;
412 BYTE read_buffer[kSizeBuffer] = {0};
413 ATL::CStringA read_buffer_global;
414 ATL::CStringA string_to_print;
416 DWORD last_error = 0;
417 while(last_error == ERROR_SUCCESS || last_error == ERROR_PIPE_LISTENING ||
418 last_error == ERROR_NO_DATA)
420 DWORD read_data_length;
421 if (::ReadFile(pipe_handle_,
422 read_buffer,
423 kSizeBuffer - 1, // Max read size
424 &read_data_length,
425 NULL)) { // Not overlapped
426 if (logfile_handle) {
427 DWORD write_data_length;
428 ::WriteFile(logfile_handle,
429 read_buffer,
430 read_data_length,
431 &write_data_length,
432 FALSE); // Not overlapped
435 // Append the new buffer to the current buffer
436 read_buffer[read_data_length] = NULL;
437 read_buffer_global += reinterpret_cast<char *>(read_buffer);
438 read_buffer_global.Remove(10); // Remove the CRs
440 // If we completed a new line, output it
441 int endline = read_buffer_global.Find(13); // search for LF
442 while (-1 != endline) {
443 string_to_print = read_buffer_global;
444 string_to_print.Delete(endline, string_to_print.GetLength());
445 read_buffer_global.Delete(0, endline);
447 // print the line (with the ending LF)
448 OutputDebugStringA(string_to_print.GetBuffer());
450 // Remove the ending LF
451 read_buffer_global.Delete(0, 1);
453 // Add the line to the log
454 AddDebugMessage(L"%S", string_to_print.GetBuffer());
456 endline = read_buffer_global.Find(13);
458 last_error = ERROR_SUCCESS;
459 } else {
460 last_error = GetLastError();
461 Sleep(100);
465 if (read_buffer_global.GetLength()) {
466 AddDebugMessage(L"%S", read_buffer_global.GetBuffer());
469 CloseHandle(pipe_handle_);
471 if (logfile_handle) {
472 CloseHandle(logfile_handle);
475 return 0;
478 bool MainUIWindow::SpawnTarget() {
479 // Generate the pipe name
480 GUID random_id;
481 CoCreateGuid(&random_id);
483 wchar_t log_pipe[MAX_PATH] = {0};
484 wnsprintf(log_pipe, MAX_PATH - 1,
485 L"\\\\.\\pipe\\sbox_pipe_log_%lu_%lu_%lu_%lu",
486 random_id.Data1,
487 random_id.Data2,
488 random_id.Data3,
489 random_id.Data4);
491 // We concatenate the four strings, add three spaces and a zero termination
492 // We use the resulting string as a param to CreateProcess (in SpawnTarget)
493 // Documented maximum for command line in CreateProcess is 32K (msdn)
494 size_t size_call = spawn_target_.length() + entry_point_.length() +
495 dll_path_.length() + wcslen(log_pipe) + 6;
496 if (32 * 1024 < (size_call * sizeof(wchar_t))) {
497 AddDebugMessage(L"The length of the arguments exceeded 32K. "
498 L"Aborting operation.");
499 return false;
502 wchar_t * arguments = new wchar_t[size_call];
503 wnsprintf(arguments, static_cast<int>(size_call), L"%ls %ls \"%ls\" %ls",
504 spawn_target_.c_str(), entry_point_.c_str(),
505 dll_path_.c_str(), log_pipe);
507 arguments[size_call - 1] = L'\0';
509 sandbox::TargetPolicy* policy = broker_->CreatePolicy();
510 policy->SetJobLevel(sandbox::JOB_LOCKDOWN, 0);
511 policy->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS,
512 sandbox::USER_LOCKDOWN);
513 policy->SetAlternateDesktop(true);
514 policy->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW);
516 // Set the rule to allow the POC dll to be loaded by the target. Note that
517 // the rule allows 'all access' to the DLL, which could mean that the target
518 // could modify the DLL on disk.
519 policy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES,
520 sandbox::TargetPolicy::FILES_ALLOW_ANY, dll_path_.c_str());
522 sandbox::ResultCode result = broker_->SpawnTarget(spawn_target_.c_str(),
523 arguments, policy,
524 &target_);
526 policy->Release();
527 policy = NULL;
529 bool return_value = false;
530 if (sandbox::SBOX_ALL_OK != result) {
531 AddDebugMessage(
532 L"Failed to spawn target %ls w/args (%ls), sandbox error code: %d",
533 spawn_target_.c_str(), arguments, result);
534 return_value = false;
535 } else {
537 DWORD thread_id;
538 ::CreateThread(NULL, // Default security attributes
539 NULL, // Default stack size
540 &MainUIWindow::WaitForTargetThunk,
541 this,
542 0, // No flags
543 &thread_id);
545 pipe_handle_ = ::CreateNamedPipe(log_pipe,
546 PIPE_ACCESS_INBOUND | WRITE_DAC,
547 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
548 1, // Number of instances.
549 512, // Out buffer size.
550 512, // In buffer size.
551 NMPWAIT_USE_DEFAULT_WAIT,
552 NULL); // Default security descriptor
554 if (INVALID_HANDLE_VALUE == pipe_handle_)
555 AddDebugMessage(L"Failed to create pipe. Error %d", ::GetLastError());
557 if (!sandbox::AddKnownSidToObject(pipe_handle_, SE_KERNEL_OBJECT,
558 WinWorldSid, GRANT_ACCESS,
559 FILE_ALL_ACCESS))
560 AddDebugMessage(L"Failed to set security on pipe. Error %d",
561 ::GetLastError());
563 ::CreateThread(NULL, // Default security attributes
564 NULL, // Default stack size
565 &MainUIWindow::ListenPipeThunk,
566 this,
567 0, // No flags
568 &thread_id);
570 ::ResumeThread(target_.hThread);
572 AddDebugMessage(L"Successfully spawned target w/args (%ls)", arguments);
573 return_value = true;
576 delete[] arguments;
577 return return_value;
580 base::string16 MainUIWindow::OnShowBrowseForDllDlg(HWND owner) {
581 wchar_t filename[MAX_PATH];
582 wcscpy_s(filename, MAX_PATH, L"");
584 OPENFILENAMEW file_info = {0};
585 file_info.lStructSize = sizeof(file_info);
586 file_info.hwndOwner = owner;
587 file_info.lpstrFile = filename;
588 file_info.nMaxFile = MAX_PATH;
589 file_info.lpstrFilter = L"DLL files (*.dll)\0*.dll\0All files\0*.*\0\0\0";
591 file_info.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
593 if (GetOpenFileName(&file_info)) {
594 return file_info.lpstrFile;
597 return L"";
600 base::string16 MainUIWindow::OnShowBrowseForLogFileDlg(HWND owner) {
601 wchar_t filename[MAX_PATH];
602 wcscpy_s(filename, MAX_PATH, L"");
604 OPENFILENAMEW file_info = {0};
605 file_info.lStructSize = sizeof(file_info);
606 file_info.hwndOwner = owner;
607 file_info.lpstrFile = filename;
608 file_info.nMaxFile = MAX_PATH;
609 file_info.lpstrFilter = L"Log file (*.txt)\0*.txt\0All files\0*.*\0\0\0";
611 file_info.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
613 if (GetSaveFileName(&file_info)) {
614 return file_info.lpstrFile;
617 return L"";
620 void MainUIWindow::AddDebugMessage(const wchar_t* format, ...) {
621 DCHECK(format);
622 if (!format)
623 return;
625 const int kMaxDebugBuffSize = 1024;
627 va_list arg_list;
628 va_start(arg_list, format);
630 wchar_t text[kMaxDebugBuffSize + 1];
631 vswprintf_s(text, kMaxDebugBuffSize, format, arg_list);
632 text[kMaxDebugBuffSize] = L'\0';
634 InsertLineInListView(text);
638 void MainUIWindow::InsertLineInListView(wchar_t* debug_message) {
639 DCHECK(debug_message);
640 if (!debug_message)
641 return;
643 // Prepend the time to the message
644 const int kSizeTime = 100;
645 size_t size_message_with_time = wcslen(debug_message) + kSizeTime;
646 wchar_t * message_time = new wchar_t[size_message_with_time];
648 time_t time_temp;
649 time_temp = time(NULL);
651 struct tm time = {0};
652 localtime_s(&time, &time_temp);
654 size_t return_code;
655 return_code = wcsftime(message_time, kSizeTime, L"[%H:%M:%S] ", &time);
657 wcscat_s(message_time, size_message_with_time, debug_message);
659 // We add the debug message to the top of the listview
660 LVITEM item;
661 item.iItem = ListView_GetItemCount(list_view_);
662 item.iSubItem = 0;
663 item.mask = LVIF_TEXT | LVIF_PARAM;
664 item.pszText = message_time;
665 item.lParam = 0;
667 ListView_InsertItem(list_view_, &item);
669 delete[] message_time;