blob: fd78209537c8416446383ad004b466e9c040ddb5 [file] [log] [blame]
// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "debugd/src/crash_sender_tool.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <memory>
#include <base/files/file.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/strings/string_number_conversions.h>
#include <brillo/dbus/exported_property_set.h>
#include "debugd/src/error_utils.h"
#include "debugd/src/process_with_id.h"
namespace debugd {
namespace {
constexpr char kErrorIOError[] = "org.chromium.debugd.error.IOError";
} // namespace
constexpr char CrashSenderTool::kErrorBadFileName[];
void CrashSenderTool::UploadCrashes() {
RunCrashSender(false /* ignore_hold_off_time */,
base::FilePath("") /* crash_directory */);
}
bool CrashSenderTool::UploadSingleCrash(
const std::vector<std::tuple<std::string, base::ScopedFD>>& in_files,
brillo::ErrorPtr* error) {
// debugd runs in a non-root mount namespace and mounts a new tmpfs on /tmp
// inside the namespace, so this should be invisible to all other processes
// and not written to disk.
//
// *It is a privacy violation* if these files are visible to non-root
// processes or are written unencrypted to disk!
base::FilePath crash_directory("/tmp/crash");
crash_directory = crash_directory.AddExtension(
base::NumberToString(next_crash_directory_id_));
next_crash_directory_id_++;
// We need to be sure to clean up the tmp directory to avoid leaking
// resources.
base::ScopedTempDir crash_directory_holder;
if (!crash_directory_holder.Set(crash_directory)) {
DEBUGD_ADD_ERROR(error, kErrorIOError, "Create directory failed");
return false;
}
for (const auto& in_file_tuple : in_files) {
base::FilePath file_name(std::get<0>(in_file_tuple));
const base::ScopedFD& file_descriptor = std::get<1>(in_file_tuple);
// Sanitize file names to ensure a bad actor cannot ask us to write to
// arbitrary files. crash_reporter should only send us the base file name,
// so if it's not just a base file name, it's not from crash_reporter.
// Also check for "..", "/", and "."
if (file_name != file_name.BaseName() || file_name.ReferencesParent() ||
file_name.IsAbsolute() ||
file_name.value() == base::FilePath::kCurrentDirectory) {
DEBUGD_ADD_ERROR(error, kErrorBadFileName, "Bad File Name");
return false;
}
// Copy contents of file_descriptor to a new file named file_name inside
// crash_directory.
base::FilePath file_path = crash_directory.Append(file_name);
base::File new_file(file_path,
base::File::FLAG_CREATE | base::File::FLAG_WRITE);
if (!new_file.IsValid()) {
LOG(WARNING) << "Error creating file " << file_path.value() << ": "
<< base::File::ErrorToString(new_file.error_details());
continue;
}
if (lseek(file_descriptor.get(), SEEK_SET, 0) == static_cast<off_t>(-1)) {
PLOG(WARNING) << "lseek failed";
}
const int kBufferSize = 1 << 16;
std::unique_ptr<char[]> buf(new char[kBufferSize]);
ssize_t len;
while ((len = HANDLE_EINTR(
read(file_descriptor.get(), buf.get(), kBufferSize))) > 0) {
if (!new_file.WriteAtCurrentPos(buf.get(), len)) {
LOG(WARNING) << "Error writing to file " << file_path.value() << ": "
<< base::File::ErrorToString(new_file.error_details());
break;
}
}
if (len < 0) {
PLOG(WARNING) << "Failed to read from passed file descriptor";
}
// Ensure data is visible to crash_sender.
new_file.Flush();
}
// Since crash_sender jails itself, it won't actually see our /tmp/crash.###
// directory. Instead, open the directory and pass the /proc path to the
// directory file descriptor as the crash directory.
base::ScopedFD crash_directory_fd(
HANDLE_EINTR(open(crash_directory.value().c_str(), O_RDONLY)));
if (!crash_directory_fd.is_valid()) {
DEBUGD_ADD_ERROR(error, kErrorIOError, "Open directory failed");
return false;
}
base::FilePath munged_crash_directory("/proc/self/fd");
munged_crash_directory = munged_crash_directory.Append(
base::NumberToString(crash_directory_fd.get()));
const bool ignore_hold_off_time = true; // We already flushed all the files.
RunCrashSender(ignore_hold_off_time, munged_crash_directory);
return true;
}
void CrashSenderTool::RunCrashSender(bool ignore_hold_off_time,
const base::FilePath& crash_directory) {
// 'crash_sender' requires accessing user mounts to upload user crashes.
ProcessWithId* p =
CreateProcess(false /* sandboxed */, true /* access_root_mount_ns */);
p->AddArg("/sbin/crash_sender");
// This is being invoked directly by the user. Override some of the limits
// we normally use to avoid interfering with user tasks.
p->AddArg("--max_spread_time=0");
p->AddArg("--ignore_rate_limits");
if (ignore_hold_off_time) {
p->AddArg("--ignore_hold_off_time");
}
if (!crash_directory.empty()) {
p->AddArg("--crash_directory=" + crash_directory.value());
}
if (test_mode_) {
p->AddArg("--test_mode");
}
p->Run();
}
void CrashSenderTool::OnTestModeChanged(
const brillo::dbus_utils::ExportedPropertyBase* test_mode_property) {
const auto* property =
dynamic_cast<const brillo::dbus_utils::ExportedProperty<bool>*>(
test_mode_property);
DCHECK(property);
test_mode_ = property->value();
LOG(INFO) << "CrashSenderTestMode set to " << std::boolalpha << test_mode_;
}
} // namespace debugd