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