1// Copyright (c) 2012 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 "chrome/tools/crash_service/crash_service.h" 6 7#include <windows.h> 8 9#include <sddl.h> 10#include <fstream> 11#include <map> 12 13#include "base/command_line.h" 14#include "base/file_util.h" 15#include "base/logging.h" 16#include "base/path_service.h" 17#include "base/win/windows_version.h" 18#include "breakpad/src/client/windows/crash_generation/client_info.h" 19#include "breakpad/src/client/windows/crash_generation/crash_generation_server.h" 20#include "breakpad/src/client/windows/sender/crash_report_sender.h" 21#include "chrome/common/chrome_constants.h" 22#include "chrome/common/chrome_paths.h" 23 24namespace { 25 26const wchar_t kTestPipeName[] = L"\\\\.\\pipe\\ChromeCrashServices"; 27 28const wchar_t kCrashReportURL[] = L"https://clients2.google.com/cr/report"; 29const wchar_t kCheckPointFile[] = L"crash_checkpoint.txt"; 30 31typedef std::map<std::wstring, std::wstring> CrashMap; 32 33bool CustomInfoToMap(const google_breakpad::ClientInfo* client_info, 34 const std::wstring& reporter_tag, CrashMap* map) { 35 google_breakpad::CustomClientInfo info = client_info->GetCustomInfo(); 36 37 for (uintptr_t i = 0; i < info.count; ++i) { 38 (*map)[info.entries[i].name] = info.entries[i].value; 39 } 40 41 (*map)[L"rept"] = reporter_tag; 42 43 return !map->empty(); 44} 45 46bool WriteCustomInfoToFile(const std::wstring& dump_path, const CrashMap& map) { 47 std::wstring file_path(dump_path); 48 size_t last_dot = file_path.rfind(L'.'); 49 if (last_dot == std::wstring::npos) 50 return false; 51 file_path.resize(last_dot); 52 file_path += L".txt"; 53 54 std::wofstream file(file_path.c_str(), 55 std::ios_base::out | std::ios_base::app | std::ios::binary); 56 if (!file.is_open()) 57 return false; 58 59 CrashMap::const_iterator pos; 60 for (pos = map.begin(); pos != map.end(); ++pos) { 61 std::wstring line = pos->first; 62 line += L':'; 63 line += pos->second; 64 line += L'\n'; 65 file.write(line.c_str(), static_cast<std::streamsize>(line.length())); 66 } 67 return true; 68} 69 70// The window procedure task is to handle when a) the user logs off. 71// b) the system shuts down or c) when the user closes the window. 72LRESULT __stdcall CrashSvcWndProc(HWND hwnd, UINT message, 73 WPARAM wparam, LPARAM lparam) { 74 switch (message) { 75 case WM_CLOSE: 76 case WM_ENDSESSION: 77 case WM_DESTROY: 78 PostQuitMessage(0); 79 break; 80 default: 81 return DefWindowProc(hwnd, message, wparam, lparam); 82 } 83 return 0; 84} 85 86// This is the main and only application window. 87HWND g_top_window = NULL; 88 89bool CreateTopWindow(HINSTANCE instance, bool visible) { 90 WNDCLASSEXW wcx = {0}; 91 wcx.cbSize = sizeof(wcx); 92 wcx.style = CS_HREDRAW | CS_VREDRAW; 93 wcx.lpfnWndProc = CrashSvcWndProc; 94 wcx.hInstance = instance; 95 wcx.lpszClassName = L"crash_svc_class"; 96 ATOM atom = ::RegisterClassExW(&wcx); 97 DWORD style = visible ? WS_POPUPWINDOW | WS_VISIBLE : WS_OVERLAPPED; 98 99 // The window size is zero but being a popup window still shows in the 100 // task bar and can be closed using the system menu or using task manager. 101 HWND window = CreateWindowExW(0, wcx.lpszClassName, L"crash service", style, 102 CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, 103 NULL, NULL, instance, NULL); 104 if (!window) 105 return false; 106 107 ::UpdateWindow(window); 108 VLOG(1) << "window handle is " << window; 109 g_top_window = window; 110 return true; 111} 112 113// Simple helper class to keep the process alive until the current request 114// finishes. 115class ProcessingLock { 116 public: 117 ProcessingLock() { 118 ::InterlockedIncrement(&op_count_); 119 } 120 ~ProcessingLock() { 121 ::InterlockedDecrement(&op_count_); 122 } 123 static bool IsWorking() { 124 return (op_count_ != 0); 125 } 126 private: 127 static volatile LONG op_count_; 128}; 129 130volatile LONG ProcessingLock::op_count_ = 0; 131 132// This structure contains the information that the worker thread needs to 133// send a crash dump to the server. 134struct DumpJobInfo { 135 DWORD pid; 136 CrashService* self; 137 CrashMap map; 138 std::wstring dump_path; 139 140 DumpJobInfo(DWORD process_id, CrashService* service, 141 const CrashMap& crash_map, const std::wstring& path) 142 : pid(process_id), self(service), map(crash_map), dump_path(path) { 143 } 144}; 145 146} // namespace 147 148// Command line switches: 149const char CrashService::kMaxReports[] = "max-reports"; 150const char CrashService::kNoWindow[] = "no-window"; 151const char CrashService::kReporterTag[] = "reporter"; 152const char CrashService::kDumpsDir[] = "dumps-dir"; 153const char CrashService::kPipeName[] = "pipe-name"; 154 155CrashService::CrashService(const std::wstring& report_dir) 156 : report_path_(report_dir), 157 sender_(NULL), 158 dumper_(NULL), 159 requests_handled_(0), 160 requests_sent_(0), 161 clients_connected_(0), 162 clients_terminated_(0) { 163 chrome::RegisterPathProvider(); 164} 165 166CrashService::~CrashService() { 167 base::AutoLock lock(sending_); 168 delete dumper_; 169 delete sender_; 170} 171 172 173bool CrashService::Initialize(const std::wstring& command_line) { 174 using google_breakpad::CrashReportSender; 175 using google_breakpad::CrashGenerationServer; 176 177 std::wstring pipe_name = kTestPipeName; 178 int max_reports = -1; 179 180 // The checkpoint file allows CrashReportSender to enforce the the maximum 181 // reports per day quota. Does not seem to serve any other purpose. 182 base::FilePath checkpoint_path = report_path_.Append(kCheckPointFile); 183 184 // The dumps path is typically : '<user profile>\Local settings\ 185 // Application data\Goggle\Chrome\Crash Reports' and the report path is 186 // Application data\Google\Chrome\Reported Crashes.txt 187 base::FilePath user_data_dir; 188 if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) { 189 LOG(ERROR) << "could not get DIR_USER_DATA"; 190 return false; 191 } 192 report_path_ = user_data_dir.Append(chrome::kCrashReportLog); 193 194 CommandLine cmd_line = CommandLine::FromString(command_line); 195 196 base::FilePath dumps_path; 197 if (cmd_line.HasSwitch(kDumpsDir)) { 198 dumps_path = base::FilePath(cmd_line.GetSwitchValueNative(kDumpsDir)); 199 } else { 200 if (!PathService::Get(chrome::DIR_CRASH_DUMPS, &dumps_path)) { 201 LOG(ERROR) << "could not get DIR_CRASH_DUMPS"; 202 return false; 203 } 204 } 205 206 // We can override the send reports quota with a command line switch. 207 if (cmd_line.HasSwitch(kMaxReports)) 208 max_reports = _wtoi(cmd_line.GetSwitchValueNative(kMaxReports).c_str()); 209 210 // Allow the global pipe name to be overridden for better testability. 211 if (cmd_line.HasSwitch(kPipeName)) 212 pipe_name = cmd_line.GetSwitchValueNative(kPipeName); 213 214#ifdef _WIN64 215 pipe_name += L"-x64"; 216#endif 217 218 if (max_reports > 0) { 219 // Create the http sender object. 220 sender_ = new CrashReportSender(checkpoint_path.value()); 221 if (!sender_) { 222 LOG(ERROR) << "could not create sender"; 223 return false; 224 } 225 sender_->set_max_reports_per_day(max_reports); 226 } 227 228 SECURITY_ATTRIBUTES security_attributes = {0}; 229 SECURITY_ATTRIBUTES* security_attributes_actual = NULL; 230 231 if (base::win::GetVersion() >= base::win::VERSION_VISTA) { 232 SECURITY_DESCRIPTOR* security_descriptor = 233 reinterpret_cast<SECURITY_DESCRIPTOR*>( 234 GetSecurityDescriptorForLowIntegrity()); 235 DCHECK(security_descriptor != NULL); 236 237 security_attributes.nLength = sizeof(security_attributes); 238 security_attributes.lpSecurityDescriptor = security_descriptor; 239 security_attributes.bInheritHandle = FALSE; 240 241 security_attributes_actual = &security_attributes; 242 } 243 244 // Create the OOP crash generator object. 245 dumper_ = new CrashGenerationServer(pipe_name, security_attributes_actual, 246 &CrashService::OnClientConnected, this, 247 &CrashService::OnClientDumpRequest, this, 248 &CrashService::OnClientExited, this, 249 NULL, NULL, 250 true, &dumps_path.value()); 251 252 if (!dumper_) { 253 LOG(ERROR) << "could not create dumper"; 254 if (security_attributes.lpSecurityDescriptor) 255 LocalFree(security_attributes.lpSecurityDescriptor); 256 return false; 257 } 258 259 if (!CreateTopWindow(::GetModuleHandleW(NULL), 260 !cmd_line.HasSwitch(kNoWindow))) { 261 LOG(ERROR) << "could not create window"; 262 if (security_attributes.lpSecurityDescriptor) 263 LocalFree(security_attributes.lpSecurityDescriptor); 264 return false; 265 } 266 267 reporter_tag_ = L"crash svc"; 268 if (cmd_line.HasSwitch(kReporterTag)) 269 reporter_tag_ = cmd_line.GetSwitchValueNative(kReporterTag); 270 271 // Log basic information. 272 VLOG(1) << "pipe name is " << pipe_name 273 << "\ndumps at " << dumps_path.value() 274 << "\nreports at " << report_path_.value(); 275 276 if (sender_) { 277 VLOG(1) << "checkpoint is " << checkpoint_path.value() 278 << "\nserver is " << kCrashReportURL 279 << "\nmaximum " << sender_->max_reports_per_day() << " reports/day" 280 << "\nreporter is " << reporter_tag_; 281 } 282 // Start servicing clients. 283 if (!dumper_->Start()) { 284 LOG(ERROR) << "could not start dumper"; 285 if (security_attributes.lpSecurityDescriptor) 286 LocalFree(security_attributes.lpSecurityDescriptor); 287 return false; 288 } 289 290 if (security_attributes.lpSecurityDescriptor) 291 LocalFree(security_attributes.lpSecurityDescriptor); 292 293 // This is throwaway code. We don't need to sync with the browser process 294 // once Google Update is updated to a version supporting OOP crash handling. 295 // Create or open an event to signal the browser process that the crash 296 // service is initialized. 297 HANDLE running_event = 298 ::CreateEventW(NULL, TRUE, TRUE, L"g_chrome_crash_svc"); 299 // If the browser already had the event open, the CreateEvent call did not 300 // signal it. We need to do it manually. 301 ::SetEvent(running_event); 302 303 return true; 304} 305 306void CrashService::OnClientConnected(void* context, 307 const google_breakpad::ClientInfo* client_info) { 308 ProcessingLock lock; 309 VLOG(1) << "client start. pid = " << client_info->pid(); 310 CrashService* self = static_cast<CrashService*>(context); 311 ::InterlockedIncrement(&self->clients_connected_); 312} 313 314void CrashService::OnClientExited(void* context, 315 const google_breakpad::ClientInfo* client_info) { 316 ProcessingLock lock; 317 VLOG(1) << "client end. pid = " << client_info->pid(); 318 CrashService* self = static_cast<CrashService*>(context); 319 ::InterlockedIncrement(&self->clients_terminated_); 320 321 if (!self->sender_) 322 return; 323 324 // When we are instructed to send reports we need to exit if there are 325 // no more clients to service. The next client that runs will start us. 326 // Only chrome.exe starts crash_service with a non-zero max_reports. 327 if (self->clients_connected_ > self->clients_terminated_) 328 return; 329 if (self->sender_->max_reports_per_day() > 0) { 330 // Wait for the other thread to send crashes, if applicable. The sender 331 // thread takes the sending_ lock, so the sleep is just to give it a 332 // chance to start. 333 ::Sleep(1000); 334 base::AutoLock lock(self->sending_); 335 // Some people can restart chrome very fast, check again if we have 336 // a new client before exiting for real. 337 if (self->clients_connected_ == self->clients_terminated_) { 338 VLOG(1) << "zero clients. exiting"; 339 ::PostMessage(g_top_window, WM_CLOSE, 0, 0); 340 } 341 } 342} 343 344void CrashService::OnClientDumpRequest(void* context, 345 const google_breakpad::ClientInfo* client_info, 346 const std::wstring* file_path) { 347 ProcessingLock lock; 348 349 if (!file_path) { 350 LOG(ERROR) << "dump with no file path"; 351 return; 352 } 353 if (!client_info) { 354 LOG(ERROR) << "dump with no client info"; 355 return; 356 } 357 358 CrashService* self = static_cast<CrashService*>(context); 359 if (!self) { 360 LOG(ERROR) << "dump with no context"; 361 return; 362 } 363 364 CrashMap map; 365 CustomInfoToMap(client_info, self->reporter_tag_, &map); 366 367 // Move dump file to the directory under client breakpad dump location. 368 base::FilePath dump_location = base::FilePath(*file_path); 369 CrashMap::const_iterator it = map.find(L"breakpad-dump-location"); 370 if (it != map.end()) { 371 base::FilePath alternate_dump_location = base::FilePath(it->second); 372 file_util::CreateDirectoryW(alternate_dump_location); 373 alternate_dump_location = alternate_dump_location.Append( 374 dump_location.BaseName()); 375 base::Move(dump_location, alternate_dump_location); 376 dump_location = alternate_dump_location; 377 } 378 379 DWORD pid = client_info->pid(); 380 VLOG(1) << "dump for pid = " << pid << " is " << dump_location.value(); 381 382 if (!WriteCustomInfoToFile(dump_location.value(), map)) { 383 LOG(ERROR) << "could not write custom info file"; 384 } 385 386 if (!self->sender_) 387 return; 388 389 // Send the crash dump using a worker thread. This operation has retry 390 // logic in case there is no internet connection at the time. 391 DumpJobInfo* dump_job = new DumpJobInfo(pid, self, map, 392 dump_location.value()); 393 if (!::QueueUserWorkItem(&CrashService::AsyncSendDump, 394 dump_job, WT_EXECUTELONGFUNCTION)) { 395 LOG(ERROR) << "could not queue job"; 396 } 397} 398 399// We are going to try sending the report several times. If we can't send, 400// we sleep from one minute to several hours depending on the retry round. 401unsigned long CrashService::AsyncSendDump(void* context) { 402 if (!context) 403 return 0; 404 405 DumpJobInfo* info = static_cast<DumpJobInfo*>(context); 406 407 std::wstring report_id = L"<unsent>"; 408 409 const DWORD kOneMinute = 60*1000; 410 const DWORD kOneHour = 60*kOneMinute; 411 412 const DWORD kSleepSchedule[] = { 413 24*kOneHour, 414 8*kOneHour, 415 4*kOneHour, 416 kOneHour, 417 15*kOneMinute, 418 0}; 419 420 int retry_round = arraysize(kSleepSchedule) - 1; 421 422 do { 423 ::Sleep(kSleepSchedule[retry_round]); 424 { 425 // Take the server lock while sending. This also prevent early 426 // termination of the service object. 427 base::AutoLock lock(info->self->sending_); 428 VLOG(1) << "trying to send report for pid = " << info->pid; 429 google_breakpad::ReportResult send_result 430 = info->self->sender_->SendCrashReport(kCrashReportURL, info->map, 431 info->dump_path, &report_id); 432 switch (send_result) { 433 case google_breakpad::RESULT_FAILED: 434 report_id = L"<network issue>"; 435 break; 436 case google_breakpad::RESULT_REJECTED: 437 report_id = L"<rejected>"; 438 ++info->self->requests_handled_; 439 retry_round = 0; 440 break; 441 case google_breakpad::RESULT_SUCCEEDED: 442 ++info->self->requests_sent_; 443 ++info->self->requests_handled_; 444 retry_round = 0; 445 break; 446 case google_breakpad::RESULT_THROTTLED: 447 report_id = L"<throttled>"; 448 break; 449 default: 450 report_id = L"<unknown>"; 451 break; 452 }; 453 } 454 455 VLOG(1) << "dump for pid =" << info->pid << " crash2 id =" << report_id; 456 --retry_round; 457 } while (retry_round >= 0); 458 459 if (!::DeleteFileW(info->dump_path.c_str())) 460 LOG(WARNING) << "could not delete " << info->dump_path; 461 462 delete info; 463 return 0; 464} 465 466int CrashService::ProcessingLoop() { 467 MSG msg; 468 while (GetMessage(&msg, NULL, 0, 0)) { 469 TranslateMessage(&msg); 470 DispatchMessage(&msg); 471 } 472 473 VLOG(1) << "session ending.."; 474 while (ProcessingLock::IsWorking()) { 475 ::Sleep(50); 476 } 477 478 VLOG(1) << "clients connected :" << clients_connected_ 479 << "\nclients terminated :" << clients_terminated_ 480 << "\ndumps serviced :" << requests_handled_ 481 << "\ndumps reported :" << requests_sent_; 482 483 return static_cast<int>(msg.wParam); 484} 485 486PSECURITY_DESCRIPTOR CrashService::GetSecurityDescriptorForLowIntegrity() { 487 // Build the SDDL string for the label. 488 std::wstring sddl = L"S:(ML;;NW;;;S-1-16-4096)"; 489 490 DWORD error = ERROR_SUCCESS; 491 PSECURITY_DESCRIPTOR sec_desc = NULL; 492 493 PACL sacl = NULL; 494 BOOL sacl_present = FALSE; 495 BOOL sacl_defaulted = FALSE; 496 497 if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(), 498 SDDL_REVISION, 499 &sec_desc, NULL)) { 500 if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl, 501 &sacl_defaulted)) { 502 return sec_desc; 503 } 504 } 505 506 return NULL; 507} 508