blob: e0e2230f1d7b32108d2aff6f0abdbb2ebc3fbb7b [file] [log] [blame]
// Copyright 2018 The Chromium 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 "chrome/browser/conflicts/module_load_attempt_log_listener_win.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/i18n/case_conversion.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/sha1.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task_runner_util.h"
#include "base/task_scheduler/post_task.h"
#include "chrome/browser/conflicts/module_blacklist_cache_util_win.h"
#include "chrome_elf/third_party_dlls/logging_api.h"
#include "chrome_elf/third_party_dlls/packed_list_format.h"
namespace {
// Drains the log of blocked modules from chrome_elf.dll.
// Note that the paths returned are device paths, which starts with
// "\Device\Harddisk". They need to be translated to their drive letter path
// equivalent.
std::vector<std::tuple<base::FilePath, uint32_t, uint32_t>>
DrainLogOnBackgroundTask() {
// Query the number of bytes needed.
uint32_t bytes_needed = 0;
DrainLog(nullptr, 0, &bytes_needed);
// Drain the log.
auto buffer = std::make_unique<uint8_t[]>(bytes_needed);
uint32_t bytes_written = DrainLog(buffer.get(), bytes_needed, nullptr);
DCHECK_EQ(bytes_needed, bytes_written);
// Parse the data using the recommanded pattern for iterating over the log.
std::vector<std::tuple<base::FilePath, uint32_t, uint32_t>> blocked_modules;
uint8_t* tracker = buffer.get();
uint8_t* buffer_end = buffer.get() + bytes_written;
while (tracker < buffer_end) {
third_party_dlls::LogEntry* entry =
reinterpret_cast<third_party_dlls::LogEntry*>(tracker);
DCHECK_LE(tracker + third_party_dlls::GetLogEntrySize(entry->path_len),
buffer_end);
// Only consider blocked modules.
// TODO(pmonette): Wire-up loaded modules to ModuleDatabase::OnModuleLoad to
// get better visibility into all modules that loads into the browser
// process.
if (entry->type == third_party_dlls::LogType::kBlocked) {
// No log path should be empty.
DCHECK(entry->path_len);
blocked_modules.emplace_back(
base::UTF8ToUTF16(base::StringPiece(entry->path, entry->path_len)),
entry->module_size, entry->time_date_stamp);
}
tracker += third_party_dlls::GetLogEntrySize(entry->path_len);
}
return blocked_modules;
}
} // namespace
ModuleLoadAttemptLogListener::ModuleLoadAttemptLogListener(
OnModuleBlockedCallback on_module_blocked_callback)
: on_module_blocked_callback_(std::move(on_module_blocked_callback)),
background_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
{base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN,
base::MayBlock()})),
// The event starts signaled so that the logs are drained once when the
// |object_watcher_| starts waiting on the newly registered event.
waitable_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::SIGNALED),
weak_ptr_factory_(this) {
if (!RegisterLogNotification(waitable_event_.handle()))
return;
object_watcher_.StartWatchingMultipleTimes(waitable_event_.handle(), this);
}
ModuleLoadAttemptLogListener::~ModuleLoadAttemptLogListener() = default;
void ModuleLoadAttemptLogListener::OnObjectSignaled(HANDLE object) {
StartDrainingLogs();
}
void ModuleLoadAttemptLogListener::StartDrainingLogs() {
base::PostTaskAndReplyWithResult(
background_task_runner_.get(), FROM_HERE,
base::BindOnce(&DrainLogOnBackgroundTask),
base::BindOnce(&ModuleLoadAttemptLogListener::OnLogDrained,
weak_ptr_factory_.GetWeakPtr()));
}
void ModuleLoadAttemptLogListener::OnLogDrained(
std::vector<std::tuple<base::FilePath, uint32_t, uint32_t>>&&
blocked_modules) {
for (auto& entry : blocked_modules) {
// Translate the device path to their drive letter equivalent then notify
// via the callback. The callback is invoked regardless of the result of
// GetDriveLetterPath() so that the module still shows up in
// chrome://conflicts.
base::FilePath module_path = std::move(std::get<0>(entry));
bool drive_letter_path_found =
GetDriveLetterPath(module_path, &module_path);
UMA_HISTOGRAM_BOOLEAN("ThirdPartyModules.GetDriveLetterPathFound",
drive_letter_path_found);
on_module_blocked_callback_.Run(std::move(module_path), std::get<1>(entry),
std::get<2>(entry));
}
}
bool ModuleLoadAttemptLogListener::GetDriveLetterPath(
const base::FilePath& device_path,
base::FilePath* drive_letter_path) {
for (size_t retry_count = 0; retry_count < 2; ++retry_count) {
// Only update the mapping if a matching device root wasn't found.
if (retry_count > 0)
UpdateDeviceToLetterPathMapping();
for (const auto& element : device_to_letter_path_mapping_) {
const base::FilePath& device_root = element.first;
const base::string16& drive_letter_root = element.second;
if (device_root.IsParent(device_path)) {
*drive_letter_path = base::FilePath(
drive_letter_root +
device_path.value().substr(device_root.value().length()));
return true;
}
}
}
return false;
}
void ModuleLoadAttemptLogListener::UpdateDeviceToLetterPathMapping() {
const int kDriveMappingSize = 1024;
wchar_t drive_mapping[kDriveMappingSize] = {'\0'};
if (!::GetLogicalDriveStrings(kDriveMappingSize - 1, drive_mapping))
return;
device_to_letter_path_mapping_.clear();
wchar_t* drive_map_ptr = drive_mapping;
wchar_t device_path_as_string[MAX_PATH];
wchar_t drive[] = L" :";
while (*drive_map_ptr) {
drive[0] = drive_map_ptr[0]; // Copy the drive letter.
if (::QueryDosDevice(drive, device_path_as_string, MAX_PATH)) {
device_to_letter_path_mapping_.emplace_back(
base::FilePath(device_path_as_string), drive);
}
// Move to the next drive letter string, which starts one
// increment after the '\0' that terminates the current string.
while (*drive_map_ptr++) {
}
}
}