blob: 6046dc553651545cb103141a7864998e2abc8373 [file] [log] [blame]
Simon Quef70060c2012-04-09 19:07:07 -07001// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Ken Mixter03403162010-08-18 15:23:16 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "crash-reporter/crash_collector.h"
6
Ken Mixter04ec10f2010-08-26 16:02:02 -07007#include <dirent.h>
Ken Mixter9b346472010-11-07 13:45:45 -08008#include <fcntl.h> // For file creation modes.
Ken Mixter03403162010-08-18 15:23:16 -07009#include <pwd.h> // For struct passwd.
10#include <sys/types.h> // for mode_t.
Ken Mixter9b346472010-11-07 13:45:45 -080011#include <sys/wait.h> // For waitpid.
12#include <unistd.h> // For execv and fork.
Mike Frysinger65b4c1e2011-09-21 12:41:29 -040013#define __STDC_FORMAT_MACROS // PRId64
14#include <inttypes.h>
Ken Mixter03403162010-08-18 15:23:16 -070015
Ken Mixteree849c52010-09-30 15:30:10 -070016#include <set>
Simon Quef70060c2012-04-09 19:07:07 -070017#include <vector>
Ken Mixteree849c52010-09-30 15:30:10 -070018
Ken Mixter9b346472010-11-07 13:45:45 -080019#include "base/eintr_wrapper.h"
Ken Mixter03403162010-08-18 15:23:16 -070020#include "base/file_util.h"
21#include "base/logging.h"
Chris Masone8a68c7c2011-05-14 11:44:04 -070022#include "base/string_split.h"
Ken Mixter03403162010-08-18 15:23:16 -070023#include "base/string_util.h"
Ken Mixtera3249322011-03-03 08:47:38 -080024#include "chromeos/process.h"
Ken Mixter03403162010-08-18 15:23:16 -070025
Michael Krebs4fe30db2011-08-05 13:54:52 -070026static const char kCollectChromeFile[] =
27 "/mnt/stateful_partition/etc/collect_chrome_crashes";
Simon Quef70060c2012-04-09 19:07:07 -070028static const char kCollectUdevSignature[] = "crash_reporter-udev-collection";
Michael Krebs4fe30db2011-08-05 13:54:52 -070029static const char kCrashTestInProgressPath[] = "/tmp/crash-test-in-progress";
Simon Quef70060c2012-04-09 19:07:07 -070030static const char kDefaultLogConfig[] = "/etc/crash_reporter_logs.conf";
Ken Mixter03403162010-08-18 15:23:16 -070031static const char kDefaultUserName[] = "chronos";
Michael Krebs4fe30db2011-08-05 13:54:52 -070032static const char kLeaveCoreFile[] = "/root/.leave_core";
Ken Mixteree849c52010-09-30 15:30:10 -070033static const char kLsbRelease[] = "/etc/lsb-release";
Ken Mixterc49dbd42010-12-14 17:44:11 -080034static const char kShellPath[] = "/bin/sh";
Ken Mixter03403162010-08-18 15:23:16 -070035static const char kSystemCrashPath[] = "/var/spool/crash";
Simon Quef70060c2012-04-09 19:07:07 -070036static const char kUdevExecName[] = "udev";
37static const char kUdevSignatureKey[] = "sig";
Ken Mixter03403162010-08-18 15:23:16 -070038static const char kUserCrashPath[] = "/home/chronos/user/crash";
39
40// Directory mode of the user crash spool directory.
41static const mode_t kUserCrashPathMode = 0755;
42
43// Directory mode of the system crash spool directory.
44static const mode_t kSystemCrashPathMode = 01755;
45
46static const uid_t kRootOwner = 0;
47static const uid_t kRootGroup = 0;
48
Ken Mixterda5db7a2010-09-17 13:50:42 -070049// Maximum crash reports per crash spool directory. Note that this is
50// a separate maximum from the maximum rate at which we upload these
51// diagnostics. The higher this rate is, the more space we allow for
52// core files, minidumps, and kcrash logs, and equivalently the more
53// processor and I/O bandwidth we dedicate to handling these crashes when
54// many occur at once. Also note that if core files are configured to
55// be left on the file system, we stop adding crashes when either the
56// number of core files or minidumps reaches this number.
57const int CrashCollector::kMaxCrashDirectorySize = 32;
Ken Mixter04ec10f2010-08-26 16:02:02 -070058
Ken Mixterafcf8082010-10-26 14:45:01 -070059CrashCollector::CrashCollector()
60 : forced_crash_directory_(NULL),
61 lsb_release_(kLsbRelease) {
Ken Mixter03403162010-08-18 15:23:16 -070062}
63
64CrashCollector::~CrashCollector() {
65}
66
67void CrashCollector::Initialize(
68 CrashCollector::CountCrashFunction count_crash_function,
Ken Mixtera3249322011-03-03 08:47:38 -080069 CrashCollector::IsFeedbackAllowedFunction is_feedback_allowed_function) {
Ken Mixter03403162010-08-18 15:23:16 -070070 CHECK(count_crash_function != NULL);
71 CHECK(is_feedback_allowed_function != NULL);
Ken Mixter03403162010-08-18 15:23:16 -070072
73 count_crash_function_ = count_crash_function;
74 is_feedback_allowed_function_ = is_feedback_allowed_function;
Ken Mixter03403162010-08-18 15:23:16 -070075}
76
Simon Quef70060c2012-04-09 19:07:07 -070077bool CrashCollector::HandleUdevCrash(const std::string &udev_event) {
78 // Process the udev event string.
79 // The udev string should be formatted as follows:
80 // "ACTION=[action]:KERNEL=[name]:SUBSYSTEM=[subsystem]"
81 // The values don't have to be in any particular order.
82
83 // First get all the key-value pairs.
84 std::vector<std::pair<std::string, std::string> > udev_event_keyval;
85 base::SplitStringIntoKeyValuePairs(udev_event, '=', ':', &udev_event_keyval);
86 std::vector<std::pair<std::string, std::string> >::const_iterator iter;
87 std::map<std::string, std::string> udev_event_map;
88 for (iter = udev_event_keyval.begin();
89 iter != udev_event_keyval.end();
90 ++iter) {
91 udev_event_map[iter->first] = iter->second;
92 }
93
94 // Construct the basename string for crash_reporter_logs.conf:
95 // "crash_reporter-udev-collection-[action]-[name]-[subsystem]"
96 // If a udev field is not provided, "" is used in its place, e.g.:
97 // "crash_reporter-udev-collection-[action]--[subsystem]"
98 // Hence, "" is used as a wildcard name string.
99 std::string basename = udev_event_map["ACTION"] + "-" +
100 udev_event_map["KERNEL"] + "-" +
101 udev_event_map["SUBSYSTEM"];
102 std::string udev_log_name = std::string(kCollectUdevSignature) + '-' +
103 basename;
104
105 // Make sure the crash directory exists, or create it if it doesn't.
106 FilePath crash_directory;
107 if (!GetCreatedCrashDirectoryByEuid(0, &crash_directory, NULL)) {
108 LOG(ERROR) << "Could not get crash directory.";
109 return false;
110 }
111 // Create the destination path.
112 std::string log_file_name =
113 FormatDumpBasename(basename, time(NULL), 0);
114 FilePath crash_path = GetCrashPath(crash_directory, log_file_name, "log");
115
116 // Handle the crash.
117 bool result = GetLogContents(FilePath(kDefaultLogConfig), udev_log_name,
118 crash_path);
119 if (!result) {
120 LOG(ERROR) << "Error reading udev log info " << udev_log_name;
121 return false;
122 }
123
124 AddCrashMetaData(kUdevSignatureKey, kCollectUdevSignature);
125 WriteCrashMetaData(GetCrashPath(crash_directory, log_file_name, "meta"),
126 kUdevExecName, crash_path.value());
127 return true;
128}
129
Ken Mixter9b346472010-11-07 13:45:45 -0800130int CrashCollector::WriteNewFile(const FilePath &filename,
131 const char *data,
132 int size) {
133 int fd = HANDLE_EINTR(open(filename.value().c_str(),
134 O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0666));
135 if (fd < 0) {
136 return -1;
137 }
138
139 int rv = file_util::WriteFileDescriptor(fd, data, size);
140 HANDLE_EINTR(close(fd));
141 return rv;
142}
143
Ken Mixteree849c52010-09-30 15:30:10 -0700144std::string CrashCollector::Sanitize(const std::string &name) {
145 std::string result = name;
146 for (size_t i = 0; i < name.size(); ++i) {
147 if (!isalnum(result[i]) && result[i] != '_')
148 result[i] = '_';
149 }
150 return result;
151}
152
Ken Mixter03403162010-08-18 15:23:16 -0700153std::string CrashCollector::FormatDumpBasename(const std::string &exec_name,
154 time_t timestamp,
155 pid_t pid) {
156 struct tm tm;
157 localtime_r(&timestamp, &tm);
Ken Mixteree849c52010-09-30 15:30:10 -0700158 std::string sanitized_exec_name = Sanitize(exec_name);
Ken Mixter03403162010-08-18 15:23:16 -0700159 return StringPrintf("%s.%04d%02d%02d.%02d%02d%02d.%d",
Ken Mixteree849c52010-09-30 15:30:10 -0700160 sanitized_exec_name.c_str(),
Ken Mixter03403162010-08-18 15:23:16 -0700161 tm.tm_year + 1900,
162 tm.tm_mon + 1,
163 tm.tm_mday,
164 tm.tm_hour,
165 tm.tm_min,
166 tm.tm_sec,
167 pid);
168}
169
Ken Mixter207694d2010-10-28 15:42:37 -0700170FilePath CrashCollector::GetCrashPath(const FilePath &crash_directory,
171 const std::string &basename,
172 const std::string &extension) {
173 return crash_directory.Append(StringPrintf("%s.%s",
174 basename.c_str(),
175 extension.c_str()));
176}
177
Ken Mixter03403162010-08-18 15:23:16 -0700178FilePath CrashCollector::GetCrashDirectoryInfo(
179 uid_t process_euid,
180 uid_t default_user_id,
181 gid_t default_user_group,
182 mode_t *mode,
183 uid_t *directory_owner,
184 gid_t *directory_group) {
Michael Krebs4fe30db2011-08-05 13:54:52 -0700185 // TODO(mkrebs): This can go away once Chrome crashes are handled
186 // normally (see crosbug.com/5872).
187 // Check if the user crash directory should be used. If we are
188 // collecting chrome crashes during autotesting, we want to put them in
189 // the system crash directory so they are outside the cryptohome -- in
190 // case we are being run during logout (see crosbug.com/18637).
191 if (process_euid == default_user_id && IsUserSpecificDirectoryEnabled()) {
Ken Mixter03403162010-08-18 15:23:16 -0700192 *mode = kUserCrashPathMode;
193 *directory_owner = default_user_id;
194 *directory_group = default_user_group;
195 return FilePath(kUserCrashPath);
196 } else {
197 *mode = kSystemCrashPathMode;
198 *directory_owner = kRootOwner;
199 *directory_group = kRootGroup;
200 return FilePath(kSystemCrashPath);
201 }
202}
203
204bool CrashCollector::GetUserInfoFromName(const std::string &name,
205 uid_t *uid,
206 gid_t *gid) {
207 char storage[256];
208 struct passwd passwd_storage;
209 struct passwd *passwd_result = NULL;
210
211 if (getpwnam_r(name.c_str(), &passwd_storage, storage, sizeof(storage),
212 &passwd_result) != 0 || passwd_result == NULL) {
Ken Mixtera3249322011-03-03 08:47:38 -0800213 LOG(ERROR) << "Cannot find user named " << name;
Ken Mixter03403162010-08-18 15:23:16 -0700214 return false;
215 }
216
217 *uid = passwd_result->pw_uid;
218 *gid = passwd_result->pw_gid;
219 return true;
220}
221
222bool CrashCollector::GetCreatedCrashDirectoryByEuid(uid_t euid,
Ken Mixter207694d2010-10-28 15:42:37 -0700223 FilePath *crash_directory,
224 bool *out_of_capacity) {
Ken Mixter03403162010-08-18 15:23:16 -0700225 uid_t default_user_id;
226 gid_t default_user_group;
227
Ken Mixter207694d2010-10-28 15:42:37 -0700228 if (out_of_capacity != NULL) *out_of_capacity = false;
229
Ken Mixter03403162010-08-18 15:23:16 -0700230 // For testing.
231 if (forced_crash_directory_ != NULL) {
232 *crash_directory = FilePath(forced_crash_directory_);
233 return true;
234 }
235
236 if (!GetUserInfoFromName(kDefaultUserName,
237 &default_user_id,
238 &default_user_group)) {
Ken Mixtera3249322011-03-03 08:47:38 -0800239 LOG(ERROR) << "Could not find default user info";
Ken Mixter03403162010-08-18 15:23:16 -0700240 return false;
241 }
242 mode_t directory_mode;
243 uid_t directory_owner;
244 gid_t directory_group;
245 *crash_directory =
246 GetCrashDirectoryInfo(euid,
247 default_user_id,
248 default_user_group,
249 &directory_mode,
250 &directory_owner,
251 &directory_group);
252
253 if (!file_util::PathExists(*crash_directory)) {
254 // Create the spool directory with the appropriate mode (regardless of
255 // umask) and ownership.
256 mode_t old_mask = umask(0);
257 if (mkdir(crash_directory->value().c_str(), directory_mode) < 0 ||
258 chown(crash_directory->value().c_str(),
259 directory_owner,
260 directory_group) < 0) {
Ken Mixtera3249322011-03-03 08:47:38 -0800261 LOG(ERROR) << "Unable to create appropriate crash directory";
Ken Mixter03403162010-08-18 15:23:16 -0700262 return false;
263 }
264 umask(old_mask);
265 }
266
267 if (!file_util::PathExists(*crash_directory)) {
Ken Mixtera3249322011-03-03 08:47:38 -0800268 LOG(ERROR) << "Unable to create crash directory "
269 << crash_directory->value().c_str();
Ken Mixter03403162010-08-18 15:23:16 -0700270 return false;
271 }
272
Ken Mixter04ec10f2010-08-26 16:02:02 -0700273 if (!CheckHasCapacity(*crash_directory)) {
Ken Mixter207694d2010-10-28 15:42:37 -0700274 if (out_of_capacity != NULL) *out_of_capacity = true;
Ken Mixter04ec10f2010-08-26 16:02:02 -0700275 return false;
276 }
277
Ken Mixter03403162010-08-18 15:23:16 -0700278 return true;
279}
Ken Mixter04ec10f2010-08-26 16:02:02 -0700280
281// Return true if the given crash directory has not already reached
282// maximum capacity.
283bool CrashCollector::CheckHasCapacity(const FilePath &crash_directory) {
284 DIR* dir = opendir(crash_directory.value().c_str());
285 if (!dir) {
286 return false;
287 }
288 struct dirent ent_buf;
289 struct dirent* ent;
Ken Mixter04ec10f2010-08-26 16:02:02 -0700290 bool full = false;
Ken Mixteree849c52010-09-30 15:30:10 -0700291 std::set<std::string> basenames;
Ken Mixter04ec10f2010-08-26 16:02:02 -0700292 while (readdir_r(dir, &ent_buf, &ent) == 0 && ent != NULL) {
293 if ((strcmp(ent->d_name, ".") == 0) ||
294 (strcmp(ent->d_name, "..") == 0))
295 continue;
296
Ken Mixteree849c52010-09-30 15:30:10 -0700297 std::string filename(ent->d_name);
298 size_t last_dot = filename.rfind(".");
299 std::string basename;
300 // If there is a valid looking extension, use the base part of the
301 // name. If the only dot is the first byte (aka a dot file), treat
302 // it as unique to avoid allowing a directory full of dot files
303 // from accumulating.
304 if (last_dot != std::string::npos && last_dot != 0)
305 basename = filename.substr(0, last_dot);
306 else
307 basename = filename;
308 basenames.insert(basename);
Ken Mixter04ec10f2010-08-26 16:02:02 -0700309
Ken Mixteree849c52010-09-30 15:30:10 -0700310 if (basenames.size() >= static_cast<size_t>(kMaxCrashDirectorySize)) {
Ken Mixtera3249322011-03-03 08:47:38 -0800311 LOG(WARNING) << "Crash directory " << crash_directory.value()
312 << " already full with " << kMaxCrashDirectorySize
313 << " pending reports";
Ken Mixter04ec10f2010-08-26 16:02:02 -0700314 full = true;
315 break;
316 }
317 }
318 closedir(dir);
319 return !full;
320}
Ken Mixteree849c52010-09-30 15:30:10 -0700321
Ken Mixterc49dbd42010-12-14 17:44:11 -0800322bool CrashCollector::IsCommentLine(const std::string &line) {
323 size_t found = line.find_first_not_of(" ");
324 return found != std::string::npos && line[found] == '#';
325}
326
Ken Mixteree849c52010-09-30 15:30:10 -0700327bool CrashCollector::ReadKeyValueFile(
328 const FilePath &path,
329 const char separator,
330 std::map<std::string, std::string> *dictionary) {
331 std::string contents;
332 if (!file_util::ReadFileToString(path, &contents)) {
333 return false;
334 }
335 typedef std::vector<std::string> StringVector;
336 StringVector lines;
Chris Masone3ba6c5b2011-05-13 16:57:09 -0700337 base::SplitString(contents, '\n', &lines);
Ken Mixteree849c52010-09-30 15:30:10 -0700338 bool any_errors = false;
339 for (StringVector::iterator line = lines.begin(); line != lines.end();
340 ++line) {
341 // Allow empty strings.
342 if (line->empty())
343 continue;
Ken Mixterc49dbd42010-12-14 17:44:11 -0800344 // Allow comment lines.
345 if (IsCommentLine(*line))
346 continue;
Ken Mixteree849c52010-09-30 15:30:10 -0700347 StringVector sides;
Chris Masone3ba6c5b2011-05-13 16:57:09 -0700348 base::SplitString(*line, separator, &sides);
Ken Mixteree849c52010-09-30 15:30:10 -0700349 if (sides.size() != 2) {
350 any_errors = true;
351 continue;
352 }
353 dictionary->insert(std::pair<std::string, std::string>(sides[0], sides[1]));
354 }
355 return !any_errors;
356}
357
Ken Mixterc49dbd42010-12-14 17:44:11 -0800358bool CrashCollector::GetLogContents(const FilePath &config_path,
359 const std::string &exec_name,
360 const FilePath &output_file) {
361 std::map<std::string, std::string> log_commands;
362 if (!ReadKeyValueFile(config_path, ':', &log_commands)) {
Ken Mixtera3249322011-03-03 08:47:38 -0800363 LOG(INFO) << "Unable to read log configuration file "
364 << config_path.value();
Ken Mixterc49dbd42010-12-14 17:44:11 -0800365 return false;
366 }
367
368 if (log_commands.find(exec_name) == log_commands.end())
369 return false;
370
Ken Mixtera3249322011-03-03 08:47:38 -0800371 chromeos::ProcessImpl diag_process;
372 diag_process.AddArg(kShellPath);
Ken Mixterc49dbd42010-12-14 17:44:11 -0800373 std::string shell_command = log_commands[exec_name];
Ken Mixtera3249322011-03-03 08:47:38 -0800374 diag_process.AddStringOption("-c", shell_command);
375 diag_process.RedirectOutput(output_file.value());
Ken Mixterc49dbd42010-12-14 17:44:11 -0800376
Ken Mixtera3249322011-03-03 08:47:38 -0800377 int result = diag_process.Run();
378 if (result != 0) {
379 LOG(INFO) << "Running shell command " << shell_command << "failed with: "
380 << result;
Ken Mixterc49dbd42010-12-14 17:44:11 -0800381 return false;
382 }
383 return true;
384}
385
Ken Mixterafcf8082010-10-26 14:45:01 -0700386void CrashCollector::AddCrashMetaData(const std::string &key,
387 const std::string &value) {
388 extra_metadata_.append(StringPrintf("%s=%s\n", key.c_str(), value.c_str()));
389}
390
Ken Mixteree849c52010-09-30 15:30:10 -0700391void CrashCollector::WriteCrashMetaData(const FilePath &meta_path,
Ken Mixterc909b692010-10-18 12:26:05 -0700392 const std::string &exec_name,
393 const std::string &payload_path) {
Ken Mixteree849c52010-09-30 15:30:10 -0700394 std::map<std::string, std::string> contents;
Ken Mixterafcf8082010-10-26 14:45:01 -0700395 if (!ReadKeyValueFile(FilePath(std::string(lsb_release_)), '=', &contents)) {
Ken Mixtera3249322011-03-03 08:47:38 -0800396 LOG(ERROR) << "Problem parsing " << lsb_release_;
Ken Mixteree849c52010-09-30 15:30:10 -0700397 // Even though there was some failure, take as much as we could read.
398 }
399 std::string version("unknown");
400 std::map<std::string, std::string>::iterator i;
401 if ((i = contents.find("CHROMEOS_RELEASE_VERSION")) != contents.end()) {
402 version = i->second;
403 }
Ken Mixterc909b692010-10-18 12:26:05 -0700404 int64 payload_size = -1;
405 file_util::GetFileSize(FilePath(payload_path), &payload_size);
Ken Mixterafcf8082010-10-26 14:45:01 -0700406 std::string meta_data = StringPrintf("%sexec_name=%s\n"
Ken Mixteree849c52010-09-30 15:30:10 -0700407 "ver=%s\n"
Ken Mixter207694d2010-10-28 15:42:37 -0700408 "payload=%s\n"
Mike Frysinger65b4c1e2011-09-21 12:41:29 -0400409 "payload_size=%"PRId64"\n"
Ken Mixteree849c52010-09-30 15:30:10 -0700410 "done=1\n",
Ken Mixterafcf8082010-10-26 14:45:01 -0700411 extra_metadata_.c_str(),
Ken Mixteree849c52010-09-30 15:30:10 -0700412 exec_name.c_str(),
Ken Mixterc909b692010-10-18 12:26:05 -0700413 version.c_str(),
Ken Mixter207694d2010-10-28 15:42:37 -0700414 payload_path.c_str(),
Ken Mixterc909b692010-10-18 12:26:05 -0700415 payload_size);
Ken Mixter9b346472010-11-07 13:45:45 -0800416 // We must use WriteNewFile instead of file_util::WriteFile as we
417 // do not want to write with root access to a symlink that an attacker
418 // might have created.
419 if (WriteNewFile(meta_path, meta_data.c_str(), meta_data.size()) < 0) {
Ken Mixtera3249322011-03-03 08:47:38 -0800420 LOG(ERROR) << "Unable to write " << meta_path.value();
Ken Mixteree849c52010-09-30 15:30:10 -0700421 }
422}
Thieu Le1652fb22011-03-03 12:14:43 -0800423
424bool CrashCollector::IsCrashTestInProgress() {
425 return file_util::PathExists(FilePath(kCrashTestInProgressPath));
426}
Michael Krebs4fe30db2011-08-05 13:54:52 -0700427
428bool CrashCollector::IsDeveloperImage() {
429 // If we're testing crash reporter itself, we don't want to special-case
430 // for developer images.
431 if (IsCrashTestInProgress())
432 return false;
433 return file_util::PathExists(FilePath(kLeaveCoreFile));
434}
435
436bool CrashCollector::ShouldHandleChromeCrashes() {
437 // If we're testing crash reporter itself, we don't want to allow an
438 // override for chrome crashes. And, let's be conservative and only
439 // allow an override for developer images.
440 if (!IsCrashTestInProgress() && IsDeveloperImage()) {
441 // Check if there's an override to indicate we should indeed collect
442 // chrome crashes. This allows the crashes to still be tracked when
443 // they occur in autotests. See "crosbug.com/17987".
444 if (file_util::PathExists(FilePath(kCollectChromeFile)))
445 return true;
446 }
447 // We default to ignoring chrome crashes.
448 return false;
449}
450
451bool CrashCollector::IsUserSpecificDirectoryEnabled() {
452 return !ShouldHandleChromeCrashes();
453}