| // Copyright 2018 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/selinux_violation_collector.h" |
| |
| #include <map> |
| |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| |
| #include "crash-reporter/util.h" |
| |
| namespace { |
| constexpr char kExecName[] = "selinux-violation"; |
| constexpr char kSignatureKey[] = "sig"; |
| // Truncate values of key=value strings longer than this |
| constexpr size_t kMaxValueLen = 128; |
| } // namespace |
| |
| using base::FilePath; |
| using base::StringPrintf; |
| |
| SELinuxViolationCollector::SELinuxViolationCollector() |
| : CrashCollector("selinux"), violation_report_path_("/dev/stdin") {} |
| |
| SELinuxViolationCollector::~SELinuxViolationCollector() {} |
| |
| bool SELinuxViolationCollector::LoadSELinuxViolation( |
| std::string* content, |
| std::string* signature, |
| std::map<std::string, std::string>* extra_metadata) { |
| std::string violation_report; |
| if (!base::ReadFileToString(violation_report_path_, &violation_report)) { |
| PLOG(ERROR) << "Could not open " << violation_report_path_.value(); |
| return false; |
| } |
| |
| // Report format |
| // First line: signature |
| // Second line: parsed metadata key\x01value\x02key\x01value\x02 |
| // Third+ line: content |
| |
| std::string::size_type signature_end_position = violation_report.find('\n'); |
| *signature = violation_report.substr(0, signature_end_position); |
| |
| violation_report = violation_report.substr(signature_end_position + 1); |
| std::string::size_type metadata_end_position = violation_report.find('\n'); |
| *content = violation_report.substr(metadata_end_position + 1); |
| |
| std::string metadata_string = |
| violation_report.substr(0, metadata_end_position); |
| while (!metadata_string.empty()) { |
| std::string::size_type key_end_position = metadata_string.find('\x01'); |
| std::string::size_type value_end_position = metadata_string.find('\x02'); |
| std::string key = metadata_string.substr(0, key_end_position); |
| std::string value = metadata_string.substr( |
| key_end_position + 1, value_end_position - key_end_position - 1); |
| extra_metadata->emplace(key, value); |
| metadata_string = metadata_string.substr(value_end_position + 1); |
| } |
| |
| return !signature->empty(); |
| } |
| |
| // Extract the value of the given key from the selinux log. |
| // Params: |
| // log: The string with the selinux message |
| // key: The key to search for |
| // has_quotes: True iff the value is surrounded by quotes; e.g. comm="cros" |
| // value: Output parameter. |
| // Return true if the key was present, or false otherwise. |
| bool GetValueFromLog(const std::string& log, |
| const std::string& key, |
| bool has_quotes, |
| std::string* value) { |
| std::string full_key = key + "="; |
| if (has_quotes) { |
| full_key += "\""; |
| } |
| std::string::size_type key_start = log.find(full_key); |
| if (key_start != std::string::npos) { |
| std::string::size_type value_start = key_start + full_key.size(); |
| char end_char = has_quotes ? '"' : ' '; |
| std::string::size_type value_end = log.find(end_char, value_start); |
| std::string::size_type substr_len = value_end - value_start; |
| substr_len = substr_len > kMaxValueLen ? kMaxValueLen : substr_len; |
| *value = log.substr(value_start, substr_len); |
| return true; |
| } |
| return false; |
| } |
| |
| bool SELinuxViolationCollector::Collect() { |
| std::string reason = "normal collection"; |
| bool feedback = true; |
| if (util::IsDeveloperImage() || developer_image_for_testing_) { |
| feedback = true; |
| reason = "always collect from developer builds"; |
| } else if (!is_feedback_allowed_function_()) { |
| reason = "no user consent"; |
| feedback = false; |
| } |
| LOG(INFO) << "Processing selinux violation: " << reason; |
| |
| if (!feedback) |
| return true; |
| |
| std::string violation_signature; |
| std::string content; |
| std::map<std::string, std::string> extra_metadata; |
| if (!LoadSELinuxViolation(&content, &violation_signature, &extra_metadata)) |
| return true; |
| |
| FilePath crash_directory; |
| if (!GetCreatedCrashDirectoryByEuid(kRootUid, &crash_directory, nullptr)) |
| return true; |
| |
| // Give crash files more unique names by taking the "comm" identifier |
| // (if one is present) and adding it to the "selinux-violation" prefix. |
| std::string name_prefix = kExecName; |
| |
| std::string comm; |
| if (GetValueFromLog(content, "comm", /*has_quotes=*/true, &comm)) { |
| name_prefix += "_" + comm; |
| } |
| |
| std::string pid_str; |
| int pid = 0; |
| if (GetValueFromLog(content, "pid", /*has_quotes=*/false, &pid_str)) { |
| if (!base::StringToInt(pid_str, &pid)) { |
| // Fall back to a pid of 0 on any errors. |
| pid = 0; |
| } |
| } |
| |
| std::string dump_basename = |
| FormatDumpBasename(name_prefix, time(nullptr), pid); |
| FilePath meta_path = GetCrashPath(crash_directory, dump_basename, "meta"); |
| FilePath log_path = GetCrashPath(crash_directory, dump_basename, "log"); |
| |
| if (WriteNewFile(log_path, content.data(), content.length()) != |
| static_cast<int>(content.length())) { |
| PLOG(WARNING) << "Failed to write audit message to " << log_path.value(); |
| return true; |
| } |
| |
| AddCrashMetaData(kSignatureKey, violation_signature); |
| |
| for (const auto& metadata : extra_metadata) |
| AddCrashMetaUploadData(metadata.first, metadata.second); |
| |
| FinishCrash(meta_path, kExecName, log_path.BaseName().value()); |
| |
| return true; |
| } |