blob: 5749b4551e06686a033dbe45f41191718dc836d3 [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/kernel_warning_collector.h"
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/strcat.h>
#include <base/strings/stringprintf.h>
#include <re2/re2.h>
#include "crash-reporter/util.h"
namespace {
const char kGenericWarningExecName[] = "kernel-warning";
const char kWifiWarningExecName[] = "kernel-wifi-warning";
const char kSMMUFaultExecName[] = "kernel-smmu-fault";
const char kSuspendWarningExecName[] = "kernel-suspend-warning";
const char kKernelIwlwifiErrorExecName[] = "kernel-iwlwifi-error";
const char kKernelWarningSignatureKey[] = "sig";
const pid_t kKernelPid = 0;
} // namespace
using base::FilePath;
using base::StringPrintf;
KernelWarningCollector::KernelWarningCollector()
: CrashCollector("kernel_warning"), warning_report_path_("/dev/stdin") {}
KernelWarningCollector::~KernelWarningCollector() {}
bool KernelWarningCollector::LoadKernelWarning(std::string* content,
std::string* signature,
std::string* func_name,
WarningType type) {
FilePath kernel_warning_path(warning_report_path_.c_str());
if (!base::ReadFileToString(kernel_warning_path, content)) {
PLOG(ERROR) << "Could not open " << kernel_warning_path.value();
return false;
}
if (type == kIwlwifi) {
if (!ExtractIwlwifiSignature(*content, signature, func_name)) {
return false;
} else if (signature->length() > 0) {
return true;
}
} else if (type == kSMMUFault) {
if (!ExtractSMMUFaultSignature(*content, signature, func_name)) {
return false;
} else if (signature->length() > 0) {
return true;
}
} else {
if (!ExtractSignature(*content, signature, func_name)) {
return false;
} else if (signature->length() > 0) {
return true;
}
}
LOG(WARNING) << "Couldn't find match for signature line. "
<< "Falling back to first line of warning.";
*signature = content->substr(0, content->find('\n'));
return true;
}
// Extract the crashing function name from the signature.
// Signature example: 6a839c19-lkdtm_do_action+0x225/0x5bc
// Signature example2: 6a839c19-unknown-function+0x161/0x344 [iwlmvm]
constexpr LazyRE2 sig_re = {R"(^[0-9a-fA-F]+-([0-9a-zA-Z_-]+)\+.*$)"};
bool KernelWarningCollector::ExtractSignature(const std::string& content,
std::string* signature,
std::string* func_name) {
// The signature is in the first or second line.
// First, try the first, and if it's not there, try the second.
std::string::size_type end_position = content.find('\n');
if (end_position == std::string::npos) {
LOG(ERROR) << "unexpected kernel warning format";
return false;
}
size_t start = 0;
for (int i = 0; i < 2; i++) {
*signature = content.substr(start, end_position - start);
if (RE2::FullMatch(*signature, *sig_re, func_name)) {
return true;
} else {
LOG(INFO) << *signature << " does not match regex";
signature->clear();
func_name->clear();
}
// Else, try the next line.
start = end_position + 1;
end_position = content.find('\n', start);
}
return true;
}
// Extract the crashing function name from the signature.
// The crashing function for the lmac appears after the line:
// Loaded firmware version: 46.b20aefee.0
// Signature example: 0x00000084 | NMI_INTERRUPT_UNKNOWN
// The crashing function for the umac appears after the line:
// iwlwifi 0000:00:0c.0: Status: 0x00000100, count: 7
// Signature example: 0x20000066 | NMI_INTERRUPT_HOST
constexpr LazyRE2 before_iwlwifi_assert_lmac = {
R"(iwlwifi (?:.+): Loaded firmware version:)"};
constexpr LazyRE2 before_iwlwifi_assert_umac = {R"(iwlwifi (?:.+): Status:)"};
constexpr LazyRE2 iwlwifi_sig_re = {
R"(iwlwifi (?:.+): \b(\w+)\b \| \b(\w+)\b)"};
enum class LineType {
Umac,
Lmac,
None,
CheckUmac,
};
bool KernelWarningCollector::ExtractIwlwifiSignature(const std::string& content,
std::string* signature,
std::string* func_name) {
LineType last_line = LineType::None;
// Extracting the function name depends on where the assert occurs in
// lmac/umac:
// 1- Assert in lmac:
// The umac have the default assert value in its signature
// (0x20000070).
// 2- Assert in umac:
// The lmac have the default assert value in its signature
// (0x00000071).
// Based on that, the function name in lmac/umac should be ignored if the
// assert number in the signature is equal to 0x00000071 in lmac or 0x20000070
// in umac.
const std::string default_lmac_assert = "0x00000071";
const std::string default_umac_assert = "0x20000070";
std::string assert_number;
std::string::size_type end_position = content.find('\n');
if (end_position == std::string::npos) {
LOG(ERROR) << "unexpected kernel iwlwifi error format";
return false;
}
// Look for the signature in the lmac and check the assert number. if the lmac
// assert number not equal (0x00000071), then return that as the signature.
// Otherwise, check the signature of the umac. if the umac assert number not
// equal (0x20000070), then return that as the signature. Otherwise, break.
size_t start = 0;
size_t end = content.size();
while (start < end && end_position != std::string::npos) {
*signature = content.substr(start, end_position - start);
if (last_line == LineType::None) {
if (RE2::PartialMatch(*signature, *before_iwlwifi_assert_lmac)) {
last_line = LineType::Lmac;
}
} else if (last_line == LineType::Lmac) {
// Check the signature of the lmac.
if (RE2::PartialMatch(*signature, *iwlwifi_sig_re, &assert_number,
func_name)) {
if (default_lmac_assert != assert_number) {
return true;
} else {
// Check umac if the lmac assertion number == default_lmac_assert.
last_line = LineType::CheckUmac;
signature->clear();
func_name->clear();
}
} else {
// Break if the signature of the lmac didn't match.
LOG(INFO) << *signature << " does not match regex";
signature->clear();
func_name->clear();
break;
}
} else if (last_line == LineType::CheckUmac) {
// Check the line before the umac signature.
if (RE2::PartialMatch(*signature, *before_iwlwifi_assert_umac)) {
last_line = LineType::Umac;
}
} else if (last_line == LineType::Umac) {
// Check the signature of the umac.
if (RE2::PartialMatch(*signature, *iwlwifi_sig_re, &assert_number,
func_name)) {
if (default_umac_assert != assert_number) {
return true;
} else {
// Break if the umac assertion number == default_umac_assert.
LOG(ERROR) << "unexpected kernel iwlwifi error format. "
"Both umac/lmac dumps have the default assert numbers.";
signature->clear();
func_name->clear();
break;
}
} else {
// Break if the signature of the umac didn't match.
LOG(INFO) << *signature << " does not match regex";
signature->clear();
func_name->clear();
break;
}
}
// Else, try the next line.
start = end_position + 1;
end_position = content.find('\n', start);
}
return true;
}
constexpr LazyRE2 smmu_sig_re = {R"((\S+): Unhandled context fault: (.*))"};
bool KernelWarningCollector::ExtractSMMUFaultSignature(
const std::string& content,
std::string* signature,
std::string* func_name) {
// The signature is the part of the line after "Unhandled context fault:"
std::string line;
std::string::size_type end_position = content.find('\n');
if (end_position == std::string::npos) {
LOG(ERROR) << "unexpected smmu fault warning format";
return false;
}
line = content.substr(0, end_position);
if (RE2::PartialMatch(line, *smmu_sig_re, func_name, signature)) {
return true;
}
LOG(INFO) << line << " does not match regex";
signature->clear();
func_name->clear();
return false;
}
bool KernelWarningCollector::Collect(WarningType type) {
LOG(INFO) << "Processing kernel warning";
std::string kernel_warning;
std::string warning_signature;
std::string func_name;
if (!LoadKernelWarning(&kernel_warning, &warning_signature, &func_name,
type)) {
return false;
}
FilePath root_crash_directory;
if (!GetCreatedCrashDirectoryByEuid(kRootUid, &root_crash_directory,
nullptr)) {
return false;
}
const char* exec_name;
if (type == kWifi)
exec_name = kWifiWarningExecName;
else if (type == kSMMUFault)
exec_name = kSMMUFaultExecName;
else if (type == kSuspend)
exec_name = kSuspendWarningExecName;
else if (type == kGeneric)
exec_name = kGenericWarningExecName;
else
exec_name = kKernelIwlwifiErrorExecName;
// Attempt to make the exec_name more unique to avoid collisions.
if (!func_name.empty()) {
func_name.insert(func_name.begin(), '_');
} else {
LOG(WARNING) << "Couldn't extract function name from signature. "
"Going on without it.";
}
std::string dump_basename = FormatDumpBasename(
base::StrCat({exec_name, func_name}), time(nullptr), kKernelPid);
FilePath log_path =
GetCrashPath(root_crash_directory, dump_basename, "log.gz");
FilePath meta_path =
GetCrashPath(root_crash_directory, dump_basename, "meta");
FilePath kernel_crash_path = root_crash_directory.Append(
StringPrintf("%s.kcrash", dump_basename.c_str()));
// We must use WriteNewFile instead of base::WriteFile as we
// do not want to write with root access to a symlink that an attacker
// might have created.
if (WriteNewFile(kernel_crash_path, kernel_warning.data(),
kernel_warning.length()) !=
static_cast<int>(kernel_warning.length())) {
LOG(INFO) << "Failed to write kernel warning to "
<< kernel_crash_path.value().c_str();
return true;
}
AddCrashMetaData(kKernelWarningSignatureKey, warning_signature);
// Get the log contents, compress, and attach to crash report.
bool result = GetLogContents(log_config_path_, exec_name, log_path);
if (result) {
AddCrashMetaUploadFile("log", log_path.BaseName().value());
}
FinishCrash(meta_path, exec_name, kernel_crash_path.BaseName().value());
return true;
}