1 // Copyright 2019 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/chrome_cleaner/engines/target/libraries.h"
6
7 #include <windows.h>
8
9 #include <delayimp.h>
10 #include <stdint.h>
11 #include <string.h>
12
13 #include <map>
14 #include <string>
15 #include <unordered_map>
16 #include <utility>
17
18 #include "base/callback.h"
19 #include "base/files/file_path.h"
20 #include "base/files/file_util.h"
21 #include "base/logging.h"
22 #include "base/native_library.h"
23 #include "base/strings/string_piece.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "chrome/chrome_cleaner/buildflags.h"
26 #include "chrome/chrome_cleaner/engines/common/engine_resources.h"
27 #include "chrome/chrome_cleaner/os/digest_verifier.h"
28 #include "chrome/chrome_cleaner/os/file_path_sanitization.h"
29 #include "chrome/chrome_cleaner/os/resource_util.h"
30 #include "chrome/chrome_cleaner/os/system_util.h"
31 #include "chrome/chrome_cleaner/settings/settings.h"
32 #include "sandbox/win/src/sandbox_factory.h"
33
34 namespace chrome_cleaner {
35
36 namespace {
37
38 internal::LibraryPostExtractionCallback* g_post_extraction_callback = nullptr;
39
40 // Extracts engine DLLs from the embedded resources and writes them on disk.
41 // Should be called before any call to an engine library.
ExtractEmbeddedLibraries(Engine::Name engine,const base::FilePath & extraction_dir)42 bool ExtractEmbeddedLibraries(Engine::Name engine,
43 const base::FilePath& extraction_dir) {
44 std::unordered_map<std::wstring, int> resource_names_to_id =
45 GetEmbeddedLibraryResourceIds(engine);
46 for (const auto& name_id : resource_names_to_id) {
47 LOG(INFO) << "Extracting " << name_id.first << " to "
48 << extraction_dir.value();
49 base::StringPiece library_data;
50 bool success =
51 LoadResourceOfKind(name_id.second, L"LIBRARY", &library_data);
52 if (!success) {
53 LOG(ERROR) << "Failed to load " << name_id.first << " from resources";
54 return false;
55 }
56 if (base::WriteFile(extraction_dir.Append(name_id.first),
57 library_data.data(), library_data.size()) < 0) {
58 PLOG(ERROR) << "Failed to write " << name_id.first;
59 return false;
60 }
61 }
62
63 if (g_post_extraction_callback)
64 g_post_extraction_callback->Run(extraction_dir);
65
66 return true;
67 }
68
VerifyEngineLibraryAllowed(Engine::Name engine,const std::wstring & requested_library)69 void VerifyEngineLibraryAllowed(Engine::Name engine,
70 const std::wstring& requested_library) {
71 #if !BUILDFLAG(IS_OFFICIAL_CHROME_CLEANER_BUILD)
72 if (Settings::GetInstance()->run_without_sandbox_for_testing())
73 return;
74 #endif
75
76 if (GetLibrariesToLoad(engine).count(requested_library) &&
77 !sandbox::SandboxFactory::GetTargetServices()) {
78 // Deny the load.
79 LOG(FATAL) << "Aborting due to attempt to load " << requested_library
80 << " outside sandbox";
81 }
82 }
83
VerifyRunningInSandbox()84 void VerifyRunningInSandbox() {
85 #if !BUILDFLAG(IS_OFFICIAL_CHROME_CLEANER_BUILD)
86 if (Settings::GetInstance()->run_without_sandbox_for_testing())
87 return;
88 #endif
89
90 CHECK(sandbox::SandboxFactory::GetTargetServices())
91 << "Attempt to load engine libraries outside of the sandbox process";
92 }
93
DllLoadHook(unsigned dliNotify,PDelayLoadInfo pdli)94 extern "C" FARPROC WINAPI DllLoadHook(unsigned dliNotify, PDelayLoadInfo pdli) {
95 switch (dliNotify) {
96 case dliNotePreLoadLibrary: {
97 const std::wstring requested_library = base::ASCIIToWide(pdli->szDll);
98 const Engine::Name engine = Settings::GetInstance()->engine();
99
100 VerifyEngineLibraryAllowed(engine, requested_library);
101
102 #if !BUILDFLAG(IS_OFFICIAL_CHROME_CLEANER_BUILD)
103 const std::unordered_map<std::wstring, std::wstring>
104 library_replacements = GetLibraryTestReplacements(engine);
105 if (library_replacements.count(requested_library)) {
106 // Try loading the original DLL first, then try the replacement.
107 HMODULE library = ::LoadLibrary(requested_library.c_str());
108 if (library == nullptr) {
109 const std::wstring& fallback_library =
110 library_replacements.find(requested_library)->second;
111 PLOG(WARNING) << "Could not load " << requested_library
112 << "; falling back to " << fallback_library;
113 library = ::LoadLibrary(fallback_library.c_str());
114 PLOG_IF(FATAL, library == nullptr)
115 << "Failed to load " << fallback_library;
116 }
117 return reinterpret_cast<FARPROC>(library);
118 }
119 #endif
120 return nullptr;
121 }
122 default:
123 return nullptr;
124 }
125 }
126
127 extern "C" const PfnDliHook __pfnDliNotifyHook2 = DllLoadHook;
128
129 } // namespace
130
131 namespace internal {
132
SetLibraryPostExtractionCallbackForTesting(LibraryPostExtractionCallback post_extraction_callback)133 void SetLibraryPostExtractionCallbackForTesting(
134 LibraryPostExtractionCallback post_extraction_callback) {
135 ClearLibraryPostExtractionCallbackForTesting();
136 g_post_extraction_callback = new LibraryPostExtractionCallback();
137 *g_post_extraction_callback = std::move(post_extraction_callback);
138 }
139
ClearLibraryPostExtractionCallbackForTesting()140 void ClearLibraryPostExtractionCallbackForTesting() {
141 delete g_post_extraction_callback;
142 g_post_extraction_callback = nullptr;
143 }
144
145 } // namespace internal
146
LoadAndValidateLibraries(Engine::Name engine,const base::FilePath & extraction_dir)147 bool LoadAndValidateLibraries(Engine::Name engine,
148 const base::FilePath& extraction_dir) {
149 // This function is invoked in the sandbox target process before LowerToken is
150 // called, so the process will have read access to the DLL files. For
151 // libraries using DELAYLOAD, DllLoadHook will also be called some time later
152 // (when the first attempt is made to access a symbol defined in the library.)
153 // This should happen after LowerToken.
154 //
155 // Calling LoadLibrary here will just load the library into memory, but not
156 // update the DELAYLOAD thunks to point to its symbols, so it won't be used
157 // for anything. But it does mean that DllLoadHook will succeed in "loading"
158 // the library even if the sandbox would usually block access (because it
159 // just gets another handle to the library already in memory.) DllLoadHooks
160 // will then update the thunks so that the library is actually used.
161 //
162 // Some engine libraries are dynamically loaded by the engine, not through
163 // DELAYLOAD. Again calling LoadLibrary here will allow the engine to access
164 // them despite the sandbox. Also it will mark the file in use, ensuring that
165 // the version that is validated here is not overwritten before it is loaded.
166 VerifyRunningInSandbox();
167
168 const std::set<std::wstring> libraries_to_load = GetLibrariesToLoad(engine);
169 if (libraries_to_load.empty())
170 return true;
171
172 // Proceed even if the library extraction fails. It happens to the elevated
173 // cleaner process as it is executed in the same directory as the non-elevated
174 // one, and the directory already has libraries on the disk. If the libraries
175 // are corrupted, DigestVerifier will catch it below.
176 ExtractEmbeddedLibraries(engine, extraction_dir);
177
178 // If modules might be replaced for testing, disable validation and do not
179 // fail on loading errors. The real DLL's may not be present, so don't abort
180 // just because they can't be loaded.
181 const bool enable_validation = GetLibraryTestReplacements(engine).empty();
182
183 #if BUILDFLAG(IS_OFFICIAL_CHROME_CLEANER_BUILD)
184 CHECK(enable_validation)
185 << "Library validation should not be disabled in official build";
186 #endif
187
188 scoped_refptr<DigestVerifier> digest_verifier =
189 DigestVerifier::CreateFromResource(GetLibrariesDigestResourcesId(engine));
190 CHECK(digest_verifier);
191
192 // Load all libraries and validate them if required.
193 for (const std::wstring& library_name : libraries_to_load) {
194 base::FilePath dll_path = extraction_dir.Append(library_name);
195
196 // Open a handle to the DLL before verifying it.
197 base::NativeLibraryLoadError error;
198 base::LoadNativeLibrary(dll_path, &error); // Return value leaks.
199 if (error.code && enable_validation) {
200 LOG(ERROR) << "Error loading library " << SanitizePath(dll_path) << ": "
201 << error.ToString();
202 return false;
203 }
204
205 if (enable_validation && !digest_verifier->IsKnownFile(dll_path)) {
206 LOG(ERROR) << SanitizePath(dll_path) << " does not have a valid digest";
207 return false;
208 }
209 }
210
211 return true;
212 }
213
214 } // namespace chrome_cleaner
215