blob: 5247d916ca0339a2db8df013e89b882dd53f7a0e [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"
Ken Mixtera3249322011-03-03 08:47:38 -080020#include "chromeos/process.h"
Ken Mixter03403162010-08-18 15:23:16 -070021
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";
Thieu Le1652fb22011-03-03 12:14:43 -080027static const char kCrashTestInProgressPath[] = "/tmp/crash-test-in-progress";
Ken Mixter03403162010-08-18 15:23:16 -070028
29// Directory mode of the user crash spool directory.
30static const mode_t kUserCrashPathMode = 0755;
31
32// Directory mode of the system crash spool directory.
33static const mode_t kSystemCrashPathMode = 01755;
34
35static const uid_t kRootOwner = 0;
36static const uid_t kRootGroup = 0;
37
Ken Mixterda5db7a2010-09-17 13:50:42 -070038// Maximum crash reports per crash spool directory. Note that this is
39// a separate maximum from the maximum rate at which we upload these
40// diagnostics. The higher this rate is, the more space we allow for
41// core files, minidumps, and kcrash logs, and equivalently the more
42// processor and I/O bandwidth we dedicate to handling these crashes when
43// many occur at once. Also note that if core files are configured to
44// be left on the file system, we stop adding crashes when either the
45// number of core files or minidumps reaches this number.
46const int CrashCollector::kMaxCrashDirectorySize = 32;
Ken Mixter04ec10f2010-08-26 16:02:02 -070047
Ken Mixterafcf8082010-10-26 14:45:01 -070048CrashCollector::CrashCollector()
49 : forced_crash_directory_(NULL),
50 lsb_release_(kLsbRelease) {
Ken Mixter03403162010-08-18 15:23:16 -070051}
52
53CrashCollector::~CrashCollector() {
54}
55
56void CrashCollector::Initialize(
57 CrashCollector::CountCrashFunction count_crash_function,
Ken Mixtera3249322011-03-03 08:47:38 -080058 CrashCollector::IsFeedbackAllowedFunction is_feedback_allowed_function) {
Ken Mixter03403162010-08-18 15:23:16 -070059 CHECK(count_crash_function != NULL);
60 CHECK(is_feedback_allowed_function != NULL);
Ken Mixter03403162010-08-18 15:23:16 -070061
62 count_crash_function_ = count_crash_function;
63 is_feedback_allowed_function_ = is_feedback_allowed_function;
Ken Mixter03403162010-08-18 15:23:16 -070064}
65
Ken Mixter9b346472010-11-07 13:45:45 -080066int CrashCollector::WriteNewFile(const FilePath &filename,
67 const char *data,
68 int size) {
69 int fd = HANDLE_EINTR(open(filename.value().c_str(),
70 O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0666));
71 if (fd < 0) {
72 return -1;
73 }
74
75 int rv = file_util::WriteFileDescriptor(fd, data, size);
76 HANDLE_EINTR(close(fd));
77 return rv;
78}
79
Ken Mixteree849c52010-09-30 15:30:10 -070080std::string CrashCollector::Sanitize(const std::string &name) {
81 std::string result = name;
82 for (size_t i = 0; i < name.size(); ++i) {
83 if (!isalnum(result[i]) && result[i] != '_')
84 result[i] = '_';
85 }
86 return result;
87}
88
Ken Mixter03403162010-08-18 15:23:16 -070089std::string CrashCollector::FormatDumpBasename(const std::string &exec_name,
90 time_t timestamp,
91 pid_t pid) {
92 struct tm tm;
93 localtime_r(&timestamp, &tm);
Ken Mixteree849c52010-09-30 15:30:10 -070094 std::string sanitized_exec_name = Sanitize(exec_name);
Ken Mixter03403162010-08-18 15:23:16 -070095 return StringPrintf("%s.%04d%02d%02d.%02d%02d%02d.%d",
Ken Mixteree849c52010-09-30 15:30:10 -070096 sanitized_exec_name.c_str(),
Ken Mixter03403162010-08-18 15:23:16 -070097 tm.tm_year + 1900,
98 tm.tm_mon + 1,
99 tm.tm_mday,
100 tm.tm_hour,
101 tm.tm_min,
102 tm.tm_sec,
103 pid);
104}
105
Ken Mixter207694d2010-10-28 15:42:37 -0700106FilePath CrashCollector::GetCrashPath(const FilePath &crash_directory,
107 const std::string &basename,
108 const std::string &extension) {
109 return crash_directory.Append(StringPrintf("%s.%s",
110 basename.c_str(),
111 extension.c_str()));
112}
113
Ken Mixter03403162010-08-18 15:23:16 -0700114FilePath CrashCollector::GetCrashDirectoryInfo(
115 uid_t process_euid,
116 uid_t default_user_id,
117 gid_t default_user_group,
118 mode_t *mode,
119 uid_t *directory_owner,
120 gid_t *directory_group) {
121 if (process_euid == default_user_id) {
122 *mode = kUserCrashPathMode;
123 *directory_owner = default_user_id;
124 *directory_group = default_user_group;
125 return FilePath(kUserCrashPath);
126 } else {
127 *mode = kSystemCrashPathMode;
128 *directory_owner = kRootOwner;
129 *directory_group = kRootGroup;
130 return FilePath(kSystemCrashPath);
131 }
132}
133
134bool CrashCollector::GetUserInfoFromName(const std::string &name,
135 uid_t *uid,
136 gid_t *gid) {
137 char storage[256];
138 struct passwd passwd_storage;
139 struct passwd *passwd_result = NULL;
140
141 if (getpwnam_r(name.c_str(), &passwd_storage, storage, sizeof(storage),
142 &passwd_result) != 0 || passwd_result == NULL) {
Ken Mixtera3249322011-03-03 08:47:38 -0800143 LOG(ERROR) << "Cannot find user named " << name;
Ken Mixter03403162010-08-18 15:23:16 -0700144 return false;
145 }
146
147 *uid = passwd_result->pw_uid;
148 *gid = passwd_result->pw_gid;
149 return true;
150}
151
152bool CrashCollector::GetCreatedCrashDirectoryByEuid(uid_t euid,
Ken Mixter207694d2010-10-28 15:42:37 -0700153 FilePath *crash_directory,
154 bool *out_of_capacity) {
Ken Mixter03403162010-08-18 15:23:16 -0700155 uid_t default_user_id;
156 gid_t default_user_group;
157
Ken Mixter207694d2010-10-28 15:42:37 -0700158 if (out_of_capacity != NULL) *out_of_capacity = false;
159
Ken Mixter03403162010-08-18 15:23:16 -0700160 // For testing.
161 if (forced_crash_directory_ != NULL) {
162 *crash_directory = FilePath(forced_crash_directory_);
163 return true;
164 }
165
166 if (!GetUserInfoFromName(kDefaultUserName,
167 &default_user_id,
168 &default_user_group)) {
Ken Mixtera3249322011-03-03 08:47:38 -0800169 LOG(ERROR) << "Could not find default user info";
Ken Mixter03403162010-08-18 15:23:16 -0700170 return false;
171 }
172 mode_t directory_mode;
173 uid_t directory_owner;
174 gid_t directory_group;
175 *crash_directory =
176 GetCrashDirectoryInfo(euid,
177 default_user_id,
178 default_user_group,
179 &directory_mode,
180 &directory_owner,
181 &directory_group);
182
183 if (!file_util::PathExists(*crash_directory)) {
184 // Create the spool directory with the appropriate mode (regardless of
185 // umask) and ownership.
186 mode_t old_mask = umask(0);
187 if (mkdir(crash_directory->value().c_str(), directory_mode) < 0 ||
188 chown(crash_directory->value().c_str(),
189 directory_owner,
190 directory_group) < 0) {
Ken Mixtera3249322011-03-03 08:47:38 -0800191 LOG(ERROR) << "Unable to create appropriate crash directory";
Ken Mixter03403162010-08-18 15:23:16 -0700192 return false;
193 }
194 umask(old_mask);
195 }
196
197 if (!file_util::PathExists(*crash_directory)) {
Ken Mixtera3249322011-03-03 08:47:38 -0800198 LOG(ERROR) << "Unable to create crash directory "
199 << crash_directory->value().c_str();
Ken Mixter03403162010-08-18 15:23:16 -0700200 return false;
201 }
202
Ken Mixter04ec10f2010-08-26 16:02:02 -0700203 if (!CheckHasCapacity(*crash_directory)) {
Ken Mixter207694d2010-10-28 15:42:37 -0700204 if (out_of_capacity != NULL) *out_of_capacity = true;
Ken Mixter04ec10f2010-08-26 16:02:02 -0700205 return false;
206 }
207
Ken Mixter03403162010-08-18 15:23:16 -0700208 return true;
209}
Ken Mixter04ec10f2010-08-26 16:02:02 -0700210
211// Return true if the given crash directory has not already reached
212// maximum capacity.
213bool CrashCollector::CheckHasCapacity(const FilePath &crash_directory) {
214 DIR* dir = opendir(crash_directory.value().c_str());
215 if (!dir) {
216 return false;
217 }
218 struct dirent ent_buf;
219 struct dirent* ent;
Ken Mixter04ec10f2010-08-26 16:02:02 -0700220 bool full = false;
Ken Mixteree849c52010-09-30 15:30:10 -0700221 std::set<std::string> basenames;
Ken Mixter04ec10f2010-08-26 16:02:02 -0700222 while (readdir_r(dir, &ent_buf, &ent) == 0 && ent != NULL) {
223 if ((strcmp(ent->d_name, ".") == 0) ||
224 (strcmp(ent->d_name, "..") == 0))
225 continue;
226
Ken Mixteree849c52010-09-30 15:30:10 -0700227 std::string filename(ent->d_name);
228 size_t last_dot = filename.rfind(".");
229 std::string basename;
230 // If there is a valid looking extension, use the base part of the
231 // name. If the only dot is the first byte (aka a dot file), treat
232 // it as unique to avoid allowing a directory full of dot files
233 // from accumulating.
234 if (last_dot != std::string::npos && last_dot != 0)
235 basename = filename.substr(0, last_dot);
236 else
237 basename = filename;
238 basenames.insert(basename);
Ken Mixter04ec10f2010-08-26 16:02:02 -0700239
Ken Mixteree849c52010-09-30 15:30:10 -0700240 if (basenames.size() >= static_cast<size_t>(kMaxCrashDirectorySize)) {
Ken Mixtera3249322011-03-03 08:47:38 -0800241 LOG(WARNING) << "Crash directory " << crash_directory.value()
242 << " already full with " << kMaxCrashDirectorySize
243 << " pending reports";
Ken Mixter04ec10f2010-08-26 16:02:02 -0700244 full = true;
245 break;
246 }
247 }
248 closedir(dir);
249 return !full;
250}
Ken Mixteree849c52010-09-30 15:30:10 -0700251
Ken Mixterc49dbd42010-12-14 17:44:11 -0800252bool CrashCollector::IsCommentLine(const std::string &line) {
253 size_t found = line.find_first_not_of(" ");
254 return found != std::string::npos && line[found] == '#';
255}
256
Ken Mixteree849c52010-09-30 15:30:10 -0700257bool CrashCollector::ReadKeyValueFile(
258 const FilePath &path,
259 const char separator,
260 std::map<std::string, std::string> *dictionary) {
261 std::string contents;
262 if (!file_util::ReadFileToString(path, &contents)) {
263 return false;
264 }
265 typedef std::vector<std::string> StringVector;
266 StringVector lines;
267 SplitString(contents, '\n', &lines);
268 bool any_errors = false;
269 for (StringVector::iterator line = lines.begin(); line != lines.end();
270 ++line) {
271 // Allow empty strings.
272 if (line->empty())
273 continue;
Ken Mixterc49dbd42010-12-14 17:44:11 -0800274 // Allow comment lines.
275 if (IsCommentLine(*line))
276 continue;
Ken Mixteree849c52010-09-30 15:30:10 -0700277 StringVector sides;
278 SplitString(*line, separator, &sides);
279 if (sides.size() != 2) {
280 any_errors = true;
281 continue;
282 }
283 dictionary->insert(std::pair<std::string, std::string>(sides[0], sides[1]));
284 }
285 return !any_errors;
286}
287
Ken Mixterc49dbd42010-12-14 17:44:11 -0800288bool CrashCollector::GetLogContents(const FilePath &config_path,
289 const std::string &exec_name,
290 const FilePath &output_file) {
291 std::map<std::string, std::string> log_commands;
292 if (!ReadKeyValueFile(config_path, ':', &log_commands)) {
Ken Mixtera3249322011-03-03 08:47:38 -0800293 LOG(INFO) << "Unable to read log configuration file "
294 << config_path.value();
Ken Mixterc49dbd42010-12-14 17:44:11 -0800295 return false;
296 }
297
298 if (log_commands.find(exec_name) == log_commands.end())
299 return false;
300
Ken Mixtera3249322011-03-03 08:47:38 -0800301 chromeos::ProcessImpl diag_process;
302 diag_process.AddArg(kShellPath);
Ken Mixterc49dbd42010-12-14 17:44:11 -0800303 std::string shell_command = log_commands[exec_name];
Ken Mixtera3249322011-03-03 08:47:38 -0800304 diag_process.AddStringOption("-c", shell_command);
305 diag_process.RedirectOutput(output_file.value());
Ken Mixterc49dbd42010-12-14 17:44:11 -0800306
Ken Mixtera3249322011-03-03 08:47:38 -0800307 int result = diag_process.Run();
308 if (result != 0) {
309 LOG(INFO) << "Running shell command " << shell_command << "failed with: "
310 << result;
Ken Mixterc49dbd42010-12-14 17:44:11 -0800311 return false;
312 }
313 return true;
314}
315
Ken Mixterafcf8082010-10-26 14:45:01 -0700316void CrashCollector::AddCrashMetaData(const std::string &key,
317 const std::string &value) {
318 extra_metadata_.append(StringPrintf("%s=%s\n", key.c_str(), value.c_str()));
319}
320
Ken Mixteree849c52010-09-30 15:30:10 -0700321void CrashCollector::WriteCrashMetaData(const FilePath &meta_path,
Ken Mixterc909b692010-10-18 12:26:05 -0700322 const std::string &exec_name,
323 const std::string &payload_path) {
Ken Mixteree849c52010-09-30 15:30:10 -0700324 std::map<std::string, std::string> contents;
Ken Mixterafcf8082010-10-26 14:45:01 -0700325 if (!ReadKeyValueFile(FilePath(std::string(lsb_release_)), '=', &contents)) {
Ken Mixtera3249322011-03-03 08:47:38 -0800326 LOG(ERROR) << "Problem parsing " << lsb_release_;
Ken Mixteree849c52010-09-30 15:30:10 -0700327 // Even though there was some failure, take as much as we could read.
328 }
329 std::string version("unknown");
330 std::map<std::string, std::string>::iterator i;
331 if ((i = contents.find("CHROMEOS_RELEASE_VERSION")) != contents.end()) {
332 version = i->second;
333 }
Ken Mixterc909b692010-10-18 12:26:05 -0700334 int64 payload_size = -1;
335 file_util::GetFileSize(FilePath(payload_path), &payload_size);
Ken Mixterafcf8082010-10-26 14:45:01 -0700336 std::string meta_data = StringPrintf("%sexec_name=%s\n"
Ken Mixteree849c52010-09-30 15:30:10 -0700337 "ver=%s\n"
Ken Mixter207694d2010-10-28 15:42:37 -0700338 "payload=%s\n"
Ken Mixterc909b692010-10-18 12:26:05 -0700339 "payload_size=%lld\n"
Ken Mixteree849c52010-09-30 15:30:10 -0700340 "done=1\n",
Ken Mixterafcf8082010-10-26 14:45:01 -0700341 extra_metadata_.c_str(),
Ken Mixteree849c52010-09-30 15:30:10 -0700342 exec_name.c_str(),
Ken Mixterc909b692010-10-18 12:26:05 -0700343 version.c_str(),
Ken Mixter207694d2010-10-28 15:42:37 -0700344 payload_path.c_str(),
Ken Mixterc909b692010-10-18 12:26:05 -0700345 payload_size);
Ken Mixter9b346472010-11-07 13:45:45 -0800346 // We must use WriteNewFile instead of file_util::WriteFile as we
347 // do not want to write with root access to a symlink that an attacker
348 // might have created.
349 if (WriteNewFile(meta_path, meta_data.c_str(), meta_data.size()) < 0) {
Ken Mixtera3249322011-03-03 08:47:38 -0800350 LOG(ERROR) << "Unable to write " << meta_path.value();
Ken Mixteree849c52010-09-30 15:30:10 -0700351 }
352}
Thieu Le1652fb22011-03-03 12:14:43 -0800353
354bool CrashCollector::IsCrashTestInProgress() {
355 return file_util::PathExists(FilePath(kCrashTestInProgressPath));
356}