| // Copyright 2021 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 "libhwsec-foundation/tpm_error/handle_auth_failure.h" |
| |
| #include <stddef.h> |
| #include <sys/wait.h> |
| |
| #include <algorithm> |
| #include <string> |
| #include <vector> |
| |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/string_split.h> |
| #include <re2/re2.h> |
| |
| #include "libhwsec-foundation/da_reset/da_resetter.h" |
| #include "libhwsec-foundation/tpm_error/auth_failure_analysis.h" |
| #include "libhwsec-foundation/tpm_error/tpm_error_data.h" |
| #include "libhwsec-foundation/tpm_error/tpm_error_uma_reporter.h" |
| |
| namespace { |
| |
| constexpr int64_t kLogMaxSize = 20'000; |
| constexpr int64_t kLogRemainingSize = 10'000; |
| char lastError[256] = {'\0'}; |
| |
| base::FilePath logFile; |
| base::FilePath permanentLogFile; |
| |
| // Set the error in order to let consumer, e.g tcsd, fetch the error by |
| // FetchAuthFailureError(). |
| void SetLastError(const std::string& msg) { |
| std::string error_msg = msg + ": " + strerror(errno); |
| strncpy(lastError, error_msg.c_str(), sizeof(lastError) - 1); |
| } |
| |
| // Append |msg| to |log_path|, and limit the size of log to |kLogMaxSize|; |
| bool AppendMessage(const base::FilePath& log_path, const std::string& msg) { |
| if (!base::PathExists(log_path)) { |
| return base::WriteFile(log_path, msg); |
| } |
| if (!base::AppendToFile(log_path, msg)) { |
| return false; |
| } |
| |
| int64_t file_size; |
| if (!base::GetFileSize(log_path, &file_size)) { |
| return false; |
| } |
| if (file_size >= kLogMaxSize) { |
| std::string contents; |
| if (!base::ReadFileToString(log_path, &contents)) { |
| return false; |
| } |
| // Truncate log size to |kLogRemainingSize|. |
| int64_t truncate_size = (int64_t)contents.size() - kLogRemainingSize; |
| contents.erase(0, truncate_size); |
| return base::WriteFile(log_path, contents); |
| } |
| return true; |
| } |
| |
| // Handle any log message in this file, and send them to |logFile| and |
| // |permanentLogFile| which is set by InitializeAuthFailureLogging(). |
| bool LogMessageHandler(int severity, |
| const char* file, |
| int line, |
| size_t message_start, |
| const std::string& str) { |
| // Skip if the message is not genenrated by this file. |
| if (strncmp(file, __FILE__, sizeof(__FILE__)) != 0) { |
| return false; |
| } |
| if (!AppendMessage(logFile, str) || !AppendMessage(permanentLogFile, str)) { |
| SetLastError("error logging"); |
| } |
| return severity != logging::LOGGING_FATAL; |
| } |
| |
| // This will log command to the file set by InitializeAuthFailureLogging(). |
| void LogAuthFailureCommand(const struct TpmErrorData& data) { |
| LOG(WARNING) << "auth failure: command " << data.command << ", response " |
| << data.response; |
| } |
| |
| constexpr LazyRE2 auth_failure_command = { |
| R"(auth failure: command (\d+), response (\d+))"}; |
| |
| uint32_t GetCommandHash(const base::FilePath& log_path) { |
| std::string contents; |
| if (!base::ReadFileToString(log_path, &contents)) { |
| return 0; |
| } |
| auto lines = base::SplitString(contents, "\n", base::KEEP_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY); |
| |
| // Parse TpmErrorData from auth failure log. |
| std::vector<struct TpmErrorData> data_set; |
| for (const std::string& line : lines) { |
| struct TpmErrorData data; |
| if (!RE2::PartialMatch(line, *auth_failure_command, &data.command, |
| &data.response)) { |
| continue; |
| } |
| data_set.push_back(data); |
| } |
| |
| // Uniquify collcection of TpmErrorData. |
| std::sort(data_set.begin(), data_set.end()); |
| auto it = std::unique(data_set.begin(), data_set.end()); |
| data_set.resize(std::distance(data_set.begin(), it)); |
| |
| return GetHashFromTpmDataSet(data_set); |
| } |
| |
| } // namespace |
| |
| extern "C" int FetchAuthFailureError(char out[], size_t size) { |
| if (size <= 1) |
| return 0; |
| if (lastError[0] == '\0') |
| return 0; |
| strncpy(out, lastError, size - 1); |
| out[size - 1] = '\0'; |
| lastError[0] = '\0'; |
| return 1; |
| } |
| |
| extern "C" void InitializeAuthFailureLogging(const char* log_path, |
| const char* permanent_log_path) { |
| CHECK(logging::GetLogMessageHandler() == nullptr) |
| << "LogMessageHandler has already been set"; |
| logFile = base::FilePath(log_path); |
| permanentLogFile = base::FilePath(permanent_log_path); |
| logging::SetLogMessageHandler(LogMessageHandler); |
| } |
| |
| extern "C" int CheckAuthFailureHistory(const char* current_path, |
| const char* previous_path, |
| size_t* auth_failure_hash) { |
| base::FilePath current_log(current_path); |
| base::FilePath previous_log(previous_path); |
| |
| if (!base::PathExists(current_log)) { |
| return 0; |
| } |
| |
| int64_t size; |
| if (!base::GetFileSize(current_log, &size)) { |
| SetLastError("error checking file size"); |
| return 0; |
| } |
| // If there is no failure log in |current_log|, nothing to do here. |
| if (size == 0) { |
| return 0; |
| } |
| |
| if (!base::Move(current_log, previous_log)) { |
| SetLastError("error moving file"); |
| return 0; |
| } |
| if (auth_failure_hash) { |
| *auth_failure_hash = GetCommandHash(previous_log); |
| } |
| return 1; |
| } |
| |
| extern "C" int HandleAuthFailure(const struct TpmErrorData* data) { |
| if (!hwsec_foundation::DoesCauseDAIncrease(*data)) { |
| return true; |
| } |
| |
| LogAuthFailureCommand(*data); |
| |
| hwsec_foundation::TpmErrorUmaReporter reporter; |
| |
| reporter.Report(*data); |
| |
| hwsec_foundation::DAResetter resetter; |
| return resetter.ResetDictionaryAttackLock(); |
| } |