interception.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. // Copyright (c) 2012 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. // For information about interceptions as a whole see
  5. // http://dev.chromium.org/developers/design-documents/sandbox .
  6. #include "sandbox/win/src/interception.h"
  7. #include <stddef.h>
  8. #include <memory>
  9. #include <set>
  10. #include <string>
  11. #include "base/bits.h"
  12. #include "base/check_op.h"
  13. #include "base/notreached.h"
  14. #include "base/scoped_native_library.h"
  15. #include "base/win/pe_image.h"
  16. #include "base/win/windows_version.h"
  17. #include "sandbox/win/src/interception_internal.h"
  18. #include "sandbox/win/src/interceptors.h"
  19. #include "sandbox/win/src/internal_types.h"
  20. #include "sandbox/win/src/sandbox.h"
  21. #include "sandbox/win/src/sandbox_rand.h"
  22. #include "sandbox/win/src/service_resolver.h"
  23. #include "sandbox/win/src/target_interceptions.h"
  24. #include "sandbox/win/src/target_process.h"
  25. #include "sandbox/win/src/win_utils.h"
  26. namespace sandbox {
  27. namespace {
  28. // Standard allocation granularity and page size for Windows.
  29. const size_t kAllocGranularity = 65536;
  30. const size_t kPageSize = 4096;
  31. // Rounds up the size of a given buffer, considering alignment (padding).
  32. // value is the current size of the buffer, and alignment is specified in
  33. // bytes.
  34. inline size_t RoundUpToMultiple(size_t value, size_t alignment) {
  35. return ((value + alignment - 1) / alignment) * alignment;
  36. }
  37. } // namespace
  38. namespace internal {
  39. // Find a random offset within 64k and aligned to ceil(log2(size)).
  40. size_t GetGranularAlignedRandomOffset(size_t size) {
  41. CHECK_LE(size, kAllocGranularity);
  42. unsigned int offset;
  43. do {
  44. GetRandom(&offset);
  45. offset &= (kAllocGranularity - 1);
  46. } while (offset > (kAllocGranularity - size));
  47. // Find an alignment between 64 and the page size (4096).
  48. size_t align_size = kPageSize;
  49. for (size_t new_size = align_size / 2; new_size >= size; new_size /= 2) {
  50. align_size = new_size;
  51. }
  52. return offset & ~(align_size - 1);
  53. }
  54. } // namespace internal
  55. SANDBOX_INTERCEPT SharedMemory* g_interceptions;
  56. // Table of the unpatched functions that we intercept. Mapped from the parent.
  57. SANDBOX_INTERCEPT OriginalFunctions g_originals = {nullptr};
  58. // Magic constant that identifies that this function is not to be patched.
  59. const char kUnloadDLLDummyFunction[] = "@";
  60. InterceptionManager::InterceptionData::InterceptionData() {}
  61. InterceptionManager::InterceptionData::InterceptionData(
  62. const InterceptionData& other) = default;
  63. InterceptionManager::InterceptionData::~InterceptionData() {}
  64. InterceptionManager::InterceptionManager(TargetProcess& child_process,
  65. bool relaxed)
  66. : child_(child_process), names_used_(false), relaxed_(relaxed) {}
  67. InterceptionManager::~InterceptionManager() {}
  68. bool InterceptionManager::AddToPatchedFunctions(
  69. const wchar_t* dll_name,
  70. const char* function_name,
  71. InterceptionType interception_type,
  72. const void* replacement_code_address,
  73. InterceptorId id) {
  74. InterceptionData function;
  75. function.type = interception_type;
  76. function.id = id;
  77. function.dll = dll_name;
  78. function.function = function_name;
  79. function.interceptor_address = replacement_code_address;
  80. interceptions_.push_back(function);
  81. return true;
  82. }
  83. bool InterceptionManager::AddToPatchedFunctions(
  84. const wchar_t* dll_name,
  85. const char* function_name,
  86. InterceptionType interception_type,
  87. const char* replacement_function_name,
  88. InterceptorId id) {
  89. InterceptionData function;
  90. function.type = interception_type;
  91. function.id = id;
  92. function.dll = dll_name;
  93. function.function = function_name;
  94. function.interceptor = replacement_function_name;
  95. function.interceptor_address = nullptr;
  96. interceptions_.push_back(function);
  97. names_used_ = true;
  98. return true;
  99. }
  100. bool InterceptionManager::AddToUnloadModules(const wchar_t* dll_name) {
  101. InterceptionData module_to_unload;
  102. module_to_unload.type = INTERCEPTION_UNLOAD_MODULE;
  103. module_to_unload.dll = dll_name;
  104. // The next two are dummy values that make the structures regular, instead
  105. // of having special cases. They should not be used.
  106. module_to_unload.function = kUnloadDLLDummyFunction;
  107. module_to_unload.interceptor_address = reinterpret_cast<void*>(1);
  108. interceptions_.push_back(module_to_unload);
  109. return true;
  110. }
  111. ResultCode InterceptionManager::InitializeInterceptions() {
  112. if (interceptions_.empty())
  113. return SBOX_ALL_OK; // Nothing to do here
  114. size_t buffer_bytes = GetBufferSize();
  115. std::unique_ptr<char[]> local_buffer(new char[buffer_bytes]);
  116. if (!SetupConfigBuffer(local_buffer.get(), buffer_bytes))
  117. return SBOX_ERROR_CANNOT_SETUP_INTERCEPTION_CONFIG_BUFFER;
  118. void* remote_buffer;
  119. if (!CopyToChildMemory(child_.Process(), local_buffer.get(), buffer_bytes,
  120. &remote_buffer))
  121. return SBOX_ERROR_CANNOT_COPY_DATA_TO_CHILD;
  122. bool hot_patch_needed = (0 != buffer_bytes);
  123. ResultCode rc = PatchNtdll(hot_patch_needed);
  124. if (rc != SBOX_ALL_OK)
  125. return rc;
  126. g_interceptions = reinterpret_cast<SharedMemory*>(remote_buffer);
  127. rc = child_.TransferVariable("g_interceptions", &g_interceptions,
  128. sizeof(g_interceptions));
  129. return rc;
  130. }
  131. size_t InterceptionManager::GetBufferSize() const {
  132. std::set<std::wstring> dlls;
  133. size_t buffer_bytes = 0;
  134. for (const auto& interception : interceptions_) {
  135. // skip interceptions that are performed from the parent
  136. if (!IsInterceptionPerformedByChild(interception))
  137. continue;
  138. if (!dlls.count(interception.dll)) {
  139. // NULL terminate the dll name on the structure
  140. size_t dll_name_bytes = (interception.dll.size() + 1) * sizeof(wchar_t);
  141. // include the dll related size
  142. buffer_bytes += RoundUpToMultiple(
  143. offsetof(DllPatchInfo, dll_name) + dll_name_bytes, sizeof(size_t));
  144. dlls.insert(interception.dll);
  145. }
  146. // we have to NULL terminate the strings on the structure
  147. size_t strings_chars =
  148. interception.function.size() + interception.interceptor.size() + 2;
  149. // a new FunctionInfo is required per function
  150. size_t record_bytes = offsetof(FunctionInfo, function) + strings_chars;
  151. record_bytes = RoundUpToMultiple(record_bytes, sizeof(size_t));
  152. buffer_bytes += record_bytes;
  153. }
  154. if (0 != buffer_bytes)
  155. // add the part of SharedMemory that we have not counted yet
  156. buffer_bytes += offsetof(SharedMemory, dll_list);
  157. return buffer_bytes;
  158. }
  159. // Basically, walk the list of interceptions moving them to the config buffer,
  160. // but keeping together all interceptions that belong to the same dll.
  161. // The config buffer is a local buffer, not the one allocated on the child.
  162. bool InterceptionManager::SetupConfigBuffer(void* buffer, size_t buffer_bytes) {
  163. if (0 == buffer_bytes)
  164. return true;
  165. DCHECK(buffer_bytes > sizeof(SharedMemory));
  166. SharedMemory* shared_memory = reinterpret_cast<SharedMemory*>(buffer);
  167. DllPatchInfo* dll_info = shared_memory->dll_list;
  168. int num_dlls = 0;
  169. shared_memory->interceptor_base = names_used_ ? child_.MainModule() : nullptr;
  170. buffer_bytes -= offsetof(SharedMemory, dll_list);
  171. buffer = dll_info;
  172. std::list<InterceptionData>::iterator it = interceptions_.begin();
  173. for (; it != interceptions_.end();) {
  174. // skip interceptions that are performed from the parent
  175. if (!IsInterceptionPerformedByChild(*it)) {
  176. ++it;
  177. continue;
  178. }
  179. const std::wstring dll = it->dll;
  180. if (!SetupDllInfo(*it, &buffer, &buffer_bytes))
  181. return false;
  182. // walk the interceptions from this point, saving the ones that are
  183. // performed on this dll, and removing the entry from the list.
  184. // advance the iterator before removing the element from the list
  185. std::list<InterceptionData>::iterator rest = it;
  186. for (; rest != interceptions_.end();) {
  187. if (rest->dll == dll) {
  188. if (!SetupInterceptionInfo(*rest, &buffer, &buffer_bytes, dll_info))
  189. return false;
  190. if (it == rest)
  191. ++it;
  192. rest = interceptions_.erase(rest);
  193. } else {
  194. ++rest;
  195. }
  196. }
  197. dll_info = reinterpret_cast<DllPatchInfo*>(buffer);
  198. ++num_dlls;
  199. }
  200. shared_memory->num_intercepted_dlls = num_dlls;
  201. return true;
  202. }
  203. // Fills up just the part that depends on the dll, not the info that depends on
  204. // the actual interception.
  205. bool InterceptionManager::SetupDllInfo(const InterceptionData& data,
  206. void** buffer,
  207. size_t* buffer_bytes) const {
  208. DCHECK(buffer_bytes);
  209. DCHECK(buffer);
  210. DCHECK(*buffer);
  211. DllPatchInfo* dll_info = reinterpret_cast<DllPatchInfo*>(*buffer);
  212. // the strings have to be zero terminated
  213. size_t required = offsetof(DllPatchInfo, dll_name) +
  214. (data.dll.size() + 1) * sizeof(wchar_t);
  215. required = RoundUpToMultiple(required, sizeof(size_t));
  216. if (*buffer_bytes < required)
  217. return false;
  218. *buffer_bytes -= required;
  219. *buffer = reinterpret_cast<char*>(*buffer) + required;
  220. // set up the dll info to be what we know about it at this time
  221. dll_info->unload_module = (data.type == INTERCEPTION_UNLOAD_MODULE);
  222. dll_info->record_bytes = required;
  223. dll_info->offset_to_functions = required;
  224. dll_info->num_functions = 0;
  225. data.dll.copy(dll_info->dll_name, data.dll.size());
  226. dll_info->dll_name[data.dll.size()] = L'\0';
  227. return true;
  228. }
  229. bool InterceptionManager::SetupInterceptionInfo(const InterceptionData& data,
  230. void** buffer,
  231. size_t* buffer_bytes,
  232. DllPatchInfo* dll_info) const {
  233. DCHECK(buffer_bytes);
  234. DCHECK(buffer);
  235. DCHECK(*buffer);
  236. if ((dll_info->unload_module) && (data.function != kUnloadDLLDummyFunction)) {
  237. // Can't specify a dll for both patch and unload.
  238. NOTREACHED();
  239. }
  240. FunctionInfo* function = reinterpret_cast<FunctionInfo*>(*buffer);
  241. size_t name_bytes = data.function.size();
  242. size_t interceptor_bytes = data.interceptor.size();
  243. // the strings at the end of the structure are zero terminated
  244. size_t required =
  245. offsetof(FunctionInfo, function) + name_bytes + interceptor_bytes + 2;
  246. required = RoundUpToMultiple(required, sizeof(size_t));
  247. if (*buffer_bytes < required)
  248. return false;
  249. // update the caller's values
  250. *buffer_bytes -= required;
  251. *buffer = reinterpret_cast<char*>(*buffer) + required;
  252. function->record_bytes = required;
  253. function->type = data.type;
  254. function->id = data.id;
  255. function->interceptor_address = data.interceptor_address;
  256. char* names = function->function;
  257. data.function.copy(names, name_bytes);
  258. names += name_bytes;
  259. *names++ = '\0';
  260. // interceptor follows the function_name
  261. data.interceptor.copy(names, interceptor_bytes);
  262. names += interceptor_bytes;
  263. *names++ = '\0';
  264. // update the dll table
  265. dll_info->num_functions++;
  266. dll_info->record_bytes += required;
  267. return true;
  268. }
  269. // Only return true if the child should be able to perform this interception.
  270. bool InterceptionManager::IsInterceptionPerformedByChild(
  271. const InterceptionData& data) const {
  272. if (INTERCEPTION_INVALID == data.type)
  273. return false;
  274. if (INTERCEPTION_SERVICE_CALL == data.type)
  275. return false;
  276. if (data.type >= INTERCEPTION_LAST)
  277. return false;
  278. std::wstring ntdll(kNtdllName);
  279. if (ntdll == data.dll)
  280. return false; // ntdll has to be intercepted from the parent
  281. return true;
  282. }
  283. ResultCode InterceptionManager::PatchNtdll(bool hot_patch_needed) {
  284. // Maybe there is nothing to do
  285. if (!hot_patch_needed && interceptions_.empty())
  286. return SBOX_ALL_OK;
  287. if (hot_patch_needed) {
  288. ADD_NT_INTERCEPTION(NtMapViewOfSection, MAP_VIEW_OF_SECTION_ID, 44);
  289. ADD_NT_INTERCEPTION(NtUnmapViewOfSection, UNMAP_VIEW_OF_SECTION_ID, 12);
  290. }
  291. // Reserve a full 64k memory range in the child process.
  292. HANDLE child = child_.Process();
  293. BYTE* thunk_base = reinterpret_cast<BYTE*>(::VirtualAllocEx(
  294. child, nullptr, kAllocGranularity, MEM_RESERVE, PAGE_NOACCESS));
  295. // Find an aligned, random location within the reserved range.
  296. size_t thunk_bytes =
  297. interceptions_.size() * sizeof(ThunkData) + sizeof(DllInterceptionData);
  298. size_t thunk_offset = internal::GetGranularAlignedRandomOffset(thunk_bytes);
  299. // Split the base and offset along page boundaries.
  300. thunk_base += thunk_offset & ~(kPageSize - 1);
  301. thunk_offset &= kPageSize - 1;
  302. // Make an aligned, padded allocation, and move the pointer to our chunk.
  303. size_t thunk_bytes_padded = base::bits::AlignUp(thunk_bytes, kPageSize);
  304. thunk_base = reinterpret_cast<BYTE*>(
  305. ::VirtualAllocEx(child, thunk_base, thunk_bytes_padded, MEM_COMMIT,
  306. PAGE_EXECUTE_READWRITE));
  307. CHECK(thunk_base); // If this fails we'd crash anyway on an invalid access.
  308. DllInterceptionData* thunks =
  309. reinterpret_cast<DllInterceptionData*>(thunk_base + thunk_offset);
  310. DllInterceptionData dll_data;
  311. dll_data.data_bytes = thunk_bytes;
  312. dll_data.num_thunks = 0;
  313. dll_data.used_bytes = offsetof(DllInterceptionData, thunks);
  314. // Reset all helpers for a new child.
  315. memset(g_originals, 0, sizeof(g_originals));
  316. // this should write all the individual thunks to the child's memory
  317. ResultCode rc = PatchClientFunctions(thunks, thunk_bytes, &dll_data);
  318. if (rc != SBOX_ALL_OK)
  319. return rc;
  320. // and now write the first part of the table to the child's memory
  321. SIZE_T written;
  322. bool ok =
  323. !!::WriteProcessMemory(child, thunks, &dll_data,
  324. offsetof(DllInterceptionData, thunks), &written);
  325. if (!ok || (offsetof(DllInterceptionData, thunks) != written))
  326. return SBOX_ERROR_CANNOT_WRITE_INTERCEPTION_THUNK;
  327. // Attempt to protect all the thunks, but ignore failure
  328. DWORD old_protection;
  329. ::VirtualProtectEx(child, thunks, thunk_bytes, PAGE_EXECUTE_READ,
  330. &old_protection);
  331. ResultCode ret =
  332. child_.TransferVariable("g_originals", g_originals, sizeof(g_originals));
  333. return ret;
  334. }
  335. ResultCode InterceptionManager::PatchClientFunctions(
  336. DllInterceptionData* thunks,
  337. size_t thunk_bytes,
  338. DllInterceptionData* dll_data) {
  339. DCHECK(thunks);
  340. DCHECK(dll_data);
  341. HMODULE ntdll_base = ::GetModuleHandle(kNtdllName);
  342. if (!ntdll_base)
  343. return SBOX_ERROR_NO_HANDLE;
  344. std::unique_ptr<ServiceResolverThunk> thunk;
  345. #if defined(_WIN64)
  346. thunk = std::make_unique<ServiceResolverThunk>(child_.Process(), relaxed_);
  347. #else
  348. base::win::OSInfo* os_info = base::win::OSInfo::GetInstance();
  349. base::win::Version real_os_version = os_info->Kernel32Version();
  350. if (os_info->IsWowX86OnAMD64()) {
  351. if (real_os_version >= base::win::Version::WIN10)
  352. thunk.reset(new Wow64W10ResolverThunk(child_.Process(), relaxed_));
  353. else if (real_os_version >= base::win::Version::WIN8)
  354. thunk.reset(new Wow64W8ResolverThunk(child_.Process(), relaxed_));
  355. else
  356. thunk.reset(new Wow64ResolverThunk(child_.Process(), relaxed_));
  357. } else if (real_os_version >= base::win::Version::WIN8) {
  358. thunk.reset(new Win8ResolverThunk(child_.Process(), relaxed_));
  359. } else {
  360. thunk.reset(new ServiceResolverThunk(child_.Process(), relaxed_));
  361. }
  362. #endif
  363. for (auto interception : interceptions_) {
  364. const std::wstring ntdll(kNtdllName);
  365. if (interception.dll != ntdll)
  366. return SBOX_ERROR_BAD_PARAMS;
  367. if (INTERCEPTION_SERVICE_CALL != interception.type)
  368. return SBOX_ERROR_BAD_PARAMS;
  369. NTSTATUS ret = thunk->Setup(
  370. ntdll_base, nullptr, interception.function.c_str(),
  371. interception.interceptor.c_str(), interception.interceptor_address,
  372. &thunks->thunks[dll_data->num_thunks],
  373. thunk_bytes - dll_data->used_bytes, nullptr);
  374. if (!NT_SUCCESS(ret)) {
  375. ::SetLastError(GetLastErrorFromNtStatus(ret));
  376. return SBOX_ERROR_CANNOT_SETUP_INTERCEPTION_THUNK;
  377. }
  378. DCHECK(!g_originals[interception.id]);
  379. g_originals[interception.id] = &thunks->thunks[dll_data->num_thunks];
  380. dll_data->num_thunks++;
  381. dll_data->used_bytes += sizeof(ThunkData);
  382. }
  383. return SBOX_ALL_OK;
  384. }
  385. } // namespace sandbox