blob: c290eaa2ae71b4e23c0b719b3b7ea7e94c80bdc4 [file] [log] [blame]
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "crash-reporter/selinux_violation_collector.h"
#include <unistd.h>
#include <memory>
#include <string>
#include <utility>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/memory/ref_counted.h>
#include <base/memory/scoped_refptr.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <metrics/metrics_library.h>
#include <metrics/metrics_library_mock.h>
#include "crash-reporter/test_util.h"
using base::FilePath;
namespace {
using testing::Return;
// Source tree log config file name.
constexpr char kLogConfigFileName[] = "crash_reporter_logs.conf";
constexpr char kTestFilename[] = "test-selinux-violation";
constexpr char kTestCrashDirectory[] = "test-crash-directory";
constexpr char TestSELinuxViolationMessage[] =
"sssss-selinux-init\n"
"comm\001init\002scontext\001context1\002\n"
"SELINUX VIOLATION TRIGGERED FOR init AT context1.\n";
constexpr char TestSELinuxViolationMessageContent[] =
"SELINUX VIOLATION TRIGGERED FOR init AT context1.\n";
constexpr char TestSELinuxViolationMessageWithComm[] =
"sssss-selinux-init\n"
"comm\001init\002scontext\001context1\002\n"
"SELINUX VIOLATION TRIGGERED FOR comm=\"init\" AT context1.\n";
constexpr char TestSELinuxViolationMessageWithPid[] =
"sssss-selinux-init\n"
"comm\001init\002scontext\001context1\002\n"
"SELINUX VIOLATION TRIGGERED FOR pid=1234 AT context1.\n";
constexpr char TestSELinuxViolationMessageWithPidAndComm[] =
"sssss-selinux-init\n"
"comm\001init\002scontext\001context1\002\n"
"SELINUX VIOLATION TRIGGERED FOR pid=1234 comm=\"init\" AT context1.\n";
constexpr char TestSELinuxViolationMessageWithCommContent[] =
"SELINUX VIOLATION TRIGGERED FOR comm=\"init\" AT context1.\n";
constexpr char TestSELinuxViolationMessageWithPidContent[] =
"SELINUX VIOLATION TRIGGERED FOR pid=1234 AT context1.\n";
constexpr char TestSELinuxViolationMessageWithPidAndCommContent[] =
"SELINUX VIOLATION TRIGGERED FOR pid=1234 comm=\"init\" AT context1.\n";
constexpr char TestSELinuxViolationMessageWithInvalidComm[] =
"sssss-selinux-init\n"
"comm\001init\002scontext\001context1\002\n"
"SELINUX VIOLATION TRIGGERED FOR comm=\"../etc/passwd💩\" AT context1.\n";
constexpr char TestSELinuxViolationMessageWithLongComm[] =
"sssss-selinux-init\n"
"comm\001init\002scontext\001context1\002\n"
"comm=\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\""
" AT context1.\n";
constexpr char TestSELinuxViolationMessageWithNonTerminatedComm[] =
"sssss-selinux-init\n"
"comm\001init\002scontext\001context1\002\n"
"comm=\"aaaa AT context1.\n";
} // namespace
class SELinuxViolationCollectorMock : public SELinuxViolationCollector {
public:
SELinuxViolationCollectorMock()
: SELinuxViolationCollector(
base::MakeRefCounted<
base::RefCountedData<std::unique_ptr<MetricsLibraryInterface>>>(
std::make_unique<MetricsLibraryMock>())) {}
MOCK_METHOD(void, SetUpDBus, (), (override));
};
class SELinuxViolationCollectorTest : public ::testing::Test {
void SetUp() {
EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
collector_.Initialize(false);
ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
test_path_ = scoped_temp_dir_.GetPath().Append(kTestFilename);
collector_.set_violation_report_path_for_testing(test_path_);
test_crash_directory_ =
scoped_temp_dir_.GetPath().Append(kTestCrashDirectory);
CreateDirectory(test_crash_directory_);
collector_.set_crash_directory_for_test(test_crash_directory_);
collector_.set_log_config_path(
test_util::GetTestDataPath(kLogConfigFileName,
/*use_testdata=*/false)
.value());
}
protected:
SELinuxViolationCollectorMock collector_;
base::ScopedTempDir scoped_temp_dir_;
FilePath test_path_;
FilePath test_crash_directory_;
};
TEST_F(SELinuxViolationCollectorTest, CollectOK) {
// Collector produces a violation report.
ASSERT_TRUE(test_util::CreateFile(test_path_, TestSELinuxViolationMessage));
EXPECT_TRUE(collector_.Collect(100));
EXPECT_FALSE(IsDirectoryEmpty(test_crash_directory_));
FilePath meta_path;
EXPECT_TRUE(test_util::DirectoryHasFileWithPattern(
test_crash_directory_, "selinux_violation.*.meta", &meta_path));
// Meta file contains proper weight.
std::string meta_content;
base::ReadFileToString(meta_path, &meta_content);
EXPECT_THAT(meta_content, testing::HasSubstr("upload_var_weight=100\n"));
FilePath log_file_path;
EXPECT_TRUE(test_util::DirectoryHasFileWithPattern(
test_crash_directory_, "selinux_violation.*.log", &log_file_path));
std::string content;
base::ReadFileToString(log_file_path, &content);
EXPECT_STREQ(content.c_str(), TestSELinuxViolationMessageContent);
}
TEST_F(SELinuxViolationCollectorTest, CollectOKWithComm) {
// Collector produces a violation report named using the "comm" key.
ASSERT_TRUE(
test_util::CreateFile(test_path_, TestSELinuxViolationMessageWithComm));
EXPECT_TRUE(collector_.Collect(100));
EXPECT_FALSE(IsDirectoryEmpty(test_crash_directory_));
EXPECT_TRUE(test_util::DirectoryHasFileWithPatternAndContents(
test_crash_directory_, "selinux_violation_init.*.meta",
"sig=sssss-selinux-init"));
FilePath file_path;
EXPECT_TRUE(test_util::DirectoryHasFileWithPattern(
test_crash_directory_, "selinux_violation_init.*.log", &file_path));
std::string content;
base::ReadFileToString(file_path, &content);
EXPECT_STREQ(content.c_str(), TestSELinuxViolationMessageWithCommContent);
}
TEST_F(SELinuxViolationCollectorTest, CollectOKWithPid) {
// Collector produces a violation report named using the "pid" key.
ASSERT_TRUE(
test_util::CreateFile(test_path_, TestSELinuxViolationMessageWithPid));
EXPECT_TRUE(collector_.Collect(100));
EXPECT_FALSE(IsDirectoryEmpty(test_crash_directory_));
EXPECT_TRUE(test_util::DirectoryHasFileWithPatternAndContents(
test_crash_directory_, "selinux_violation.*.1234.meta",
"sig=sssss-selinux-init"));
FilePath file_path;
EXPECT_TRUE(test_util::DirectoryHasFileWithPattern(
test_crash_directory_, "selinux_violation.*.1234.log", &file_path));
std::string content;
base::ReadFileToString(file_path, &content);
EXPECT_STREQ(content.c_str(), TestSELinuxViolationMessageWithPidContent);
}
TEST_F(SELinuxViolationCollectorTest, CollectOKWithPidAndComm) {
// Collector produces a violation report named using "pid" and "comm" keys.
ASSERT_TRUE(test_util::CreateFile(test_path_,
TestSELinuxViolationMessageWithPidAndComm));
EXPECT_TRUE(collector_.Collect(100));
EXPECT_FALSE(IsDirectoryEmpty(test_crash_directory_));
EXPECT_TRUE(test_util::DirectoryHasFileWithPatternAndContents(
test_crash_directory_, "selinux_violation_init.*.1234.meta",
"sig=sssss-selinux-init"));
FilePath file_path;
EXPECT_TRUE(test_util::DirectoryHasFileWithPattern(
test_crash_directory_, "selinux_violation_init.*.1234.log", &file_path));
std::string content;
base::ReadFileToString(file_path, &content);
EXPECT_STREQ(content.c_str(),
TestSELinuxViolationMessageWithPidAndCommContent);
}
TEST_F(SELinuxViolationCollectorTest, CollectWithInvalidComm) {
// Collector properly sanitizes an invalid "comm" key
ASSERT_TRUE(test_util::CreateFile(
test_path_, TestSELinuxViolationMessageWithInvalidComm));
EXPECT_TRUE(collector_.Collect(100));
EXPECT_FALSE(IsDirectoryEmpty(test_crash_directory_));
EXPECT_TRUE(test_util::DirectoryHasFileWithPatternAndContents(
test_crash_directory_, "selinux_violation____etc_passwd____.*.meta",
"sig=sssss-selinux-init"));
EXPECT_TRUE(test_util::DirectoryHasFileWithPattern(
test_crash_directory_, "selinux_violation____etc_passwd____.*.log",
nullptr));
}
TEST_F(SELinuxViolationCollectorTest, CollectWithLongComm) {
// Collector properly shortens a long "comm" key
ASSERT_TRUE(test_util::CreateFile(test_path_,
TestSELinuxViolationMessageWithLongComm));
EXPECT_TRUE(collector_.Collect(100));
EXPECT_FALSE(IsDirectoryEmpty(test_crash_directory_));
std::string as =
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
EXPECT_TRUE(test_util::DirectoryHasFileWithPatternAndContents(
test_crash_directory_, "selinux_violation_" + as + ".*.meta",
"sig=sssss-selinux-init"));
EXPECT_TRUE(test_util::DirectoryHasFileWithPattern(
test_crash_directory_, "selinux_violation_" + as + ".*.log", nullptr));
}
TEST_F(SELinuxViolationCollectorTest, CollectWithNonTerminatedComm) {
// Collector properly shortens a long "comm" key
ASSERT_TRUE(test_util::CreateFile(
test_path_, TestSELinuxViolationMessageWithNonTerminatedComm));
EXPECT_TRUE(collector_.Collect(100));
EXPECT_FALSE(IsDirectoryEmpty(test_crash_directory_));
EXPECT_TRUE(test_util::DirectoryHasFileWithPatternAndContents(
test_crash_directory_, "selinux_violation_aaaa_AT_context1__.*.meta",
"sig=sssss-selinux-init"));
EXPECT_TRUE(test_util::DirectoryHasFileWithPattern(
test_crash_directory_, "selinux_violation_aaaa_AT_context1__.*.log",
nullptr));
}
TEST_F(SELinuxViolationCollectorTest, CollectSample) {
// Collector produces a violation report.
ASSERT_TRUE(test_util::CreateFile(test_path_, TestSELinuxViolationMessage));
EXPECT_TRUE(collector_.Collect(100));
EXPECT_FALSE(IsDirectoryEmpty(test_crash_directory_));
EXPECT_TRUE(test_util::DirectoryHasFileWithPatternAndContents(
test_crash_directory_, "selinux_violation.*.meta",
"sig=sssss-selinux-init"));
FilePath file_path;
EXPECT_TRUE(test_util::DirectoryHasFileWithPattern(
test_crash_directory_, "selinux_violation.*.log", &file_path));
std::string content;
base::ReadFileToString(file_path, &content);
EXPECT_STREQ(content.c_str(), TestSELinuxViolationMessageContent);
}
TEST_F(SELinuxViolationCollectorTest, UploadWeightedUMA) {
auto metrics_lib = std::make_unique<MetricsLibraryMock>();
MetricsLibraryMock* mock_ref = metrics_lib.get();
collector_.set_metrics_library_for_test(std::move(metrics_lib));
EXPECT_CALL(
*mock_ref,
SendRepeatedEnumToUMA(
"ChromeOS.Stability.Info",
static_cast<int>(CrashCollector::Product::kPlatform),
static_cast<int>(CrashCollector::Product::kMaxValue) + 1, 100))
.WillOnce(Return(true));
// Collector produces a violation report.
ASSERT_TRUE(test_util::CreateFile(test_path_, TestSELinuxViolationMessage));
EXPECT_TRUE(collector_.Collect(100));
}
TEST_F(SELinuxViolationCollectorTest, FailureReportDoesNotExist) {
// SELinux violation report file doesn't exist.
EXPECT_TRUE(collector_.Collect(100));
EXPECT_TRUE(IsDirectoryEmpty(test_crash_directory_));
}
TEST_F(SELinuxViolationCollectorTest, EmptyFailureReport) {
// SELinux violation report file exists, but doesn't have the expected
// contents.
ASSERT_TRUE(test_util::CreateFile(test_path_, ""));
EXPECT_TRUE(collector_.Collect(100));
EXPECT_TRUE(IsDirectoryEmpty(test_crash_directory_));
}
TEST_F(SELinuxViolationCollectorTest, ComputeSeverity) {
CrashCollector::ComputedCrashSeverity computed_severity =
collector_.ComputeSeverity("any executable");
EXPECT_EQ(computed_severity.crash_severity,
CrashCollector::CrashSeverity::kInfo);
EXPECT_EQ(computed_severity.product_group,
CrashCollector::Product::kPlatform);
}