blob: 660755951a0f53cda11f84177f049e63ac44285e [file] [log] [blame]
// Copyright (c) 2012 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 "crash-reporter/user_collector.h"
#include <bits/wordsize.h>
#include <elf.h>
#include <unistd.h>
#include <vector>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/strings/string_split.h>
#include <brillo/syslog_logging.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "crash-reporter/test_util.h"
#include "crash-reporter/vm_support.h"
using base::FilePath;
using brillo::FindLog;
using ::testing::_;
using ::testing::AllOf;
using ::testing::EndsWith;
using ::testing::Property;
using ::testing::Return;
using ::testing::StartsWith;
namespace {
const char kFilePath[] = "/my/path";
// Keep in sync with UserCollector::ShouldDump.
const char kChromeIgnoreMsg[] =
"ignoring call by kernel - chrome crash; "
"waiting for chrome to call us directly";
} // namespace
class UserCollectorMock : public UserCollector {
public:
MOCK_METHOD(void, SetUpDBus, (), (override));
MOCK_METHOD(std::vector<std::string>,
GetCommandLine,
(pid_t),
(const, override));
MOCK_METHOD(void, AccounceUserCrash, (), (override));
MOCK_METHOD(ErrorType,
ConvertCoreToMinidump,
(pid_t pid,
const base::FilePath&,
const base::FilePath&,
const base::FilePath&),
(override));
};
class UserCollectorTest : public ::testing::Test {
void SetUp() {
EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
const std::vector<std::string> default_command_line = {"test_command",
"--test-arg"};
EXPECT_CALL(collector_, GetCommandLine(testing::_))
.WillRepeatedly(testing::Return(default_command_line));
ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
test_dir_ = scoped_temp_dir_.GetPath();
const pid_t pid = getpid();
collector_.Initialize(kFilePath, false, false, false);
// Setup paths for output files.
test_core_pattern_file_ = test_dir_.Append("core_pattern");
collector_.set_core_pattern_file(test_core_pattern_file_.value());
test_core_pipe_limit_file_ = test_dir_.Append("core_pipe_limit");
collector_.set_core_pipe_limit_file(test_core_pipe_limit_file_.value());
collector_.set_filter_path(test_dir_.Append("no_filter").value());
crash_dir_ = test_dir_.Append("crash_dir");
ASSERT_TRUE(base::CreateDirectory(crash_dir_));
collector_.set_crash_directory_for_test(crash_dir_);
pid_ = pid;
brillo::ClearLog();
}
protected:
void ExpectFileEquals(const char* golden, const FilePath& file_path) {
std::string contents;
EXPECT_TRUE(base::ReadFileToString(file_path, &contents));
EXPECT_EQ(golden, contents);
}
std::vector<std::string> SplitLines(const std::string& lines) const {
return base::SplitString(lines, "\n", base::KEEP_WHITESPACE,
base::SPLIT_WANT_ALL);
}
// Verify that the root directory is not writable. Several tests depend on
// this fact, and are failing in ways that might be explained by having a
// writable root directory.
// Not using base::PathIsWritable because that doesn't actually check if the
// user can write to a path :-/ See 'man 2 access'.
static bool IsRootDirectoryWritable() {
base::FilePath temp_file_path;
if (!CreateTemporaryFileInDir(base::FilePath("/"), &temp_file_path)) {
return false;
}
base::DeleteFile(temp_file_path);
return true;
}
UserCollectorMock collector_;
pid_t pid_;
FilePath test_dir_;
FilePath crash_dir_;
FilePath test_core_pattern_file_;
FilePath test_core_pipe_limit_file_;
base::ScopedTempDir scoped_temp_dir_;
};
TEST_F(UserCollectorTest, EnableOK) {
ASSERT_TRUE(collector_.Enable(false));
ExpectFileEquals("|/my/path --user=%P:%s:%u:%g:%f", test_core_pattern_file_);
ExpectFileEquals("4", test_core_pipe_limit_file_);
EXPECT_TRUE(FindLog("Enabling user crash handling"));
}
TEST_F(UserCollectorTest, EnableNoPatternFileAccess) {
// Basic checking:
// Confirm we don't have junk left over from other tests.
ASSERT_FALSE(base::PathExists(base::FilePath("/does_not_exist")));
// We've seen strange problems that might be explained by having / writable.
ASSERT_FALSE(IsRootDirectoryWritable());
collector_.set_core_pattern_file("/does_not_exist");
ASSERT_FALSE(collector_.Enable(false));
EXPECT_TRUE(FindLog("Enabling user crash handling"));
EXPECT_TRUE(FindLog("Unable to write /does_not_exist"));
}
TEST_F(UserCollectorTest, EnableNoPipeLimitFileAccess) {
// Basic checking:
// Confirm we don't have junk left over from other tests.
ASSERT_FALSE(base::PathExists(base::FilePath("/does_not_exist")));
// We've seen strange problems that might be explained by having / writable.
ASSERT_FALSE(IsRootDirectoryWritable());
collector_.set_core_pipe_limit_file("/does_not_exist");
ASSERT_FALSE(collector_.Enable(false));
// Core pattern should not be written if we cannot access the pipe limit
// or otherwise we may set a pattern that results in infinite recursion.
ASSERT_FALSE(base::PathExists(test_core_pattern_file_));
EXPECT_TRUE(FindLog("Enabling user crash handling"));
EXPECT_TRUE(FindLog("Unable to write /does_not_exist"));
}
TEST_F(UserCollectorTest, DisableOK) {
ASSERT_TRUE(collector_.Disable());
ExpectFileEquals("core", test_core_pattern_file_);
EXPECT_TRUE(FindLog("Disabling user crash handling"));
}
TEST_F(UserCollectorTest, DisableNoFileAccess) {
// Basic checking:
// Confirm we don't have junk left over from other tests.
ASSERT_FALSE(base::PathExists(base::FilePath("/does_not_exist")));
// We've seen strange problems that might be explained by having / writable.
ASSERT_FALSE(IsRootDirectoryWritable());
collector_.set_core_pattern_file("/does_not_exist");
ASSERT_FALSE(collector_.Disable());
EXPECT_TRUE(FindLog("Disabling user crash handling"));
EXPECT_TRUE(FindLog("Unable to write /does_not_exist"));
}
TEST_F(UserCollectorTest, ParseCrashAttributes) {
base::Optional<UserCollectorBase::CrashAttributes> attrs =
UserCollectorBase::ParseCrashAttributes("123456:11:1000:2000:foobar");
ASSERT_TRUE(attrs);
EXPECT_EQ(123456, attrs->pid);
EXPECT_EQ(11, attrs->signal);
EXPECT_EQ(1000, attrs->uid);
EXPECT_EQ(2000, attrs->gid);
EXPECT_EQ("foobar", attrs->exec_name);
attrs = UserCollectorBase::ParseCrashAttributes("4321:6:0:0:barfoo");
ASSERT_TRUE(attrs);
EXPECT_EQ(4321, attrs->pid);
EXPECT_EQ(6, attrs->signal);
EXPECT_EQ(0, attrs->uid);
EXPECT_EQ(0, attrs->gid);
EXPECT_EQ("barfoo", attrs->exec_name);
EXPECT_FALSE(UserCollectorBase::ParseCrashAttributes("123456:11:1000"));
EXPECT_FALSE(UserCollectorBase::ParseCrashAttributes("123456:11:1000:100"));
attrs =
UserCollectorBase::ParseCrashAttributes("123456:11:1000:100:exec:extra");
ASSERT_TRUE(attrs);
EXPECT_EQ("exec:extra", attrs->exec_name);
EXPECT_FALSE(
UserCollectorBase::ParseCrashAttributes("12345p:11:1000:100:foobar"));
EXPECT_FALSE(
UserCollectorBase::ParseCrashAttributes("123456:1 :1000:0:foobar"));
EXPECT_FALSE(UserCollectorBase::ParseCrashAttributes("123456::::foobar"));
}
TEST_F(UserCollectorTest, ShouldDumpChromeOverridesDeveloperImage) {
std::string reason;
// When handle_chrome_crashes is false, should ignore chrome processes.
EXPECT_FALSE(collector_.ShouldDump(pid_, false, "chrome", &reason));
EXPECT_EQ(kChromeIgnoreMsg, reason);
EXPECT_FALSE(
collector_.ShouldDump(pid_, false, "supplied_Compositor", &reason));
EXPECT_EQ(kChromeIgnoreMsg, reason);
EXPECT_FALSE(
collector_.ShouldDump(pid_, false, "supplied_PipelineThread", &reason));
EXPECT_EQ(kChromeIgnoreMsg, reason);
EXPECT_FALSE(
collector_.ShouldDump(pid_, false, "Chrome_ChildIOThread", &reason));
EXPECT_EQ(kChromeIgnoreMsg, reason);
EXPECT_FALSE(
collector_.ShouldDump(pid_, false, "supplied_Chrome_ChildIOT", &reason));
EXPECT_EQ(kChromeIgnoreMsg, reason);
EXPECT_FALSE(
collector_.ShouldDump(pid_, false, "supplied_ChromotingClien", &reason));
EXPECT_EQ(kChromeIgnoreMsg, reason);
EXPECT_FALSE(
collector_.ShouldDump(pid_, false, "supplied_LocalInputMonit", &reason));
EXPECT_EQ(kChromeIgnoreMsg, reason);
// Test that chrome crashes are handled when the "handle_chrome_crashes" flag
// is set.
EXPECT_TRUE(collector_.ShouldDump(pid_, true, "chrome", &reason));
EXPECT_EQ("handling", reason);
EXPECT_TRUE(
collector_.ShouldDump(pid_, true, "supplied_Compositor", &reason));
EXPECT_EQ("handling", reason);
EXPECT_TRUE(
collector_.ShouldDump(pid_, true, "supplied_PipelineThread", &reason));
EXPECT_EQ("handling", reason);
EXPECT_TRUE(
collector_.ShouldDump(pid_, true, "Chrome_ChildIOThread", &reason));
EXPECT_EQ("handling", reason);
EXPECT_TRUE(
collector_.ShouldDump(pid_, true, "supplied_Chrome_ChildIOT", &reason));
EXPECT_EQ("handling", reason);
EXPECT_TRUE(
collector_.ShouldDump(pid_, true, "supplied_ChromotingClien", &reason));
EXPECT_EQ("handling", reason);
EXPECT_TRUE(
collector_.ShouldDump(pid_, true, "supplied_LocalInputMonit", &reason));
EXPECT_EQ("handling", reason);
}
TEST_F(UserCollectorTest, ShouldDumpUserConsentProductionImage) {
std::string reason;
EXPECT_TRUE(collector_.ShouldDump(pid_, false, "chrome-wm", &reason));
EXPECT_EQ("handling", reason);
}
// HandleNonChromeCrashWithConsent tests that we will create a dmp file if we
// (a) have user consent to collect crash data and
// (b) the process is not a Chrome process.
TEST_F(UserCollectorTest, HandleNonChromeCrashWithConsent) {
// Note the _ which is different from the - in the original |force_exec|
// passed to HandleCrash. This is due to the CrashCollector::Sanitize call in
// FormatDumpBasename.
const std::string crash_prefix = crash_dir_.Append("chromeos_wm").value();
int expected_mock_calls = 1;
if (VmSupport::Get()) {
expected_mock_calls = 0;
}
EXPECT_CALL(collector_, AccounceUserCrash()).Times(expected_mock_calls);
// NOTE: The '5' which appears in several strings below is the pid of the
// simulated crashing process.
EXPECT_CALL(collector_,
ConvertCoreToMinidump(
5, FilePath("/tmp/crash_reporter/5"),
Property(&FilePath::value,
AllOf(StartsWith(crash_prefix), EndsWith("core"))),
Property(&FilePath::value,
AllOf(StartsWith(crash_prefix), EndsWith("dmp")))))
.Times(expected_mock_calls)
.WillRepeatedly(Return(CrashCollector::kErrorNone));
UserCollectorBase::CrashAttributes attrs;
attrs.pid = 5;
attrs.signal = 2;
attrs.uid = 1000;
attrs.gid = 1000;
attrs.exec_name = "ignored";
EXPECT_TRUE(collector_.HandleCrash(attrs, "chromeos-wm"));
if (!VmSupport::Get()) {
EXPECT_TRUE(
FindLog("Received crash notification for chromeos-wm[5] sig 2"));
}
}
// HandleChromeCrashWithConsent tests that we do not attempt to create a dmp
// file if the process is named chrome. This is because we expect Chrome's own
// crash handling library (Breakpad or Crashpad) to call us directly -- see
// chrome_collector.h.
TEST_F(UserCollectorTest, HandleChromeCrashWithConsent) {
EXPECT_CALL(collector_, AccounceUserCrash()).Times(0);
EXPECT_CALL(collector_, ConvertCoreToMinidump(_, _, _, _)).Times(0);
UserCollectorBase::CrashAttributes attrs;
attrs.pid = 5;
attrs.signal = 2;
attrs.uid = 1000;
attrs.gid = 1000;
attrs.exec_name = "ignored";
EXPECT_TRUE(collector_.HandleCrash(attrs, "chrome"));
if (!VmSupport::Get()) {
EXPECT_TRUE(FindLog("Received crash notification for chrome[5] sig 2"));
EXPECT_TRUE(FindLog(kChromeIgnoreMsg));
}
}
// HandleSuppliedChromeCrashWithConsent also tests that we do not attempt to
// create a dmp file if the process is named chrome. This differs only in the
// fact that we are using the kernel's supplied name instead of the |force_exec|
// name. This is actually much closer to the real usage.
TEST_F(UserCollectorTest, HandleSuppliedChromeCrashWithConsent) {
EXPECT_CALL(collector_, AccounceUserCrash()).Times(0);
EXPECT_CALL(collector_, ConvertCoreToMinidump(_, _, _, _)).Times(0);
UserCollectorBase::CrashAttributes attrs;
attrs.pid = 5;
attrs.signal = 2;
attrs.uid = 1000;
attrs.gid = 1000;
attrs.exec_name = "chrome";
EXPECT_TRUE(collector_.HandleCrash(attrs, nullptr));
if (!VmSupport::Get()) {
EXPECT_TRUE(
FindLog("Received crash notification for supplied_chrome[5] sig 2"));
EXPECT_TRUE(FindLog(kChromeIgnoreMsg));
}
}
TEST_F(UserCollectorTest, GetProcessPath) {
FilePath path = collector_.GetProcessPath(100);
ASSERT_EQ("/proc/100", path.value());
}
TEST_F(UserCollectorTest, GetExecutableBaseNameFromPid) {
std::string base_name;
EXPECT_FALSE(collector_.GetExecutableBaseNameFromPid(0, &base_name));
EXPECT_TRUE(
FindLog("ReadSymbolicLink failed - Path /proc/0 DirectoryExists: 0"));
EXPECT_TRUE(FindLog("stat /proc/0/exe failed: -1 2"));
brillo::ClearLog();
pid_t my_pid = getpid();
EXPECT_TRUE(collector_.GetExecutableBaseNameFromPid(my_pid, &base_name));
EXPECT_FALSE(FindLog("Readlink failed"));
EXPECT_EQ("crash_reporter_test", base_name);
}
TEST_F(UserCollectorTest, GetFirstLineWithPrefix) {
std::vector<std::string> lines;
std::string line;
EXPECT_FALSE(collector_.GetFirstLineWithPrefix(lines, "Name:", &line));
EXPECT_EQ("", line);
lines.push_back("Name:\tls");
lines.push_back("State:\tR (running)");
lines.push_back(" Foo:\t1000");
line.clear();
EXPECT_TRUE(collector_.GetFirstLineWithPrefix(lines, "Name:", &line));
EXPECT_EQ(lines[0], line);
line.clear();
EXPECT_TRUE(collector_.GetFirstLineWithPrefix(lines, "State:", &line));
EXPECT_EQ(lines[1], line);
line.clear();
EXPECT_FALSE(collector_.GetFirstLineWithPrefix(lines, "Foo:", &line));
EXPECT_EQ("", line);
line.clear();
EXPECT_TRUE(collector_.GetFirstLineWithPrefix(lines, " Foo:", &line));
EXPECT_EQ(lines[2], line);
line.clear();
EXPECT_FALSE(collector_.GetFirstLineWithPrefix(lines, "Bar:", &line));
EXPECT_EQ("", line);
}
TEST_F(UserCollectorTest, GetIdFromStatus) {
int id = 1;
EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
UserCollector::kIdEffective,
SplitLines("nothing here"), &id));
EXPECT_EQ(id, 1);
// Not enough parameters.
EXPECT_FALSE(
collector_.GetIdFromStatus(UserCollector::kUserId, UserCollector::kIdReal,
SplitLines("line 1\nUid:\t1\n"), &id));
const std::vector<std::string> valid_contents =
SplitLines("\nUid:\t1\t2\t3\t4\nGid:\t5\t6\t7\t8\n");
EXPECT_TRUE(collector_.GetIdFromStatus(
UserCollector::kUserId, UserCollector::kIdReal, valid_contents, &id));
EXPECT_EQ(1, id);
EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
UserCollector::kIdEffective,
valid_contents, &id));
EXPECT_EQ(2, id);
EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
UserCollector::kIdFileSystem,
valid_contents, &id));
EXPECT_EQ(4, id);
EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kGroupId,
UserCollector::kIdEffective,
valid_contents, &id));
EXPECT_EQ(6, id);
EXPECT_TRUE(collector_.GetIdFromStatus(
UserCollector::kGroupId, UserCollector::kIdSet, valid_contents, &id));
EXPECT_EQ(7, id);
EXPECT_FALSE(collector_.GetIdFromStatus(
UserCollector::kGroupId, UserCollector::IdKind(5), valid_contents, &id));
EXPECT_FALSE(collector_.GetIdFromStatus(
UserCollector::kGroupId, UserCollector::IdKind(-1), valid_contents, &id));
// Fail if junk after number
EXPECT_FALSE(
collector_.GetIdFromStatus(UserCollector::kUserId, UserCollector::kIdReal,
SplitLines("Uid:\t1f\t2\t3\t4\n"), &id));
EXPECT_TRUE(
collector_.GetIdFromStatus(UserCollector::kUserId, UserCollector::kIdReal,
SplitLines("Uid:\t1\t2\t3\t4\n"), &id));
EXPECT_EQ(1, id);
// Fail if more than 4 numbers.
EXPECT_FALSE(
collector_.GetIdFromStatus(UserCollector::kUserId, UserCollector::kIdReal,
SplitLines("Uid:\t1\t2\t3\t4\t5\n"), &id));
}
TEST_F(UserCollectorTest, GetStateFromStatus) {
std::string state;
EXPECT_FALSE(
collector_.GetStateFromStatus(SplitLines("nothing here"), &state));
EXPECT_EQ("", state);
EXPECT_TRUE(
collector_.GetStateFromStatus(SplitLines("State:\tR (running)"), &state));
EXPECT_EQ("R (running)", state);
EXPECT_TRUE(collector_.GetStateFromStatus(
SplitLines("Name:\tls\nState:\tZ (zombie)\n"), &state));
EXPECT_EQ("Z (zombie)", state);
}
TEST_F(UserCollectorTest, ClobberContainerDirectory) {
// Try a path that is not writable.
ASSERT_FALSE(collector_.ClobberContainerDirectory(FilePath("/bad/path")));
EXPECT_TRUE(FindLog("Could not create /bad/path"));
}
TEST_F(UserCollectorTest, CopyOffProcFilesBadPid) {
FilePath container_path = test_dir_.Append("container");
ASSERT_TRUE(collector_.ClobberContainerDirectory(container_path));
ASSERT_FALSE(collector_.CopyOffProcFiles(0, container_path));
EXPECT_TRUE(FindLog("Path /proc/0 does not exist"));
}
TEST_F(UserCollectorTest, CopyOffProcFilesOK) {
FilePath container_path = test_dir_.Append("container");
ASSERT_TRUE(collector_.ClobberContainerDirectory(container_path));
ASSERT_TRUE(collector_.CopyOffProcFiles(pid_, container_path));
EXPECT_FALSE(FindLog("Could not copy"));
static const struct {
const char* name;
bool exists;
} kExpectations[] = {
{"auxv", true}, {"cmdline", true}, {"environ", true}, {"maps", true},
{"mem", false}, {"mounts", false}, {"sched", false}, {"status", true},
};
for (const auto& expectation : kExpectations) {
EXPECT_EQ(expectation.exists,
base::PathExists(container_path.Append(expectation.name)));
}
}
TEST_F(UserCollectorTest, ValidateProcFiles) {
FilePath container_dir = test_dir_;
// maps file not exists (i.e. GetFileSize fails)
EXPECT_FALSE(collector_.ValidateProcFiles(container_dir));
// maps file is empty
FilePath maps_file = container_dir.Append("maps");
ASSERT_TRUE(test_util::CreateFile(maps_file, ""));
ASSERT_TRUE(base::PathExists(maps_file));
EXPECT_FALSE(collector_.ValidateProcFiles(container_dir));
// maps file is not empty
const char data[] = "test data";
ASSERT_TRUE(test_util::CreateFile(maps_file, data));
ASSERT_TRUE(base::PathExists(maps_file));
EXPECT_TRUE(collector_.ValidateProcFiles(container_dir));
}
TEST_F(UserCollectorTest, ValidateCoreFile) {
FilePath core_file = test_dir_.Append("core");
// Core file does not exist
EXPECT_EQ(UserCollector::kErrorReadCoreData,
collector_.ValidateCoreFile(core_file));
char e_ident[EI_NIDENT];
e_ident[EI_MAG0] = ELFMAG0;
e_ident[EI_MAG1] = ELFMAG1;
e_ident[EI_MAG2] = ELFMAG2;
e_ident[EI_MAG3] = ELFMAG3;
#if __WORDSIZE == 32
e_ident[EI_CLASS] = ELFCLASS32;
#elif __WORDSIZE == 64
e_ident[EI_CLASS] = ELFCLASS64;
#else
#error Unknown/unsupported value of __WORDSIZE.
#endif
// Core file has the expected header
ASSERT_TRUE(
test_util::CreateFile(core_file, std::string(e_ident, sizeof(e_ident))));
EXPECT_EQ(UserCollector::kErrorNone, collector_.ValidateCoreFile(core_file));
#if __WORDSIZE == 64
// 32-bit core file on 64-bit platform
e_ident[EI_CLASS] = ELFCLASS32;
ASSERT_TRUE(
test_util::CreateFile(core_file, std::string(e_ident, sizeof(e_ident))));
EXPECT_EQ(UserCollector::kErrorUnsupported32BitCoreFile,
collector_.ValidateCoreFile(core_file));
e_ident[EI_CLASS] = ELFCLASS64;
#endif
// Invalid core files
ASSERT_TRUE(test_util::CreateFile(core_file,
std::string(e_ident, sizeof(e_ident) - 1)));
EXPECT_EQ(UserCollector::kErrorInvalidCoreFile,
collector_.ValidateCoreFile(core_file));
e_ident[EI_MAG0] = 0;
ASSERT_TRUE(
test_util::CreateFile(core_file, std::string(e_ident, sizeof(e_ident))));
EXPECT_EQ(UserCollector::kErrorInvalidCoreFile,
collector_.ValidateCoreFile(core_file));
}