1 /*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "crash_collector.h"
18
19 #include <dirent.h>
20 #include <fcntl.h> // For file creation modes.
21 #include <inttypes.h>
22 #include <linux/limits.h> // PATH_MAX
23 #include <pwd.h> // For struct passwd.
24 #include <sys/types.h> // for mode_t.
25 #include <sys/wait.h> // For waitpid.
26 #include <unistd.h> // For execv and fork.
27
28 #include <set>
29 #include <utility>
30 #include <vector>
31
32 #include <base/files/file_util.h>
33 #include <base/logging.h>
34 #include <base/posix/eintr_wrapper.h>
35 #include <base/strings/string_split.h>
36 #include <base/strings/string_util.h>
37 #include <base/strings/stringprintf.h>
38 #include <brillo/key_value_store.h>
39 #include <brillo/process.h>
40
41 namespace {
42
43 const char kCollectChromeFile[] =
44 "/mnt/stateful_partition/etc/collect_chrome_crashes";
45 const char kCrashTestInProgressPath[] =
46 "/data/misc/crash_reporter/tmp/crash-test-in-progress";
47 const char kDefaultLogConfig[] = "/etc/crash_reporter_logs.conf";
48 const char kDefaultUserName[] = "chronos";
49 const char kLeaveCoreFile[] = "/data/misc/crash_reporter/.leave_core";
50 const char kShellPath[] = "/system/bin/sh";
51 const char kSystemCrashPath[] = "/data/misc/crash_reporter/crash";
52 const char kUploadVarPrefix[] = "upload_var_";
53 const char kUploadFilePrefix[] = "upload_file_";
54
55 // Normally this path is not used. Unfortunately, there are a few edge cases
56 // where we need this. Any process that runs as kDefaultUserName that crashes
57 // is consider a "user crash". That includes the initial Chrome browser that
58 // runs the login screen. If that blows up, there is no logged in user yet,
59 // so there is no per-user dir for us to stash things in. Instead we fallback
60 // to this path as it is at least encrypted on a per-system basis.
61 //
62 // This also comes up when running autotests. The GUI is sitting at the login
63 // screen while tests are sshing in, changing users, and triggering crashes as
64 // the user (purposefully).
65 const char kFallbackUserCrashPath[] = "/home/chronos/crash";
66
67 // Directory mode of the user crash spool directory.
68 const mode_t kUserCrashPathMode = 0755;
69
70 // Directory mode of the system crash spool directory.
71 const mode_t kSystemCrashPathMode = 01755;
72
73 const uid_t kRootOwner = 0;
74 const uid_t kRootGroup = 0;
75
76 } // namespace
77
78 // Maximum crash reports per crash spool directory. Note that this is
79 // a separate maximum from the maximum rate at which we upload these
80 // diagnostics. The higher this rate is, the more space we allow for
81 // core files, minidumps, and kcrash logs, and equivalently the more
82 // processor and I/O bandwidth we dedicate to handling these crashes when
83 // many occur at once. Also note that if core files are configured to
84 // be left on the file system, we stop adding crashes when either the
85 // number of core files or minidumps reaches this number.
86 const int CrashCollector::kMaxCrashDirectorySize = 32;
87
88 using base::FilePath;
89 using base::StringPrintf;
90
CrashCollector()91 CrashCollector::CrashCollector()
92 : log_config_path_(kDefaultLogConfig) {
93 }
94
~CrashCollector()95 CrashCollector::~CrashCollector() {
96 }
97
Initialize(CrashCollector::CountCrashFunction count_crash_function,CrashCollector::IsFeedbackAllowedFunction is_feedback_allowed_function)98 void CrashCollector::Initialize(
99 CrashCollector::CountCrashFunction count_crash_function,
100 CrashCollector::IsFeedbackAllowedFunction is_feedback_allowed_function) {
101 CHECK(count_crash_function);
102 CHECK(is_feedback_allowed_function);
103
104 count_crash_function_ = count_crash_function;
105 is_feedback_allowed_function_ = is_feedback_allowed_function;
106 }
107
WriteNewFile(const FilePath & filename,const char * data,int size)108 int CrashCollector::WriteNewFile(const FilePath &filename,
109 const char *data,
110 int size) {
111 int fd = HANDLE_EINTR(open(filename.value().c_str(),
112 O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0666));
113 if (fd < 0) {
114 return -1;
115 }
116
117 int rv = base::WriteFileDescriptor(fd, data, size) ? size : -1;
118 IGNORE_EINTR(close(fd));
119 return rv;
120 }
121
Sanitize(const std::string & name)122 std::string CrashCollector::Sanitize(const std::string &name) {
123 // Make sure the sanitized name does not include any periods.
124 // The logic in crash_sender relies on this.
125 std::string result = name;
126 for (size_t i = 0; i < name.size(); ++i) {
127 if (!isalnum(result[i]) && result[i] != '_')
128 result[i] = '_';
129 }
130 return result;
131 }
132
FormatDumpBasename(const std::string & exec_name,time_t timestamp,pid_t pid)133 std::string CrashCollector::FormatDumpBasename(const std::string &exec_name,
134 time_t timestamp,
135 pid_t pid) {
136 struct tm tm;
137 localtime_r(×tamp, &tm);
138 std::string sanitized_exec_name = Sanitize(exec_name);
139 return StringPrintf("%s.%04d%02d%02d.%02d%02d%02d.%d",
140 sanitized_exec_name.c_str(),
141 tm.tm_year + 1900,
142 tm.tm_mon + 1,
143 tm.tm_mday,
144 tm.tm_hour,
145 tm.tm_min,
146 tm.tm_sec,
147 pid);
148 }
149
GetCrashPath(const FilePath & crash_directory,const std::string & basename,const std::string & extension)150 FilePath CrashCollector::GetCrashPath(const FilePath &crash_directory,
151 const std::string &basename,
152 const std::string &extension) {
153 return crash_directory.Append(StringPrintf("%s.%s",
154 basename.c_str(),
155 extension.c_str()));
156 }
157
GetCrashDirectoryInfo(mode_t * mode,uid_t * directory_owner,gid_t * directory_group)158 FilePath CrashCollector::GetCrashDirectoryInfo(
159 mode_t *mode,
160 uid_t *directory_owner,
161 gid_t *directory_group) {
162 *mode = kSystemCrashPathMode;
163 *directory_owner = kRootOwner;
164 *directory_group = kRootGroup;
165 return FilePath(kSystemCrashPath);
166 }
167
GetUserInfoFromName(const std::string & name,uid_t * uid,gid_t * gid)168 bool CrashCollector::GetUserInfoFromName(const std::string &name,
169 uid_t *uid,
170 gid_t *gid) {
171 char storage[256];
172 struct passwd passwd_storage;
173 struct passwd *passwd_result = nullptr;
174
175 if (getpwnam_r(name.c_str(), &passwd_storage, storage, sizeof(storage),
176 &passwd_result) != 0 || passwd_result == nullptr) {
177 LOG(ERROR) << "Cannot find user named " << name;
178 return false;
179 }
180
181 *uid = passwd_result->pw_uid;
182 *gid = passwd_result->pw_gid;
183 return true;
184 }
185
GetCreatedCrashDirectoryByEuid(uid_t euid,FilePath * crash_directory,bool * out_of_capacity)186 bool CrashCollector::GetCreatedCrashDirectoryByEuid(uid_t euid,
187 FilePath *crash_directory,
188 bool *out_of_capacity) {
189 if (out_of_capacity) *out_of_capacity = false;
190
191 // For testing.
192 if (!forced_crash_directory_.empty()) {
193 *crash_directory = forced_crash_directory_;
194 return true;
195 }
196
197 mode_t directory_mode;
198 uid_t directory_owner;
199 gid_t directory_group;
200 *crash_directory =
201 GetCrashDirectoryInfo(&directory_mode,
202 &directory_owner,
203 &directory_group);
204
205 if (!base::PathExists(*crash_directory)) {
206 // Create the spool directory with the appropriate mode (regardless of
207 // umask) and ownership.
208 mode_t old_mask = umask(0);
209 if (mkdir(crash_directory->value().c_str(), directory_mode) < 0 ||
210 chown(crash_directory->value().c_str(),
211 directory_owner,
212 directory_group) < 0) {
213 LOG(ERROR) << "Unable to create appropriate crash directory";
214 return false;
215 }
216 umask(old_mask);
217 }
218
219 if (!base::PathExists(*crash_directory)) {
220 LOG(ERROR) << "Unable to create crash directory "
221 << crash_directory->value().c_str();
222 return false;
223 }
224
225 if (!CheckHasCapacity(*crash_directory)) {
226 if (out_of_capacity) *out_of_capacity = true;
227 LOG(ERROR) << "Directory " << crash_directory->value()
228 << " is out of capacity.";
229 return false;
230 }
231
232 return true;
233 }
234
GetProcessPath(pid_t pid)235 FilePath CrashCollector::GetProcessPath(pid_t pid) {
236 return FilePath(StringPrintf("/proc/%d", pid));
237 }
238
GetSymlinkTarget(const FilePath & symlink,FilePath * target)239 bool CrashCollector::GetSymlinkTarget(const FilePath &symlink,
240 FilePath *target) {
241 ssize_t max_size = 64;
242 std::vector<char> buffer;
243
244 while (true) {
245 buffer.resize(max_size + 1);
246 ssize_t size = readlink(symlink.value().c_str(), buffer.data(), max_size);
247 if (size < 0) {
248 int saved_errno = errno;
249 LOG(ERROR) << "Readlink failed on " << symlink.value() << " with "
250 << saved_errno;
251 return false;
252 }
253
254 buffer[size] = 0;
255 if (size == max_size) {
256 max_size *= 2;
257 if (max_size > PATH_MAX) {
258 return false;
259 }
260 continue;
261 }
262 break;
263 }
264
265 *target = FilePath(buffer.data());
266 return true;
267 }
268
GetExecutableBaseNameFromPid(pid_t pid,std::string * base_name)269 bool CrashCollector::GetExecutableBaseNameFromPid(pid_t pid,
270 std::string *base_name) {
271 FilePath target;
272 FilePath process_path = GetProcessPath(pid);
273 FilePath exe_path = process_path.Append("exe");
274 if (!GetSymlinkTarget(exe_path, &target)) {
275 LOG(INFO) << "GetSymlinkTarget failed - Path " << process_path.value()
276 << " DirectoryExists: "
277 << base::DirectoryExists(process_path);
278 // Try to further diagnose exe readlink failure cause.
279 struct stat buf;
280 int stat_result = stat(exe_path.value().c_str(), &buf);
281 int saved_errno = errno;
282 if (stat_result < 0) {
283 LOG(INFO) << "stat " << exe_path.value() << " failed: " << stat_result
284 << " " << saved_errno;
285 } else {
286 LOG(INFO) << "stat " << exe_path.value() << " succeeded: st_mode="
287 << buf.st_mode;
288 }
289 return false;
290 }
291 *base_name = target.BaseName().value();
292 return true;
293 }
294
295 // Return true if the given crash directory has not already reached
296 // maximum capacity.
CheckHasCapacity(const FilePath & crash_directory)297 bool CrashCollector::CheckHasCapacity(const FilePath &crash_directory) {
298 DIR* dir = opendir(crash_directory.value().c_str());
299 if (!dir) {
300 LOG(WARNING) << "Unable to open crash directory "
301 << crash_directory.value();
302 return false;
303 }
304 struct dirent ent_buf;
305 struct dirent* ent;
306 bool full = false;
307 std::set<std::string> basenames;
308 while (readdir_r(dir, &ent_buf, &ent) == 0 && ent) {
309 if ((strcmp(ent->d_name, ".") == 0) ||
310 (strcmp(ent->d_name, "..") == 0))
311 continue;
312
313 std::string filename(ent->d_name);
314 size_t last_dot = filename.rfind(".");
315 std::string basename;
316 // If there is a valid looking extension, use the base part of the
317 // name. If the only dot is the first byte (aka a dot file), treat
318 // it as unique to avoid allowing a directory full of dot files
319 // from accumulating.
320 if (last_dot != std::string::npos && last_dot != 0)
321 basename = filename.substr(0, last_dot);
322 else
323 basename = filename;
324 basenames.insert(basename);
325
326 if (basenames.size() >= static_cast<size_t>(kMaxCrashDirectorySize)) {
327 LOG(WARNING) << "Crash directory " << crash_directory.value()
328 << " already full with " << kMaxCrashDirectorySize
329 << " pending reports";
330 full = true;
331 break;
332 }
333 }
334 closedir(dir);
335 return !full;
336 }
337
GetLogContents(const FilePath & config_path,const std::string & exec_name,const FilePath & output_file)338 bool CrashCollector::GetLogContents(const FilePath &config_path,
339 const std::string &exec_name,
340 const FilePath &output_file) {
341 brillo::KeyValueStore store;
342 if (!store.Load(config_path)) {
343 LOG(INFO) << "Unable to read log configuration file "
344 << config_path.value();
345 return false;
346 }
347
348 std::string command;
349 if (!store.GetString(exec_name, &command))
350 return false;
351
352 brillo::ProcessImpl diag_process;
353 diag_process.AddArg(kShellPath);
354 diag_process.AddStringOption("-c", command);
355 diag_process.RedirectOutput(output_file.value());
356
357 const int result = diag_process.Run();
358 if (result != 0) {
359 LOG(INFO) << "Log command \"" << command << "\" exited with " << result;
360 return false;
361 }
362 return true;
363 }
364
AddCrashMetaData(const std::string & key,const std::string & value)365 void CrashCollector::AddCrashMetaData(const std::string &key,
366 const std::string &value) {
367 extra_metadata_.append(StringPrintf("%s=%s\n", key.c_str(), value.c_str()));
368 }
369
AddCrashMetaUploadFile(const std::string & key,const std::string & path)370 void CrashCollector::AddCrashMetaUploadFile(const std::string &key,
371 const std::string &path) {
372 if (!path.empty())
373 AddCrashMetaData(kUploadFilePrefix + key, path);
374 }
375
AddCrashMetaUploadData(const std::string & key,const std::string & value)376 void CrashCollector::AddCrashMetaUploadData(const std::string &key,
377 const std::string &value) {
378 if (!value.empty())
379 AddCrashMetaData(kUploadVarPrefix + key, value);
380 }
381
WriteCrashMetaData(const FilePath & meta_path,const std::string & exec_name,const std::string & payload_path)382 void CrashCollector::WriteCrashMetaData(const FilePath &meta_path,
383 const std::string &exec_name,
384 const std::string &payload_path) {
385 int64_t payload_size = -1;
386 base::GetFileSize(FilePath(payload_path), &payload_size);
387 std::string meta_data = StringPrintf("%sexec_name=%s\n"
388 "payload=%s\n"
389 "payload_size=%" PRId64 "\n"
390 "done=1\n",
391 extra_metadata_.c_str(),
392 exec_name.c_str(),
393 payload_path.c_str(),
394 payload_size);
395 // We must use WriteNewFile instead of base::WriteFile as we
396 // do not want to write with root access to a symlink that an attacker
397 // might have created.
398 if (WriteNewFile(meta_path, meta_data.c_str(), meta_data.size()) < 0) {
399 LOG(ERROR) << "Unable to write " << meta_path.value();
400 }
401 }
402
IsCrashTestInProgress()403 bool CrashCollector::IsCrashTestInProgress() {
404 return base::PathExists(FilePath(kCrashTestInProgressPath));
405 }
406
IsDeveloperImage()407 bool CrashCollector::IsDeveloperImage() {
408 // If we're testing crash reporter itself, we don't want to special-case
409 // for developer images.
410 if (IsCrashTestInProgress())
411 return false;
412 return base::PathExists(FilePath(kLeaveCoreFile));
413 }
414