elevated_recovery_impl.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  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. #include "chrome/elevation_service/elevated_recovery_impl.h"
  5. #include <objbase.h>
  6. #include <string>
  7. #include <utility>
  8. #include "base/base_paths.h"
  9. #include "base/bind.h"
  10. #include "base/callback_helpers.h"
  11. #include "base/command_line.h"
  12. #include "base/files/file_enumerator.h"
  13. #include "base/files/file_path.h"
  14. #include "base/files/file_util.h"
  15. #include "base/files/scoped_temp_dir.h"
  16. #include "base/logging.h"
  17. #include "base/path_service.h"
  18. #include "base/process/launch.h"
  19. #include "base/process/process.h"
  20. #include "base/strings/utf_string_conversions.h"
  21. #include "base/version.h"
  22. #include "base/win/scoped_process_information.h"
  23. #include "chrome/install_static/install_util.h"
  24. #include "third_party/zlib/google/zip.h"
  25. namespace elevation_service {
  26. namespace {
  27. // Input CRX files over 10 MB are considered invalid.
  28. constexpr int64_t kMaxFileSize = 10u * 1000u * 1000u;
  29. // The hard-coded file name that the Recovery CRX is copied to.
  30. constexpr base::FilePath::CharType kCRXFileName[] =
  31. FILE_PATH_LITERAL("ChromeRecoveryCRX.crx");
  32. // The hard-coded Recovery subdirectory where the CRX is unpacked and executed.
  33. constexpr base::FilePath::CharType kRecoveryDirectory[] =
  34. FILE_PATH_LITERAL("ChromeRecovery");
  35. // The hard-coded Recovery executable. This file comes from the CRX, and we
  36. // create an elevated process from it.
  37. constexpr base::FilePath::CharType kRecoveryExeName[] =
  38. FILE_PATH_LITERAL("ChromeRecovery.exe");
  39. // The hard-coded SHA256 of the SubjectPublicKeyInfo used to sign the Recovery
  40. // CRX which contains ChromeRecovery.exe.
  41. std::vector<uint8_t> GetRecoveryCRXHash() {
  42. return std::vector<uint8_t>{0x5f, 0x94, 0xe0, 0x3c, 0x64, 0x30, 0x9f, 0xbc,
  43. 0xfe, 0x00, 0x9a, 0x27, 0x3e, 0x52, 0xbf, 0xa5,
  44. 0x84, 0xb9, 0xb3, 0x75, 0x07, 0x29, 0xde, 0xfa,
  45. 0x32, 0x76, 0xd9, 0x93, 0xb5, 0xa3, 0xce, 0x02};
  46. }
  47. // This function is only meant to be called when a Windows API function errors
  48. // out, and the corresponding ::GetLastResult is expected to be set to an error.
  49. // There could be cases where ::GetLastError() is not set correctly, this
  50. // function returns E_FAIL in those cases.
  51. HRESULT HRESULTFromLastError() {
  52. const auto error_code = ::GetLastError();
  53. return (error_code != ERROR_SUCCESS) ? HRESULT_FROM_WIN32(error_code)
  54. : E_FAIL;
  55. }
  56. // Opens and returns the COM caller's |process| given the process id, or the
  57. // current process if |proc_id| is 0.
  58. HRESULT OpenCallingProcess(uint32_t proc_id, base::Process* process) {
  59. DCHECK(proc_id);
  60. DCHECK(process);
  61. HRESULT hr = ::CoImpersonateClient();
  62. if (FAILED(hr))
  63. return hr;
  64. base::ScopedClosureRunner revert_to_self(
  65. base::BindOnce([]() { ::CoRevertToSelf(); }));
  66. *process = base::Process::OpenWithAccess(proc_id, PROCESS_DUP_HANDLE);
  67. return process->IsValid() ? S_OK : HRESULTFromLastError();
  68. }
  69. // Opens and returns a base::File instance for the |file_path|. We impersonate
  70. // the COM caller when opening the base::File instance. This is to ensure that
  71. // the COM caller has access to the file.
  72. HRESULT OpenFileImpersonated(const base::FilePath& file_path,
  73. int flags,
  74. base::File* file) {
  75. DCHECK(file);
  76. HRESULT hr = ::CoImpersonateClient();
  77. if (FAILED(hr))
  78. return hr;
  79. base::ScopedClosureRunner revert_to_self(
  80. base::BindOnce([]() { ::CoRevertToSelf(); }));
  81. file->Initialize(file_path, flags);
  82. if (!file->IsValid())
  83. return HRESULTFromLastError();
  84. if (::GetFileType(file->GetPlatformFile()) != FILE_TYPE_DISK)
  85. return E_INVALIDARG;
  86. int64_t from_length = file->GetLength();
  87. return from_length > 0 && from_length < kMaxFileSize ? S_OK : E_INVALIDARG;
  88. }
  89. // Opens |from| while impersonating the COM caller, and then copies the contents
  90. // of |from| to |to|. |from| is opened impersonated to ensure that the COM
  91. // caller has access to the file.
  92. HRESULT CopyFileImpersonated(const base::FilePath from,
  93. const base::FilePath& to) {
  94. base::File from_file;
  95. HRESULT hr =
  96. OpenFileImpersonated(from,
  97. base::File::FLAG_READ | base::File::FLAG_OPEN |
  98. base::File::FLAG_WIN_SEQUENTIAL_SCAN,
  99. &from_file);
  100. if (FAILED(hr))
  101. return hr;
  102. base::File to_file;
  103. to_file.Initialize(to, base::File::FLAG_WRITE |
  104. base::File::FLAG_CREATE_ALWAYS |
  105. base::File::FLAG_WIN_SEQUENTIAL_SCAN);
  106. if (!to_file.IsValid())
  107. return HRESULTFromLastError();
  108. constexpr size_t kBufferSize = 0x10000;
  109. std::vector<char> buffer(kBufferSize);
  110. for (uint64_t total_bytes_read = 0;;) {
  111. const int bytes_read =
  112. from_file.ReadAtCurrentPos(buffer.data(), buffer.size());
  113. if (bytes_read < 0)
  114. return HRESULTFromLastError();
  115. if (bytes_read == 0)
  116. return S_OK;
  117. total_bytes_read += bytes_read;
  118. if (total_bytes_read > kMaxFileSize)
  119. return E_INVALIDARG;
  120. const int bytes_written = to_file.WriteAtCurrentPos(&buffer[0], bytes_read);
  121. if (bytes_written < 0)
  122. return HRESULTFromLastError();
  123. if (bytes_written != bytes_read)
  124. return E_UNEXPECTED;
  125. }
  126. NOTREACHED();
  127. return S_OK;
  128. }
  129. // Validates the provided CRX using the |crx_hash|, and if validation succeeds,
  130. // unpacks the CRX under |unpack_under_path|. Returns the unpacked CRX
  131. // directory in |unpacked_crx_dir|.
  132. HRESULT ValidateAndUnpackCRX(const base::FilePath& from_crx_path,
  133. const crx_file::VerifierFormat& crx_format,
  134. const std::vector<uint8_t>& crx_hash,
  135. const base::FilePath& unpack_under_path,
  136. base::ScopedTempDir* unpacked_crx_dir) {
  137. DCHECK(unpacked_crx_dir);
  138. base::ScopedTempDir to_dir;
  139. if (!to_dir.CreateUniqueTempDirUnderPath(unpack_under_path))
  140. return HRESULTFromLastError();
  141. const base::FilePath to_crx_path = to_dir.GetPath().Append(kCRXFileName);
  142. // Copy |from_crx_path| impersonated. This is to prevent us from copying files
  143. // that may not be accessible to the calling COM user.
  144. HRESULT hr = CopyFileImpersonated(from_crx_path, to_crx_path);
  145. if (FAILED(hr))
  146. return hr;
  147. std::string public_key;
  148. if (crx_file::Verify(to_crx_path, crx_format, {crx_hash}, {}, &public_key,
  149. nullptr, /*compressed_verified_contents=*/nullptr) !=
  150. crx_file::VerifierResult::OK_FULL) {
  151. return CRYPT_E_NO_MATCH;
  152. }
  153. if (!zip::Unzip(to_crx_path, to_dir.GetPath()))
  154. return E_UNEXPECTED;
  155. LOG_IF(WARNING, !base::DeleteFile(to_crx_path));
  156. LOG_IF(WARNING, !unpacked_crx_dir->Set(to_dir.Take()));
  157. return S_OK;
  158. }
  159. // Runs the executable |path_and_name| with the provided |args|. The returned
  160. // |proc_handle| is a process handle that is valid for the |calling_process|
  161. // process.
  162. HRESULT LaunchCmd(const base::CommandLine& command_line,
  163. const base::Process& calling_process,
  164. base::win::ScopedHandle* proc_handle) {
  165. DCHECK(!command_line.GetCommandLineString().empty());
  166. DCHECK(proc_handle);
  167. base::LaunchOptions options = {};
  168. options.feedback_cursor_off = true;
  169. base::GetTempDir(&options.current_directory);
  170. base::Process proc = base::LaunchProcess(command_line, options);
  171. if (!proc.IsValid())
  172. return HRESULTFromLastError();
  173. HANDLE duplicate_proc_handle = nullptr;
  174. constexpr DWORD desired_access =
  175. PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE;
  176. bool res = ::DuplicateHandle(
  177. ::GetCurrentProcess(), // Current process.
  178. proc.Handle(), // Process handle to duplicate.
  179. calling_process.Handle(), // Process receiving the handle.
  180. &duplicate_proc_handle, // Duplicated handle.
  181. desired_access, // Access requested for the new handle.
  182. FALSE, // Don't inherit the new handle.
  183. 0) != 0;
  184. if (!res)
  185. return HRESULTFromLastError();
  186. proc_handle->Set(duplicate_proc_handle);
  187. return S_OK;
  188. }
  189. HRESULT ValidateCRXArgs(const std::wstring& browser_appid,
  190. const std::wstring& browser_version,
  191. const std::wstring& session_id) {
  192. if (!browser_appid.empty()) {
  193. GUID guid = {};
  194. HRESULT hr = ::IIDFromString(browser_appid.c_str(), &guid);
  195. if (FAILED(hr))
  196. return hr;
  197. }
  198. const base::Version version(base::WideToASCII(browser_version));
  199. if (!version.IsValid())
  200. return E_INVALIDARG;
  201. GUID session_guid = {};
  202. return ::IIDFromString(session_id.c_str(), &session_guid);
  203. }
  204. // Deletes all the files and subdirectories within |directory_path|. Errors are
  205. // ignored.
  206. void DeleteDirectoryFiles(const base::FilePath& directory_path) {
  207. base::FileEnumerator file_enum(
  208. directory_path, false,
  209. base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
  210. for (base::FilePath current = file_enum.Next(); !current.empty();
  211. current = file_enum.Next()) {
  212. base::DeletePathRecursively(current);
  213. }
  214. }
  215. // Schedules deletion after reboot of |dir_name| as well as all the files and
  216. // subdirectories within |dir_name|. Errors are ignored.
  217. void ScheduleDirectoryForDeletion(const base::FilePath& dir_name) {
  218. // First schedule all the files within |dir_name| for deletion.
  219. base::FileEnumerator file_enum(dir_name, false, base::FileEnumerator::FILES);
  220. for (base::FilePath file = file_enum.Next(); !file.empty();
  221. file = file_enum.Next()) {
  222. base::DeleteFileAfterReboot(file);
  223. }
  224. // Then recurse to all the subdirectories.
  225. base::FileEnumerator dir_enum(dir_name, false,
  226. base::FileEnumerator::DIRECTORIES);
  227. for (base::FilePath sub_dir = dir_enum.Next(); !sub_dir.empty();
  228. sub_dir = dir_enum.Next()) {
  229. ScheduleDirectoryForDeletion(sub_dir);
  230. }
  231. // Now schedule the empty directory itself.
  232. base::DeleteFileAfterReboot(dir_name);
  233. }
  234. // Returns the ChromeRecovery directory under Google\Chrome. For machine
  235. // installs, this directory is under %ProgramFiles%, which is writeable only by
  236. // adminstrators. We use this secure directory to validate and unpack the CRX to
  237. // prevent tampering.
  238. HRESULT GetChromeRecoveryDirectory(base::FilePath* dir) {
  239. base::FilePath recovery_dir;
  240. if (!base::PathService::Get(base::DIR_EXE, &recovery_dir))
  241. return HRESULTFromLastError();
  242. recovery_dir = recovery_dir.DirName().DirName().Append(kRecoveryDirectory);
  243. *dir = std::move(recovery_dir);
  244. return S_OK;
  245. }
  246. } // namespace
  247. HRESULT CleanupChromeRecoveryDirectory() {
  248. base::FilePath recovery_dir;
  249. HRESULT hr = GetChromeRecoveryDirectory(&recovery_dir);
  250. if (FAILED(hr))
  251. return hr;
  252. DeleteDirectoryFiles(recovery_dir);
  253. return S_OK;
  254. }
  255. HRESULT RunChromeRecoveryCRX(const base::FilePath& crx_path,
  256. const std::wstring& browser_appid,
  257. const std::wstring& browser_version,
  258. const std::wstring& session_id,
  259. uint32_t caller_proc_id,
  260. base::win::ScopedHandle* proc_handle) {
  261. if (crx_path.empty() || !caller_proc_id || !proc_handle)
  262. return E_INVALIDARG;
  263. HRESULT hr = ValidateCRXArgs(browser_appid, browser_version, session_id);
  264. if (FAILED(hr))
  265. return hr;
  266. base::CommandLine args(base::CommandLine::NO_PROGRAM);
  267. if (!browser_appid.empty())
  268. args.AppendSwitchNative("appguid", browser_appid);
  269. args.AppendSwitchNative("browser-version", browser_version);
  270. args.AppendSwitchNative("sessionid", session_id);
  271. args.AppendSwitch("system");
  272. base::FilePath unpack_dir;
  273. hr = GetChromeRecoveryDirectory(&unpack_dir);
  274. if (FAILED(hr))
  275. return hr;
  276. return RunCRX(crx_path, args,
  277. crx_file::VerifierFormat::CRX3_WITH_PUBLISHER_PROOF,
  278. GetRecoveryCRXHash(), unpack_dir,
  279. base::FilePath(kRecoveryExeName), caller_proc_id, proc_handle);
  280. }
  281. HRESULT RunCRX(const base::FilePath& crx_path,
  282. const base::CommandLine& args,
  283. const crx_file::VerifierFormat& crx_format,
  284. const std::vector<uint8_t>& crx_hash,
  285. const base::FilePath& unpack_under_path,
  286. const base::FilePath& exe_filename,
  287. uint32_t caller_proc_id,
  288. base::win::ScopedHandle* proc_handle) {
  289. DCHECK(!crx_path.empty());
  290. DCHECK(!crx_hash.empty());
  291. DCHECK(!unpack_under_path.empty());
  292. DCHECK(!exe_filename.empty());
  293. DCHECK(caller_proc_id);
  294. DCHECK(proc_handle);
  295. base::Process calling_process;
  296. HRESULT hr = OpenCallingProcess(caller_proc_id, &calling_process);
  297. if (FAILED(hr))
  298. return hr;
  299. base::ScopedTempDir unpacked_crx_dir;
  300. hr = ValidateAndUnpackCRX(crx_path, crx_format, crx_hash, unpack_under_path,
  301. &unpacked_crx_dir);
  302. if (FAILED(hr))
  303. return hr;
  304. const base::FilePath path_and_name =
  305. unpacked_crx_dir.GetPath().Append(exe_filename);
  306. base::CommandLine command_line(path_and_name);
  307. command_line.AppendArguments(args, false);
  308. base::win::ScopedHandle scoped_proc_handle;
  309. hr = LaunchCmd(command_line, calling_process, &scoped_proc_handle);
  310. if (FAILED(hr))
  311. return hr;
  312. ScheduleDirectoryForDeletion(unpacked_crx_dir.Take());
  313. *proc_handle = std::move(scoped_proc_handle);
  314. return hr;
  315. }
  316. } // namespace elevation_service