blob: b60f7ba694fdda6c9a64cbc22a43f898bf44d004 [file] [log] [blame]
Ken Mixter03403162010-08-18 15:23:16 -07001// Copyright (c) 2010 The Chromium OS 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 "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.
Ken Mixter03403162010-08-18 15:23:16 -070013
Ken Mixteree849c52010-09-30 15:30:10 -070014#include <set>
15
Ken Mixter9b346472010-11-07 13:45:45 -080016#include "base/eintr_wrapper.h"
Ken Mixter03403162010-08-18 15:23:16 -070017#include "base/file_util.h"
18#include "base/logging.h"
Chris Masone8a68c7c2011-05-14 11:44:04 -070019#include "base/string_split.h"
Ken Mixter03403162010-08-18 15:23:16 -070020#include "base/string_util.h"
Ken Mixtera3249322011-03-03 08:47:38 -080021#include "chromeos/process.h"
Ken Mixter03403162010-08-18 15:23:16 -070022
Michael Krebs4fe30db2011-08-05 13:54:52 -070023static const char kCollectChromeFile[] =
24 "/mnt/stateful_partition/etc/collect_chrome_crashes";
25static const char kCrashTestInProgressPath[] = "/tmp/crash-test-in-progress";
Ken Mixter03403162010-08-18 15:23:16 -070026static const char kDefaultUserName[] = "chronos";
Michael Krebs4fe30db2011-08-05 13:54:52 -070027static const char kLeaveCoreFile[] = "/root/.leave_core";
Ken Mixteree849c52010-09-30 15:30:10 -070028static const char kLsbRelease[] = "/etc/lsb-release";
Ken Mixterc49dbd42010-12-14 17:44:11 -080029static const char kShellPath[] = "/bin/sh";
Ken Mixter03403162010-08-18 15:23:16 -070030static const char kSystemCrashPath[] = "/var/spool/crash";
31static const char kUserCrashPath[] = "/home/chronos/user/crash";
32
33// Directory mode of the user crash spool directory.
34static const mode_t kUserCrashPathMode = 0755;
35
36// Directory mode of the system crash spool directory.
37static const mode_t kSystemCrashPathMode = 01755;
38
39static const uid_t kRootOwner = 0;
40static const uid_t kRootGroup = 0;
41
Ken Mixterda5db7a2010-09-17 13:50:42 -070042// Maximum crash reports per crash spool directory. Note that this is
43// a separate maximum from the maximum rate at which we upload these
44// diagnostics. The higher this rate is, the more space we allow for
45// core files, minidumps, and kcrash logs, and equivalently the more
46// processor and I/O bandwidth we dedicate to handling these crashes when
47// many occur at once. Also note that if core files are configured to
48// be left on the file system, we stop adding crashes when either the
49// number of core files or minidumps reaches this number.
50const int CrashCollector::kMaxCrashDirectorySize = 32;
Ken Mixter04ec10f2010-08-26 16:02:02 -070051
Ken Mixterafcf8082010-10-26 14:45:01 -070052CrashCollector::CrashCollector()
53 : forced_crash_directory_(NULL),
54 lsb_release_(kLsbRelease) {
Ken Mixter03403162010-08-18 15:23:16 -070055}
56
57CrashCollector::~CrashCollector() {
58}
59
60void CrashCollector::Initialize(
61 CrashCollector::CountCrashFunction count_crash_function,
Ken Mixtera3249322011-03-03 08:47:38 -080062 CrashCollector::IsFeedbackAllowedFunction is_feedback_allowed_function) {
Ken Mixter03403162010-08-18 15:23:16 -070063 CHECK(count_crash_function != NULL);
64 CHECK(is_feedback_allowed_function != NULL);
Ken Mixter03403162010-08-18 15:23:16 -070065
66 count_crash_function_ = count_crash_function;
67 is_feedback_allowed_function_ = is_feedback_allowed_function;
Ken Mixter03403162010-08-18 15:23:16 -070068}
69
Ken Mixter9b346472010-11-07 13:45:45 -080070int CrashCollector::WriteNewFile(const FilePath &filename,
71 const char *data,
72 int size) {
73 int fd = HANDLE_EINTR(open(filename.value().c_str(),
74 O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0666));
75 if (fd < 0) {
76 return -1;
77 }
78
79 int rv = file_util::WriteFileDescriptor(fd, data, size);
80 HANDLE_EINTR(close(fd));
81 return rv;
82}
83
Ken Mixteree849c52010-09-30 15:30:10 -070084std::string CrashCollector::Sanitize(const std::string &name) {
85 std::string result = name;
86 for (size_t i = 0; i < name.size(); ++i) {
87 if (!isalnum(result[i]) && result[i] != '_')
88 result[i] = '_';
89 }
90 return result;
91}
92
Ken Mixter03403162010-08-18 15:23:16 -070093std::string CrashCollector::FormatDumpBasename(const std::string &exec_name,
94 time_t timestamp,
95 pid_t pid) {
96 struct tm tm;
97 localtime_r(&timestamp, &tm);
Ken Mixteree849c52010-09-30 15:30:10 -070098 std::string sanitized_exec_name = Sanitize(exec_name);
Ken Mixter03403162010-08-18 15:23:16 -070099 return StringPrintf("%s.%04d%02d%02d.%02d%02d%02d.%d",
Ken Mixteree849c52010-09-30 15:30:10 -0700100 sanitized_exec_name.c_str(),
Ken Mixter03403162010-08-18 15:23:16 -0700101 tm.tm_year + 1900,
102 tm.tm_mon + 1,
103 tm.tm_mday,
104 tm.tm_hour,
105 tm.tm_min,
106 tm.tm_sec,
107 pid);
108}
109
Ken Mixter207694d2010-10-28 15:42:37 -0700110FilePath CrashCollector::GetCrashPath(const FilePath &crash_directory,
111 const std::string &basename,
112 const std::string &extension) {
113 return crash_directory.Append(StringPrintf("%s.%s",
114 basename.c_str(),
115 extension.c_str()));
116}
117
Ken Mixter03403162010-08-18 15:23:16 -0700118FilePath CrashCollector::GetCrashDirectoryInfo(
119 uid_t process_euid,
120 uid_t default_user_id,
121 gid_t default_user_group,
122 mode_t *mode,
123 uid_t *directory_owner,
124 gid_t *directory_group) {
Michael Krebs4fe30db2011-08-05 13:54:52 -0700125 // TODO(mkrebs): This can go away once Chrome crashes are handled
126 // normally (see crosbug.com/5872).
127 // Check if the user crash directory should be used. If we are
128 // collecting chrome crashes during autotesting, we want to put them in
129 // the system crash directory so they are outside the cryptohome -- in
130 // case we are being run during logout (see crosbug.com/18637).
131 if (process_euid == default_user_id && IsUserSpecificDirectoryEnabled()) {
Ken Mixter03403162010-08-18 15:23:16 -0700132 *mode = kUserCrashPathMode;
133 *directory_owner = default_user_id;
134 *directory_group = default_user_group;
135 return FilePath(kUserCrashPath);
136 } else {
137 *mode = kSystemCrashPathMode;
138 *directory_owner = kRootOwner;
139 *directory_group = kRootGroup;
140 return FilePath(kSystemCrashPath);
141 }
142}
143
144bool CrashCollector::GetUserInfoFromName(const std::string &name,
145 uid_t *uid,
146 gid_t *gid) {
147 char storage[256];
148 struct passwd passwd_storage;
149 struct passwd *passwd_result = NULL;
150
151 if (getpwnam_r(name.c_str(), &passwd_storage, storage, sizeof(storage),
152 &passwd_result) != 0 || passwd_result == NULL) {
Ken Mixtera3249322011-03-03 08:47:38 -0800153 LOG(ERROR) << "Cannot find user named " << name;
Ken Mixter03403162010-08-18 15:23:16 -0700154 return false;
155 }
156
157 *uid = passwd_result->pw_uid;
158 *gid = passwd_result->pw_gid;
159 return true;
160}
161
162bool CrashCollector::GetCreatedCrashDirectoryByEuid(uid_t euid,
Ken Mixter207694d2010-10-28 15:42:37 -0700163 FilePath *crash_directory,
164 bool *out_of_capacity) {
Ken Mixter03403162010-08-18 15:23:16 -0700165 uid_t default_user_id;
166 gid_t default_user_group;
167
Ken Mixter207694d2010-10-28 15:42:37 -0700168 if (out_of_capacity != NULL) *out_of_capacity = false;
169
Ken Mixter03403162010-08-18 15:23:16 -0700170 // For testing.
171 if (forced_crash_directory_ != NULL) {
172 *crash_directory = FilePath(forced_crash_directory_);
173 return true;
174 }
175
176 if (!GetUserInfoFromName(kDefaultUserName,
177 &default_user_id,
178 &default_user_group)) {
Ken Mixtera3249322011-03-03 08:47:38 -0800179 LOG(ERROR) << "Could not find default user info";
Ken Mixter03403162010-08-18 15:23:16 -0700180 return false;
181 }
182 mode_t directory_mode;
183 uid_t directory_owner;
184 gid_t directory_group;
185 *crash_directory =
186 GetCrashDirectoryInfo(euid,
187 default_user_id,
188 default_user_group,
189 &directory_mode,
190 &directory_owner,
191 &directory_group);
192
193 if (!file_util::PathExists(*crash_directory)) {
194 // Create the spool directory with the appropriate mode (regardless of
195 // umask) and ownership.
196 mode_t old_mask = umask(0);
197 if (mkdir(crash_directory->value().c_str(), directory_mode) < 0 ||
198 chown(crash_directory->value().c_str(),
199 directory_owner,
200 directory_group) < 0) {
Ken Mixtera3249322011-03-03 08:47:38 -0800201 LOG(ERROR) << "Unable to create appropriate crash directory";
Ken Mixter03403162010-08-18 15:23:16 -0700202 return false;
203 }
204 umask(old_mask);
205 }
206
207 if (!file_util::PathExists(*crash_directory)) {
Ken Mixtera3249322011-03-03 08:47:38 -0800208 LOG(ERROR) << "Unable to create crash directory "
209 << crash_directory->value().c_str();
Ken Mixter03403162010-08-18 15:23:16 -0700210 return false;
211 }
212
Ken Mixter04ec10f2010-08-26 16:02:02 -0700213 if (!CheckHasCapacity(*crash_directory)) {
Ken Mixter207694d2010-10-28 15:42:37 -0700214 if (out_of_capacity != NULL) *out_of_capacity = true;
Ken Mixter04ec10f2010-08-26 16:02:02 -0700215 return false;
216 }
217
Ken Mixter03403162010-08-18 15:23:16 -0700218 return true;
219}
Ken Mixter04ec10f2010-08-26 16:02:02 -0700220
221// Return true if the given crash directory has not already reached
222// maximum capacity.
223bool CrashCollector::CheckHasCapacity(const FilePath &crash_directory) {
224 DIR* dir = opendir(crash_directory.value().c_str());
225 if (!dir) {
226 return false;
227 }
228 struct dirent ent_buf;
229 struct dirent* ent;
Ken Mixter04ec10f2010-08-26 16:02:02 -0700230 bool full = false;
Ken Mixteree849c52010-09-30 15:30:10 -0700231 std::set<std::string> basenames;
Ken Mixter04ec10f2010-08-26 16:02:02 -0700232 while (readdir_r(dir, &ent_buf, &ent) == 0 && ent != NULL) {
233 if ((strcmp(ent->d_name, ".") == 0) ||
234 (strcmp(ent->d_name, "..") == 0))
235 continue;
236
Ken Mixteree849c52010-09-30 15:30:10 -0700237 std::string filename(ent->d_name);
238 size_t last_dot = filename.rfind(".");
239 std::string basename;
240 // If there is a valid looking extension, use the base part of the
241 // name. If the only dot is the first byte (aka a dot file), treat
242 // it as unique to avoid allowing a directory full of dot files
243 // from accumulating.
244 if (last_dot != std::string::npos && last_dot != 0)
245 basename = filename.substr(0, last_dot);
246 else
247 basename = filename;
248 basenames.insert(basename);
Ken Mixter04ec10f2010-08-26 16:02:02 -0700249
Ken Mixteree849c52010-09-30 15:30:10 -0700250 if (basenames.size() >= static_cast<size_t>(kMaxCrashDirectorySize)) {
Ken Mixtera3249322011-03-03 08:47:38 -0800251 LOG(WARNING) << "Crash directory " << crash_directory.value()
252 << " already full with " << kMaxCrashDirectorySize
253 << " pending reports";
Ken Mixter04ec10f2010-08-26 16:02:02 -0700254 full = true;
255 break;
256 }
257 }
258 closedir(dir);
259 return !full;
260}
Ken Mixteree849c52010-09-30 15:30:10 -0700261
Ken Mixterc49dbd42010-12-14 17:44:11 -0800262bool CrashCollector::IsCommentLine(const std::string &line) {
263 size_t found = line.find_first_not_of(" ");
264 return found != std::string::npos && line[found] == '#';
265}
266
Ken Mixteree849c52010-09-30 15:30:10 -0700267bool CrashCollector::ReadKeyValueFile(
268 const FilePath &path,
269 const char separator,
270 std::map<std::string, std::string> *dictionary) {
271 std::string contents;
272 if (!file_util::ReadFileToString(path, &contents)) {
273 return false;
274 }
275 typedef std::vector<std::string> StringVector;
276 StringVector lines;
Chris Masone3ba6c5b2011-05-13 16:57:09 -0700277 base::SplitString(contents, '\n', &lines);
Ken Mixteree849c52010-09-30 15:30:10 -0700278 bool any_errors = false;
279 for (StringVector::iterator line = lines.begin(); line != lines.end();
280 ++line) {
281 // Allow empty strings.
282 if (line->empty())
283 continue;
Ken Mixterc49dbd42010-12-14 17:44:11 -0800284 // Allow comment lines.
285 if (IsCommentLine(*line))
286 continue;
Ken Mixteree849c52010-09-30 15:30:10 -0700287 StringVector sides;
Chris Masone3ba6c5b2011-05-13 16:57:09 -0700288 base::SplitString(*line, separator, &sides);
Ken Mixteree849c52010-09-30 15:30:10 -0700289 if (sides.size() != 2) {
290 any_errors = true;
291 continue;
292 }
293 dictionary->insert(std::pair<std::string, std::string>(sides[0], sides[1]));
294 }
295 return !any_errors;
296}
297
Ken Mixterc49dbd42010-12-14 17:44:11 -0800298bool CrashCollector::GetLogContents(const FilePath &config_path,
299 const std::string &exec_name,
300 const FilePath &output_file) {
301 std::map<std::string, std::string> log_commands;
302 if (!ReadKeyValueFile(config_path, ':', &log_commands)) {
Ken Mixtera3249322011-03-03 08:47:38 -0800303 LOG(INFO) << "Unable to read log configuration file "
304 << config_path.value();
Ken Mixterc49dbd42010-12-14 17:44:11 -0800305 return false;
306 }
307
308 if (log_commands.find(exec_name) == log_commands.end())
309 return false;
310
Ken Mixtera3249322011-03-03 08:47:38 -0800311 chromeos::ProcessImpl diag_process;
312 diag_process.AddArg(kShellPath);
Ken Mixterc49dbd42010-12-14 17:44:11 -0800313 std::string shell_command = log_commands[exec_name];
Ken Mixtera3249322011-03-03 08:47:38 -0800314 diag_process.AddStringOption("-c", shell_command);
315 diag_process.RedirectOutput(output_file.value());
Ken Mixterc49dbd42010-12-14 17:44:11 -0800316
Ken Mixtera3249322011-03-03 08:47:38 -0800317 int result = diag_process.Run();
318 if (result != 0) {
319 LOG(INFO) << "Running shell command " << shell_command << "failed with: "
320 << result;
Ken Mixterc49dbd42010-12-14 17:44:11 -0800321 return false;
322 }
323 return true;
324}
325
Ken Mixterafcf8082010-10-26 14:45:01 -0700326void CrashCollector::AddCrashMetaData(const std::string &key,
327 const std::string &value) {
328 extra_metadata_.append(StringPrintf("%s=%s\n", key.c_str(), value.c_str()));
329}
330
Ken Mixteree849c52010-09-30 15:30:10 -0700331void CrashCollector::WriteCrashMetaData(const FilePath &meta_path,
Ken Mixterc909b692010-10-18 12:26:05 -0700332 const std::string &exec_name,
333 const std::string &payload_path) {
Ken Mixteree849c52010-09-30 15:30:10 -0700334 std::map<std::string, std::string> contents;
Ken Mixterafcf8082010-10-26 14:45:01 -0700335 if (!ReadKeyValueFile(FilePath(std::string(lsb_release_)), '=', &contents)) {
Ken Mixtera3249322011-03-03 08:47:38 -0800336 LOG(ERROR) << "Problem parsing " << lsb_release_;
Ken Mixteree849c52010-09-30 15:30:10 -0700337 // Even though there was some failure, take as much as we could read.
338 }
339 std::string version("unknown");
340 std::map<std::string, std::string>::iterator i;
341 if ((i = contents.find("CHROMEOS_RELEASE_VERSION")) != contents.end()) {
342 version = i->second;
343 }
Ken Mixterc909b692010-10-18 12:26:05 -0700344 int64 payload_size = -1;
345 file_util::GetFileSize(FilePath(payload_path), &payload_size);
Ken Mixterafcf8082010-10-26 14:45:01 -0700346 std::string meta_data = StringPrintf("%sexec_name=%s\n"
Ken Mixteree849c52010-09-30 15:30:10 -0700347 "ver=%s\n"
Ken Mixter207694d2010-10-28 15:42:37 -0700348 "payload=%s\n"
Ken Mixterc909b692010-10-18 12:26:05 -0700349 "payload_size=%lld\n"
Ken Mixteree849c52010-09-30 15:30:10 -0700350 "done=1\n",
Ken Mixterafcf8082010-10-26 14:45:01 -0700351 extra_metadata_.c_str(),
Ken Mixteree849c52010-09-30 15:30:10 -0700352 exec_name.c_str(),
Ken Mixterc909b692010-10-18 12:26:05 -0700353 version.c_str(),
Ken Mixter207694d2010-10-28 15:42:37 -0700354 payload_path.c_str(),
Ken Mixterc909b692010-10-18 12:26:05 -0700355 payload_size);
Ken Mixter9b346472010-11-07 13:45:45 -0800356 // We must use WriteNewFile instead of file_util::WriteFile as we
357 // do not want to write with root access to a symlink that an attacker
358 // might have created.
359 if (WriteNewFile(meta_path, meta_data.c_str(), meta_data.size()) < 0) {
Ken Mixtera3249322011-03-03 08:47:38 -0800360 LOG(ERROR) << "Unable to write " << meta_path.value();
Ken Mixteree849c52010-09-30 15:30:10 -0700361 }
362}
Thieu Le1652fb22011-03-03 12:14:43 -0800363
364bool CrashCollector::IsCrashTestInProgress() {
365 return file_util::PathExists(FilePath(kCrashTestInProgressPath));
366}
Michael Krebs4fe30db2011-08-05 13:54:52 -0700367
368bool CrashCollector::IsDeveloperImage() {
369 // If we're testing crash reporter itself, we don't want to special-case
370 // for developer images.
371 if (IsCrashTestInProgress())
372 return false;
373 return file_util::PathExists(FilePath(kLeaveCoreFile));
374}
375
376bool CrashCollector::ShouldHandleChromeCrashes() {
377 // If we're testing crash reporter itself, we don't want to allow an
378 // override for chrome crashes. And, let's be conservative and only
379 // allow an override for developer images.
380 if (!IsCrashTestInProgress() && IsDeveloperImage()) {
381 // Check if there's an override to indicate we should indeed collect
382 // chrome crashes. This allows the crashes to still be tracked when
383 // they occur in autotests. See "crosbug.com/17987".
384 if (file_util::PathExists(FilePath(kCollectChromeFile)))
385 return true;
386 }
387 // We default to ignoring chrome crashes.
388 return false;
389}
390
391bool CrashCollector::IsUserSpecificDirectoryEnabled() {
392 return !ShouldHandleChromeCrashes();
393}