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