1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/win/conflicts/module_load_attempt_log_listener.h"
6 
7 #include <algorithm>
8 #include <memory>
9 #include <utility>
10 
11 #include "base/bind.h"
12 #include "base/check_op.h"
13 #include "base/i18n/case_conversion.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/strings/string_piece.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/task/post_task.h"
18 #include "base/task/thread_pool.h"
19 #include "base/task_runner_util.h"
20 #include "chrome/browser/win/conflicts/module_blacklist_cache_util.h"
21 #include "chrome/chrome_elf/third_party_dlls/public_api.h"
22 
23 namespace {
24 
25 // Drains the log of blocked modules from chrome_elf.dll.
26 // Note that the paths returned are device paths, which starts with
27 // "\Device\Harddisk". They need to be translated to their drive letter path
28 // equivalent.
29 std::vector<std::tuple<base::FilePath, uint32_t, uint32_t>>
DrainLogOnBackgroundTask()30 DrainLogOnBackgroundTask() {
31   // Query the number of bytes needed.
32   uint32_t bytes_needed = 0;
33   DrainLog(nullptr, 0, &bytes_needed);
34 
35   // Drain the log.
36   auto buffer = std::make_unique<uint8_t[]>(bytes_needed);
37   uint32_t bytes_written = DrainLog(buffer.get(), bytes_needed, nullptr);
38   DCHECK_EQ(bytes_needed, bytes_written);
39 
40   // Parse the data using the recommanded pattern for iterating over the log.
41   std::vector<std::tuple<base::FilePath, uint32_t, uint32_t>> blocked_modules;
42   uint8_t* tracker = buffer.get();
43   uint8_t* buffer_end = buffer.get() + bytes_written;
44   while (tracker < buffer_end) {
45     third_party_dlls::LogEntry* entry =
46         reinterpret_cast<third_party_dlls::LogEntry*>(tracker);
47     DCHECK_LE(tracker + third_party_dlls::GetLogEntrySize(entry->path_len),
48               buffer_end);
49 
50     // Only consider blocked modules.
51     // TODO(pmonette): Wire-up loaded modules to ModuleDatabase::OnModuleLoad to
52     // get better visibility into all modules that loads into the browser
53     // process.
54     if (entry->type == third_party_dlls::LogType::kBlocked) {
55       // No log path should be empty.
56       DCHECK(entry->path_len);
57       blocked_modules.emplace_back(
58           base::UTF8ToUTF16(base::StringPiece(entry->path, entry->path_len)),
59           entry->module_size, entry->time_date_stamp);
60     }
61 
62     tracker += third_party_dlls::GetLogEntrySize(entry->path_len);
63   }
64 
65   return blocked_modules;
66 }
67 
68 }  // namespace
69 
ModuleLoadAttemptLogListener(OnModuleBlockedCallback on_module_blocked_callback)70 ModuleLoadAttemptLogListener::ModuleLoadAttemptLogListener(
71     OnModuleBlockedCallback on_module_blocked_callback)
72     : on_module_blocked_callback_(std::move(on_module_blocked_callback)),
73       background_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
74           {base::TaskPriority::BEST_EFFORT,
75            base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN,
76            base::MayBlock()})),
77       // The event starts signaled so that the logs are drained once when the
78       // |object_watcher_| starts waiting on the newly registered event.
79       waitable_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
80                       base::WaitableEvent::InitialState::SIGNALED),
81       weak_ptr_factory_(this) {
82   if (!RegisterLogNotification(waitable_event_.handle()))
83     return;
84 
85   object_watcher_.StartWatchingMultipleTimes(waitable_event_.handle(), this);
86 }
87 
88 ModuleLoadAttemptLogListener::~ModuleLoadAttemptLogListener() = default;
89 
OnObjectSignaled(HANDLE object)90 void ModuleLoadAttemptLogListener::OnObjectSignaled(HANDLE object) {
91   StartDrainingLogs();
92 }
93 
StartDrainingLogs()94 void ModuleLoadAttemptLogListener::StartDrainingLogs() {
95   base::PostTaskAndReplyWithResult(
96       background_task_runner_.get(), FROM_HERE,
97       base::BindOnce(&DrainLogOnBackgroundTask),
98       base::BindOnce(&ModuleLoadAttemptLogListener::OnLogDrained,
99                      weak_ptr_factory_.GetWeakPtr()));
100 }
101 
OnLogDrained(std::vector<std::tuple<base::FilePath,uint32_t,uint32_t>> && blocked_modules)102 void ModuleLoadAttemptLogListener::OnLogDrained(
103     std::vector<std::tuple<base::FilePath, uint32_t, uint32_t>>&&
104         blocked_modules) {
105   for (auto& entry : blocked_modules) {
106     // Translate the device path to their drive letter equivalent then notify
107     // via the callback. The callback is invoked regardless of the result of
108     // GetDriveLetterPath() so that the module still shows up in
109     // chrome://conflicts.
110     base::FilePath module_path = std::move(std::get<0>(entry));
111     bool drive_letter_path_found =
112         GetDriveLetterPath(module_path, &module_path);
113     UMA_HISTOGRAM_BOOLEAN("ThirdPartyModules.GetDriveLetterPathFound",
114                           drive_letter_path_found);
115     on_module_blocked_callback_.Run(std::move(module_path), std::get<1>(entry),
116                                     std::get<2>(entry));
117   }
118 }
119 
GetDriveLetterPath(const base::FilePath & device_path,base::FilePath * drive_letter_path)120 bool ModuleLoadAttemptLogListener::GetDriveLetterPath(
121     const base::FilePath& device_path,
122     base::FilePath* drive_letter_path) {
123   for (size_t retry_count = 0; retry_count < 2; ++retry_count) {
124     // Only update the mapping if a matching device root wasn't found.
125     if (retry_count > 0)
126       UpdateDeviceToLetterPathMapping();
127 
128     for (const auto& element : device_to_letter_path_mapping_) {
129       const base::FilePath& device_root = element.first;
130       const base::string16& drive_letter_root = element.second;
131       if (device_root.IsParent(device_path)) {
132         *drive_letter_path = base::FilePath(
133             drive_letter_root +
134             device_path.value().substr(device_root.value().length()));
135         return true;
136       }
137     }
138   }
139 
140   return false;
141 }
142 
UpdateDeviceToLetterPathMapping()143 void ModuleLoadAttemptLogListener::UpdateDeviceToLetterPathMapping() {
144   const int kDriveMappingSize = 1024;
145   wchar_t drive_mapping[kDriveMappingSize] = {'\0'};
146   if (!::GetLogicalDriveStrings(kDriveMappingSize - 1, drive_mapping))
147     return;
148 
149   device_to_letter_path_mapping_.clear();
150 
151   wchar_t* drive_map_ptr = drive_mapping;
152   wchar_t device_path_as_string[MAX_PATH];
153   wchar_t drive[] = L" :";
154 
155   while (*drive_map_ptr) {
156     drive[0] = drive_map_ptr[0];  // Copy the drive letter.
157 
158     if (::QueryDosDevice(drive, device_path_as_string, MAX_PATH)) {
159       device_to_letter_path_mapping_.emplace_back(
160           base::FilePath(device_path_as_string), drive);
161     }
162 
163     // Move to the next drive letter string, which starts one
164     // increment after the '\0' that terminates the current string.
165     while (*drive_map_ptr++) {
166     }
167   }
168 }
169