blob: 89fe41de46cf2c81ab6b65472fa137f42317f654 [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"
19#include "base/string_util.h"
20#include "crash-reporter/system_logging.h"
21
22static const char kDefaultUserName[] = "chronos";
Ken Mixteree849c52010-09-30 15:30:10 -070023static const char kLsbRelease[] = "/etc/lsb-release";
Ken Mixterc49dbd42010-12-14 17:44:11 -080024static const char kShellPath[] = "/bin/sh";
Ken Mixter03403162010-08-18 15:23:16 -070025static const char kSystemCrashPath[] = "/var/spool/crash";
26static const char kUserCrashPath[] = "/home/chronos/user/crash";
27
28// Directory mode of the user crash spool directory.
29static const mode_t kUserCrashPathMode = 0755;
30
31// Directory mode of the system crash spool directory.
32static const mode_t kSystemCrashPathMode = 01755;
33
34static const uid_t kRootOwner = 0;
35static const uid_t kRootGroup = 0;
36
Ken Mixterda5db7a2010-09-17 13:50:42 -070037// Maximum crash reports per crash spool directory. Note that this is
38// a separate maximum from the maximum rate at which we upload these
39// diagnostics. The higher this rate is, the more space we allow for
40// core files, minidumps, and kcrash logs, and equivalently the more
41// processor and I/O bandwidth we dedicate to handling these crashes when
42// many occur at once. Also note that if core files are configured to
43// be left on the file system, we stop adding crashes when either the
44// number of core files or minidumps reaches this number.
45const int CrashCollector::kMaxCrashDirectorySize = 32;
Ken Mixter04ec10f2010-08-26 16:02:02 -070046
Ken Mixterafcf8082010-10-26 14:45:01 -070047CrashCollector::CrashCollector()
48 : forced_crash_directory_(NULL),
49 lsb_release_(kLsbRelease) {
Ken Mixter03403162010-08-18 15:23:16 -070050}
51
52CrashCollector::~CrashCollector() {
53}
54
55void CrashCollector::Initialize(
56 CrashCollector::CountCrashFunction count_crash_function,
57 CrashCollector::IsFeedbackAllowedFunction is_feedback_allowed_function,
58 SystemLogging *logger) {
59 CHECK(count_crash_function != NULL);
60 CHECK(is_feedback_allowed_function != NULL);
61 CHECK(logger != NULL);
62
63 count_crash_function_ = count_crash_function;
64 is_feedback_allowed_function_ = is_feedback_allowed_function;
65 logger_ = logger;
66}
67
Ken Mixter9b346472010-11-07 13:45:45 -080068int CrashCollector::WriteNewFile(const FilePath &filename,
69 const char *data,
70 int size) {
71 int fd = HANDLE_EINTR(open(filename.value().c_str(),
72 O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0666));
73 if (fd < 0) {
74 return -1;
75 }
76
77 int rv = file_util::WriteFileDescriptor(fd, data, size);
78 HANDLE_EINTR(close(fd));
79 return rv;
80}
81
82int CrashCollector::ForkExecAndPipe(std::vector<const char *> &arguments,
83 const char *output_file) {
84 // Copy off a writeable version of arguments.
85 scoped_array<char*> argv(new char *[arguments.size() + 1]);
86 int total_args_size = 0;
87 for (size_t i = 0; i < arguments.size(); ++i) {
88 if (arguments[i] == NULL) {
89 logger_->LogError("Bad parameter");
90 return -1;
91 }
92 total_args_size += strlen(arguments[i]) + 1;
93 }
94 scoped_array<char> buffer(new char[total_args_size]);
95 char *buffer_pointer = &buffer[0];
96
97 for (size_t i = 0; i < arguments.size(); ++i) {
98 argv[i] = buffer_pointer;
99 strcpy(buffer_pointer, arguments[i]);
100 buffer_pointer += strlen(arguments[i]);
101 *buffer_pointer = '\0';
102 ++buffer_pointer;
103 }
104 argv[arguments.size()] = NULL;
105
106 int pid = fork();
107 if (pid < 0) {
108 logger_->LogError("Fork failed: %d", errno);
109 return -1;
110 }
111
112 if (pid == 0) {
Ken Mixterc49dbd42010-12-14 17:44:11 -0800113 int output_handle = HANDLE_EINTR(creat(output_file, 0600));
Ken Mixter9b346472010-11-07 13:45:45 -0800114 if (output_handle < 0) {
115 logger_->LogError("Could not create %s: %d", output_file, errno);
116 // Avoid exit() to avoid atexit handlers from parent.
117 _exit(127);
118 }
119 dup2(output_handle, 1);
120 dup2(output_handle, 2);
121 execv(argv[0], &argv[0]);
122 logger_->LogError("Exec failed: %d", errno);
123 _exit(127);
124 }
125
126 int status = 0;
127 if (HANDLE_EINTR(waitpid(pid, &status, 0)) < 0) {
128 logger_->LogError("Problem waiting for pid: %d", errno);
129 return -1;
130 }
131 if (!WIFEXITED(status)) {
132 logger_->LogError("Process did not exit normally: %d", status);
133 return -1;
134 }
135 return WEXITSTATUS(status);
136}
137
Ken Mixteree849c52010-09-30 15:30:10 -0700138std::string CrashCollector::Sanitize(const std::string &name) {
139 std::string result = name;
140 for (size_t i = 0; i < name.size(); ++i) {
141 if (!isalnum(result[i]) && result[i] != '_')
142 result[i] = '_';
143 }
144 return result;
145}
146
Ken Mixter03403162010-08-18 15:23:16 -0700147std::string CrashCollector::FormatDumpBasename(const std::string &exec_name,
148 time_t timestamp,
149 pid_t pid) {
150 struct tm tm;
151 localtime_r(&timestamp, &tm);
Ken Mixteree849c52010-09-30 15:30:10 -0700152 std::string sanitized_exec_name = Sanitize(exec_name);
Ken Mixter03403162010-08-18 15:23:16 -0700153 return StringPrintf("%s.%04d%02d%02d.%02d%02d%02d.%d",
Ken Mixteree849c52010-09-30 15:30:10 -0700154 sanitized_exec_name.c_str(),
Ken Mixter03403162010-08-18 15:23:16 -0700155 tm.tm_year + 1900,
156 tm.tm_mon + 1,
157 tm.tm_mday,
158 tm.tm_hour,
159 tm.tm_min,
160 tm.tm_sec,
161 pid);
162}
163
Ken Mixter207694d2010-10-28 15:42:37 -0700164FilePath CrashCollector::GetCrashPath(const FilePath &crash_directory,
165 const std::string &basename,
166 const std::string &extension) {
167 return crash_directory.Append(StringPrintf("%s.%s",
168 basename.c_str(),
169 extension.c_str()));
170}
171
Ken Mixter03403162010-08-18 15:23:16 -0700172FilePath CrashCollector::GetCrashDirectoryInfo(
173 uid_t process_euid,
174 uid_t default_user_id,
175 gid_t default_user_group,
176 mode_t *mode,
177 uid_t *directory_owner,
178 gid_t *directory_group) {
179 if (process_euid == default_user_id) {
180 *mode = kUserCrashPathMode;
181 *directory_owner = default_user_id;
182 *directory_group = default_user_group;
183 return FilePath(kUserCrashPath);
184 } else {
185 *mode = kSystemCrashPathMode;
186 *directory_owner = kRootOwner;
187 *directory_group = kRootGroup;
188 return FilePath(kSystemCrashPath);
189 }
190}
191
192bool CrashCollector::GetUserInfoFromName(const std::string &name,
193 uid_t *uid,
194 gid_t *gid) {
195 char storage[256];
196 struct passwd passwd_storage;
197 struct passwd *passwd_result = NULL;
198
199 if (getpwnam_r(name.c_str(), &passwd_storage, storage, sizeof(storage),
200 &passwd_result) != 0 || passwd_result == NULL) {
201 logger_->LogError("Cannot find user named %s", name.c_str());
202 return false;
203 }
204
205 *uid = passwd_result->pw_uid;
206 *gid = passwd_result->pw_gid;
207 return true;
208}
209
210bool CrashCollector::GetCreatedCrashDirectoryByEuid(uid_t euid,
Ken Mixter207694d2010-10-28 15:42:37 -0700211 FilePath *crash_directory,
212 bool *out_of_capacity) {
Ken Mixter03403162010-08-18 15:23:16 -0700213 uid_t default_user_id;
214 gid_t default_user_group;
215
Ken Mixter207694d2010-10-28 15:42:37 -0700216 if (out_of_capacity != NULL) *out_of_capacity = false;
217
Ken Mixter03403162010-08-18 15:23:16 -0700218 // For testing.
219 if (forced_crash_directory_ != NULL) {
220 *crash_directory = FilePath(forced_crash_directory_);
221 return true;
222 }
223
224 if (!GetUserInfoFromName(kDefaultUserName,
225 &default_user_id,
226 &default_user_group)) {
227 logger_->LogError("Could not find default user info");
228 return false;
229 }
230 mode_t directory_mode;
231 uid_t directory_owner;
232 gid_t directory_group;
233 *crash_directory =
234 GetCrashDirectoryInfo(euid,
235 default_user_id,
236 default_user_group,
237 &directory_mode,
238 &directory_owner,
239 &directory_group);
240
241 if (!file_util::PathExists(*crash_directory)) {
242 // Create the spool directory with the appropriate mode (regardless of
243 // umask) and ownership.
244 mode_t old_mask = umask(0);
245 if (mkdir(crash_directory->value().c_str(), directory_mode) < 0 ||
246 chown(crash_directory->value().c_str(),
247 directory_owner,
248 directory_group) < 0) {
249 logger_->LogError("Unable to create appropriate crash directory");
250 return false;
251 }
252 umask(old_mask);
253 }
254
255 if (!file_util::PathExists(*crash_directory)) {
256 logger_->LogError("Unable to create crash directory %s",
257 crash_directory->value().c_str());
258 return false;
259 }
260
Ken Mixter04ec10f2010-08-26 16:02:02 -0700261 if (!CheckHasCapacity(*crash_directory)) {
Ken Mixter207694d2010-10-28 15:42:37 -0700262 if (out_of_capacity != NULL) *out_of_capacity = true;
Ken Mixter04ec10f2010-08-26 16:02:02 -0700263 return false;
264 }
265
Ken Mixter03403162010-08-18 15:23:16 -0700266 return true;
267}
Ken Mixter04ec10f2010-08-26 16:02:02 -0700268
269// Return true if the given crash directory has not already reached
270// maximum capacity.
271bool CrashCollector::CheckHasCapacity(const FilePath &crash_directory) {
272 DIR* dir = opendir(crash_directory.value().c_str());
273 if (!dir) {
274 return false;
275 }
276 struct dirent ent_buf;
277 struct dirent* ent;
Ken Mixter04ec10f2010-08-26 16:02:02 -0700278 bool full = false;
Ken Mixteree849c52010-09-30 15:30:10 -0700279 std::set<std::string> basenames;
Ken Mixter04ec10f2010-08-26 16:02:02 -0700280 while (readdir_r(dir, &ent_buf, &ent) == 0 && ent != NULL) {
281 if ((strcmp(ent->d_name, ".") == 0) ||
282 (strcmp(ent->d_name, "..") == 0))
283 continue;
284
Ken Mixteree849c52010-09-30 15:30:10 -0700285 std::string filename(ent->d_name);
286 size_t last_dot = filename.rfind(".");
287 std::string basename;
288 // If there is a valid looking extension, use the base part of the
289 // name. If the only dot is the first byte (aka a dot file), treat
290 // it as unique to avoid allowing a directory full of dot files
291 // from accumulating.
292 if (last_dot != std::string::npos && last_dot != 0)
293 basename = filename.substr(0, last_dot);
294 else
295 basename = filename;
296 basenames.insert(basename);
Ken Mixter04ec10f2010-08-26 16:02:02 -0700297
Ken Mixteree849c52010-09-30 15:30:10 -0700298 if (basenames.size() >= static_cast<size_t>(kMaxCrashDirectorySize)) {
Ken Mixter04ec10f2010-08-26 16:02:02 -0700299 logger_->LogWarning(
300 "Crash directory %s already full with %d pending reports",
301 crash_directory.value().c_str(),
302 kMaxCrashDirectorySize);
303 full = true;
304 break;
305 }
306 }
307 closedir(dir);
308 return !full;
309}
Ken Mixteree849c52010-09-30 15:30:10 -0700310
Ken Mixterc49dbd42010-12-14 17:44:11 -0800311bool CrashCollector::IsCommentLine(const std::string &line) {
312 size_t found = line.find_first_not_of(" ");
313 return found != std::string::npos && line[found] == '#';
314}
315
Ken Mixteree849c52010-09-30 15:30:10 -0700316bool CrashCollector::ReadKeyValueFile(
317 const FilePath &path,
318 const char separator,
319 std::map<std::string, std::string> *dictionary) {
320 std::string contents;
321 if (!file_util::ReadFileToString(path, &contents)) {
322 return false;
323 }
324 typedef std::vector<std::string> StringVector;
325 StringVector lines;
326 SplitString(contents, '\n', &lines);
327 bool any_errors = false;
328 for (StringVector::iterator line = lines.begin(); line != lines.end();
329 ++line) {
330 // Allow empty strings.
331 if (line->empty())
332 continue;
Ken Mixterc49dbd42010-12-14 17:44:11 -0800333 // Allow comment lines.
334 if (IsCommentLine(*line))
335 continue;
Ken Mixteree849c52010-09-30 15:30:10 -0700336 StringVector sides;
337 SplitString(*line, separator, &sides);
338 if (sides.size() != 2) {
339 any_errors = true;
340 continue;
341 }
342 dictionary->insert(std::pair<std::string, std::string>(sides[0], sides[1]));
343 }
344 return !any_errors;
345}
346
Ken Mixterc49dbd42010-12-14 17:44:11 -0800347bool CrashCollector::GetLogContents(const FilePath &config_path,
348 const std::string &exec_name,
349 const FilePath &output_file) {
350 std::map<std::string, std::string> log_commands;
351 if (!ReadKeyValueFile(config_path, ':', &log_commands)) {
352 logger_->LogInfo("Unable to read log configuration file %s",
353 config_path.value().c_str());
354 return false;
355 }
356
357 if (log_commands.find(exec_name) == log_commands.end())
358 return false;
359
360 std::vector<const char *> command;
361 command.push_back(kShellPath);
362 command.push_back("-c");
363 std::string shell_command = log_commands[exec_name];
364 command.push_back(shell_command.c_str());
365
366 int fork_result = ForkExecAndPipe(command, output_file.value().c_str());
367 if (fork_result != 0) {
368 logger_->LogInfo("Running shell command %s failed with: %d",
369 shell_command.c_str(), fork_result);
370 return false;
371 }
372 return true;
373}
374
Ken Mixterafcf8082010-10-26 14:45:01 -0700375void CrashCollector::AddCrashMetaData(const std::string &key,
376 const std::string &value) {
377 extra_metadata_.append(StringPrintf("%s=%s\n", key.c_str(), value.c_str()));
378}
379
Ken Mixteree849c52010-09-30 15:30:10 -0700380void CrashCollector::WriteCrashMetaData(const FilePath &meta_path,
Ken Mixterc909b692010-10-18 12:26:05 -0700381 const std::string &exec_name,
382 const std::string &payload_path) {
Ken Mixteree849c52010-09-30 15:30:10 -0700383 std::map<std::string, std::string> contents;
Ken Mixterafcf8082010-10-26 14:45:01 -0700384 if (!ReadKeyValueFile(FilePath(std::string(lsb_release_)), '=', &contents)) {
385 logger_->LogError("Problem parsing %s", lsb_release_);
Ken Mixteree849c52010-09-30 15:30:10 -0700386 // Even though there was some failure, take as much as we could read.
387 }
388 std::string version("unknown");
389 std::map<std::string, std::string>::iterator i;
390 if ((i = contents.find("CHROMEOS_RELEASE_VERSION")) != contents.end()) {
391 version = i->second;
392 }
Ken Mixterc909b692010-10-18 12:26:05 -0700393 int64 payload_size = -1;
394 file_util::GetFileSize(FilePath(payload_path), &payload_size);
Ken Mixterafcf8082010-10-26 14:45:01 -0700395 std::string meta_data = StringPrintf("%sexec_name=%s\n"
Ken Mixteree849c52010-09-30 15:30:10 -0700396 "ver=%s\n"
Ken Mixter207694d2010-10-28 15:42:37 -0700397 "payload=%s\n"
Ken Mixterc909b692010-10-18 12:26:05 -0700398 "payload_size=%lld\n"
Ken Mixteree849c52010-09-30 15:30:10 -0700399 "done=1\n",
Ken Mixterafcf8082010-10-26 14:45:01 -0700400 extra_metadata_.c_str(),
Ken Mixteree849c52010-09-30 15:30:10 -0700401 exec_name.c_str(),
Ken Mixterc909b692010-10-18 12:26:05 -0700402 version.c_str(),
Ken Mixter207694d2010-10-28 15:42:37 -0700403 payload_path.c_str(),
Ken Mixterc909b692010-10-18 12:26:05 -0700404 payload_size);
Ken Mixter9b346472010-11-07 13:45:45 -0800405 // We must use WriteNewFile instead of file_util::WriteFile as we
406 // do not want to write with root access to a symlink that an attacker
407 // might have created.
408 if (WriteNewFile(meta_path, meta_data.c_str(), meta_data.size()) < 0) {
Ken Mixteree849c52010-09-30 15:30:10 -0700409 logger_->LogError("Unable to write %s", meta_path.value().c_str());
410 }
411}