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 5 // For information about interceptions as a whole see 6 // http://dev.chromium.org/developers/design-documents/sandbox . 7 8 #include <set> 9 10 #include "sandbox/win/src/interception.h" 11 12 #include "base/logging.h" 13 #include "base/memory/scoped_ptr.h" 14 #include "base/win/pe_image.h" 15 #include "base/win/windows_version.h" 16 #include "sandbox/win/src/interception_internal.h" 17 #include "sandbox/win/src/interceptors.h" 18 #include "sandbox/win/src/sandbox.h" 19 #include "sandbox/win/src/sandbox_utils.h" 20 #include "sandbox/win/src/service_resolver.h" 21 #include "sandbox/win/src/target_interceptions.h" 22 #include "sandbox/win/src/target_process.h" 23 #include "sandbox/win/src/wow64.h" 24 25 namespace { 26 27 const char kMapViewOfSectionName[] = "NtMapViewOfSection"; 28 const char kUnmapViewOfSectionName[] = "NtUnmapViewOfSection"; 29 30 // Standard allocation granularity and page size for Windows. 31 const size_t kAllocGranularity = 65536; 32 const size_t kPageSize = 4096; 33 34 // Find a random offset within 64k and aligned to ceil(log2(size)). 35 size_t GetGranularAlignedRandomOffset(size_t size) { 36 CHECK_LE(size, kAllocGranularity); 37 unsigned int offset; 38 39 do { 40 rand_s(&offset); 41 offset &= (kAllocGranularity - 1); 42 } while (offset > (kAllocGranularity - size)); 43 44 // Find an alignment between 64 and the page size (4096). 45 size_t align_size = kPageSize; 46 for (size_t new_size = align_size / 2; new_size >= size; new_size /= 2) { 47 align_size = new_size; 48 } 49 return offset & ~(align_size - 1); 50 } 51 52 } // namespace 53 54 namespace sandbox { 55 56 SANDBOX_INTERCEPT SharedMemory* g_interceptions; 57 58 // Table of the unpatched functions that we intercept. Mapped from the parent. 59 SANDBOX_INTERCEPT OriginalFunctions g_originals = { NULL }; 60 61 // Magic constant that identifies that this function is not to be patched. 62 const char kUnloadDLLDummyFunction[] = "@"; 63 64 InterceptionManager::InterceptionManager(TargetProcess* child_process, 65 bool relaxed) 66 : child_(child_process), names_used_(false), relaxed_(relaxed) { 67 child_->AddRef(); 68 } 69 InterceptionManager::~InterceptionManager() { 70 child_->Release(); 71 } 72 73 bool InterceptionManager::AddToPatchedFunctions( 74 const wchar_t* dll_name, const char* function_name, 75 InterceptionType interception_type, const void* replacement_code_address, 76 InterceptorId id) { 77 InterceptionData function; 78 function.type = interception_type; 79 function.id = id; 80 function.dll = dll_name; 81 function.function = function_name; 82 function.interceptor_address = replacement_code_address; 83 84 interceptions_.push_back(function); 85 return true; 86 } 87 88 bool InterceptionManager::AddToPatchedFunctions( 89 const wchar_t* dll_name, const char* function_name, 90 InterceptionType interception_type, const char* replacement_function_name, 91 InterceptorId id) { 92 InterceptionData function; 93 function.type = interception_type; 94 function.id = id; 95 function.dll = dll_name; 96 function.function = function_name; 97 function.interceptor = replacement_function_name; 98 function.interceptor_address = NULL; 99 100 interceptions_.push_back(function); 101 names_used_ = true; 102 return true; 103 } 104 105 bool InterceptionManager::AddToUnloadModules(const wchar_t* dll_name) { 106 InterceptionData module_to_unload; 107 module_to_unload.type = INTERCEPTION_UNLOAD_MODULE; 108 module_to_unload.dll = dll_name; 109 // The next two are dummy values that make the structures regular, instead 110 // of having special cases. They should not be used. 111 module_to_unload.function = kUnloadDLLDummyFunction; 112 module_to_unload.interceptor_address = reinterpret_cast<void*>(1); 113 114 interceptions_.push_back(module_to_unload); 115 return true; 116 } 117 118 bool InterceptionManager::InitializeInterceptions() { 119 if (interceptions_.empty()) 120 return true; // Nothing to do here 121 122 size_t buffer_bytes = GetBufferSize(); 123 scoped_ptr<char[]> local_buffer(new char[buffer_bytes]); 124 125 if (!SetupConfigBuffer(local_buffer.get(), buffer_bytes)) 126 return false; 127 128 void* remote_buffer; 129 if (!CopyDataToChild(local_buffer.get(), buffer_bytes, &remote_buffer)) 130 return false; 131 132 bool hot_patch_needed = (0 != buffer_bytes); 133 if (!PatchNtdll(hot_patch_needed)) 134 return false; 135 136 g_interceptions = reinterpret_cast<SharedMemory*>(remote_buffer); 137 ResultCode rc = child_->TransferVariable("g_interceptions", 138 &g_interceptions, 139 sizeof(g_interceptions)); 140 return (SBOX_ALL_OK == rc); 141 } 142 143 size_t InterceptionManager::GetBufferSize() const { 144 std::set<std::wstring> dlls; 145 size_t buffer_bytes = 0; 146 147 std::list<InterceptionData>::const_iterator it = interceptions_.begin(); 148 for (; it != interceptions_.end(); ++it) { 149 // skip interceptions that are performed from the parent 150 if (!IsInterceptionPerformedByChild(*it)) 151 continue; 152 153 if (!dlls.count(it->dll)) { 154 // NULL terminate the dll name on the structure 155 size_t dll_name_bytes = (it->dll.size() + 1) * sizeof(wchar_t); 156 157 // include the dll related size 158 buffer_bytes += RoundUpToMultiple(offsetof(DllPatchInfo, dll_name) + 159 dll_name_bytes, sizeof(size_t)); 160 dlls.insert(it->dll); 161 } 162 163 // we have to NULL terminate the strings on the structure 164 size_t strings_chars = it->function.size() + it->interceptor.size() + 2; 165 166 // a new FunctionInfo is required per function 167 size_t record_bytes = offsetof(FunctionInfo, function) + strings_chars; 168 record_bytes = RoundUpToMultiple(record_bytes, sizeof(size_t)); 169 buffer_bytes += record_bytes; 170 } 171 172 if (0 != buffer_bytes) 173 // add the part of SharedMemory that we have not counted yet 174 buffer_bytes += offsetof(SharedMemory, dll_list); 175 176 return buffer_bytes; 177 } 178 179 // Basically, walk the list of interceptions moving them to the config buffer, 180 // but keeping together all interceptions that belong to the same dll. 181 // The config buffer is a local buffer, not the one allocated on the child. 182 bool InterceptionManager::SetupConfigBuffer(void* buffer, size_t buffer_bytes) { 183 if (0 == buffer_bytes) 184 return true; 185 186 DCHECK(buffer_bytes > sizeof(SharedMemory)); 187 188 SharedMemory* shared_memory = reinterpret_cast<SharedMemory*>(buffer); 189 DllPatchInfo* dll_info = shared_memory->dll_list; 190 int num_dlls = 0; 191 192 shared_memory->interceptor_base = names_used_ ? child_->MainModule() : NULL; 193 194 buffer_bytes -= offsetof(SharedMemory, dll_list); 195 buffer = dll_info; 196 197 std::list<InterceptionData>::iterator it = interceptions_.begin(); 198 for (; it != interceptions_.end();) { 199 // skip interceptions that are performed from the parent 200 if (!IsInterceptionPerformedByChild(*it)) { 201 ++it; 202 continue; 203 } 204 205 const std::wstring dll = it->dll; 206 if (!SetupDllInfo(*it, &buffer, &buffer_bytes)) 207 return false; 208 209 // walk the interceptions from this point, saving the ones that are 210 // performed on this dll, and removing the entry from the list. 211 // advance the iterator before removing the element from the list 212 std::list<InterceptionData>::iterator rest = it; 213 for (; rest != interceptions_.end();) { 214 if (rest->dll == dll) { 215 if (!SetupInterceptionInfo(*rest, &buffer, &buffer_bytes, dll_info)) 216 return false; 217 if (it == rest) 218 ++it; 219 rest = interceptions_.erase(rest); 220 } else { 221 ++rest; 222 } 223 } 224 dll_info = reinterpret_cast<DllPatchInfo*>(buffer); 225 ++num_dlls; 226 } 227 228 shared_memory->num_intercepted_dlls = num_dlls; 229 return true; 230 } 231 232 // Fills up just the part that depends on the dll, not the info that depends on 233 // the actual interception. 234 bool InterceptionManager::SetupDllInfo(const InterceptionData& data, 235 void** buffer, 236 size_t* buffer_bytes) const { 237 DCHECK(buffer_bytes); 238 DCHECK(buffer); 239 DCHECK(*buffer); 240 241 DllPatchInfo* dll_info = reinterpret_cast<DllPatchInfo*>(*buffer); 242 243 // the strings have to be zero terminated 244 size_t required = offsetof(DllPatchInfo, dll_name) + 245 (data.dll.size() + 1) * sizeof(wchar_t); 246 required = RoundUpToMultiple(required, sizeof(size_t)); 247 if (*buffer_bytes < required) 248 return false; 249 250 *buffer_bytes -= required; 251 *buffer = reinterpret_cast<char*>(*buffer) + required; 252 253 // set up the dll info to be what we know about it at this time 254 dll_info->unload_module = (data.type == INTERCEPTION_UNLOAD_MODULE); 255 dll_info->record_bytes = required; 256 dll_info->offset_to_functions = required; 257 dll_info->num_functions = 0; 258 data.dll._Copy_s(dll_info->dll_name, data.dll.size(), data.dll.size()); 259 dll_info->dll_name[data.dll.size()] = L'\0'; 260 261 return true; 262 } 263 264 bool InterceptionManager::SetupInterceptionInfo(const InterceptionData& data, 265 void** buffer, 266 size_t* buffer_bytes, 267 DllPatchInfo* dll_info) const { 268 DCHECK(buffer_bytes); 269 DCHECK(buffer); 270 DCHECK(*buffer); 271 272 if ((dll_info->unload_module) && 273 (data.function != kUnloadDLLDummyFunction)) { 274 // Can't specify a dll for both patch and unload. 275 NOTREACHED(); 276 } 277 278 FunctionInfo* function = reinterpret_cast<FunctionInfo*>(*buffer); 279 280 size_t name_bytes = data.function.size(); 281 size_t interceptor_bytes = data.interceptor.size(); 282 283 // the strings at the end of the structure are zero terminated 284 size_t required = offsetof(FunctionInfo, function) + 285 name_bytes + interceptor_bytes + 2; 286 required = RoundUpToMultiple(required, sizeof(size_t)); 287 if (*buffer_bytes < required) 288 return false; 289 290 // update the caller's values 291 *buffer_bytes -= required; 292 *buffer = reinterpret_cast<char*>(*buffer) + required; 293 294 function->record_bytes = required; 295 function->type = data.type; 296 function->id = data.id; 297 function->interceptor_address = data.interceptor_address; 298 char* names = function->function; 299 300 data.function._Copy_s(names, name_bytes, name_bytes); 301 names += name_bytes; 302 *names++ = '\0'; 303 304 // interceptor follows the function_name 305 data.interceptor._Copy_s(names, interceptor_bytes, interceptor_bytes); 306 names += interceptor_bytes; 307 *names++ = '\0'; 308 309 // update the dll table 310 dll_info->num_functions++; 311 dll_info->record_bytes += required; 312 313 return true; 314 } 315 316 bool InterceptionManager::CopyDataToChild(const void* local_buffer, 317 size_t buffer_bytes, 318 void** remote_buffer) const { 319 DCHECK(NULL != remote_buffer); 320 if (0 == buffer_bytes) { 321 *remote_buffer = NULL; 322 return true; 323 } 324 325 HANDLE child = child_->Process(); 326 327 // Allocate memory on the target process without specifying the address 328 void* remote_data = ::VirtualAllocEx(child, NULL, buffer_bytes, 329 MEM_COMMIT, PAGE_READWRITE); 330 if (NULL == remote_data) 331 return false; 332 333 SIZE_T bytes_written; 334 BOOL success = ::WriteProcessMemory(child, remote_data, local_buffer, 335 buffer_bytes, &bytes_written); 336 if (FALSE == success || bytes_written != buffer_bytes) { 337 ::VirtualFreeEx(child, remote_data, 0, MEM_RELEASE); 338 return false; 339 } 340 341 *remote_buffer = remote_data; 342 343 return true; 344 } 345 346 // Only return true if the child should be able to perform this interception. 347 bool InterceptionManager::IsInterceptionPerformedByChild( 348 const InterceptionData& data) const { 349 if (INTERCEPTION_INVALID == data.type) 350 return false; 351 352 if (INTERCEPTION_SERVICE_CALL == data.type) 353 return false; 354 355 if (data.type >= INTERCEPTION_LAST) 356 return false; 357 358 std::wstring ntdll(kNtdllName); 359 if (ntdll == data.dll) 360 return false; // ntdll has to be intercepted from the parent 361 362 return true; 363 } 364 365 bool InterceptionManager::PatchNtdll(bool hot_patch_needed) { 366 // Maybe there is nothing to do 367 if (!hot_patch_needed && interceptions_.empty()) 368 return true; 369 370 if (hot_patch_needed) { 371 #if SANDBOX_EXPORTS 372 // Make sure the functions are not excluded by the linker. 373 #if defined(_WIN64) 374 #pragma comment(linker, "/include:TargetNtMapViewOfSection64") 375 #pragma comment(linker, "/include:TargetNtUnmapViewOfSection64") 376 #else 377 #pragma comment(linker, "/include:_TargetNtMapViewOfSection@44") 378 #pragma comment(linker, "/include:_TargetNtUnmapViewOfSection@12") 379 #endif 380 #endif 381 ADD_NT_INTERCEPTION(NtMapViewOfSection, MAP_VIEW_OF_SECTION_ID, 44); 382 ADD_NT_INTERCEPTION(NtUnmapViewOfSection, UNMAP_VIEW_OF_SECTION_ID, 12); 383 } 384 385 // Reserve a full 64k memory range in the child process. 386 HANDLE child = child_->Process(); 387 BYTE* thunk_base = reinterpret_cast<BYTE*>( 388 ::VirtualAllocEx(child, NULL, kAllocGranularity, 389 MEM_RESERVE, PAGE_NOACCESS)); 390 391 // Find an aligned, random location within the reserved range. 392 size_t thunk_bytes = interceptions_.size() * sizeof(ThunkData) + 393 sizeof(DllInterceptionData); 394 size_t thunk_offset = GetGranularAlignedRandomOffset(thunk_bytes); 395 396 // Split the base and offset along page boundaries. 397 thunk_base += thunk_offset & ~(kPageSize - 1); 398 thunk_offset &= kPageSize - 1; 399 400 // Make an aligned, padded allocation, and move the pointer to our chunk. 401 size_t thunk_bytes_padded = (thunk_bytes + kPageSize - 1) & kPageSize; 402 thunk_base = reinterpret_cast<BYTE*>( 403 ::VirtualAllocEx(child, thunk_base, thunk_bytes_padded, 404 MEM_COMMIT, PAGE_EXECUTE_READWRITE)); 405 CHECK(thunk_base); // If this fails we'd crash anyway on an invalid access. 406 DllInterceptionData* thunks = reinterpret_cast<DllInterceptionData*>( 407 thunk_base + thunk_offset); 408 409 DllInterceptionData dll_data; 410 dll_data.data_bytes = thunk_bytes; 411 dll_data.num_thunks = 0; 412 dll_data.used_bytes = offsetof(DllInterceptionData, thunks); 413 414 // Reset all helpers for a new child. 415 memset(g_originals, 0, sizeof(g_originals)); 416 417 // this should write all the individual thunks to the child's memory 418 if (!PatchClientFunctions(thunks, thunk_bytes, &dll_data)) 419 return false; 420 421 // and now write the first part of the table to the child's memory 422 SIZE_T written; 423 bool ok = FALSE != ::WriteProcessMemory(child, thunks, &dll_data, 424 offsetof(DllInterceptionData, thunks), 425 &written); 426 427 if (!ok || (offsetof(DllInterceptionData, thunks) != written)) 428 return false; 429 430 // Attempt to protect all the thunks, but ignore failure 431 DWORD old_protection; 432 ::VirtualProtectEx(child, thunks, thunk_bytes, 433 PAGE_EXECUTE_READ, &old_protection); 434 435 ResultCode ret = child_->TransferVariable("g_originals", g_originals, 436 sizeof(g_originals)); 437 return (SBOX_ALL_OK == ret); 438 } 439 440 bool InterceptionManager::PatchClientFunctions(DllInterceptionData* thunks, 441 size_t thunk_bytes, 442 DllInterceptionData* dll_data) { 443 DCHECK(NULL != thunks); 444 DCHECK(NULL != dll_data); 445 446 HMODULE ntdll_base = ::GetModuleHandle(kNtdllName); 447 if (!ntdll_base) 448 return false; 449 450 base::win::PEImage ntdll_image(ntdll_base); 451 452 // Bypass purify's interception. 453 wchar_t* loader_get = reinterpret_cast<wchar_t*>( 454 ntdll_image.GetProcAddress("LdrGetDllHandle")); 455 if (loader_get) { 456 if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | 457 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, 458 loader_get, &ntdll_base)) 459 return false; 460 } 461 462 if (base::win::GetVersion() <= base::win::VERSION_VISTA) { 463 Wow64 WowHelper(child_, ntdll_base); 464 if (!WowHelper.WaitForNtdll()) 465 return false; 466 } 467 468 char* interceptor_base = NULL; 469 470 #if SANDBOX_EXPORTS 471 interceptor_base = reinterpret_cast<char*>(child_->MainModule()); 472 HMODULE local_interceptor = ::LoadLibrary(child_->Name()); 473 #endif 474 475 ServiceResolverThunk* thunk; 476 #if defined(_WIN64) 477 thunk = new ServiceResolverThunk(child_->Process(), relaxed_); 478 #else 479 base::win::OSInfo* os_info = base::win::OSInfo::GetInstance(); 480 if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) { 481 if (os_info->version() >= base::win::VERSION_WIN8) 482 thunk = new Wow64W8ResolverThunk(child_->Process(), relaxed_); 483 else 484 thunk = new Wow64ResolverThunk(child_->Process(), relaxed_); 485 } else if (!IsXPSP2OrLater()) { 486 thunk = new Win2kResolverThunk(child_->Process(), relaxed_); 487 } else if (os_info->version() >= base::win::VERSION_WIN8) { 488 thunk = new Win8ResolverThunk(child_->Process(), relaxed_); 489 } else { 490 thunk = new ServiceResolverThunk(child_->Process(), relaxed_); 491 } 492 #endif 493 494 std::list<InterceptionData>::iterator it = interceptions_.begin(); 495 for (; it != interceptions_.end(); ++it) { 496 const std::wstring ntdll(kNtdllName); 497 if (it->dll != ntdll) 498 break; 499 500 if (INTERCEPTION_SERVICE_CALL != it->type) 501 break; 502 503 #if SANDBOX_EXPORTS 504 // We may be trying to patch by function name. 505 if (NULL == it->interceptor_address) { 506 const char* address; 507 NTSTATUS ret = thunk->ResolveInterceptor(local_interceptor, 508 it->interceptor.c_str(), 509 reinterpret_cast<const void**>( 510 &address)); 511 if (!NT_SUCCESS(ret)) 512 break; 513 514 // Translate the local address to an address on the child. 515 it->interceptor_address = interceptor_base + (address - 516 reinterpret_cast<char*>(local_interceptor)); 517 } 518 #endif 519 NTSTATUS ret = thunk->Setup(ntdll_base, 520 interceptor_base, 521 it->function.c_str(), 522 it->interceptor.c_str(), 523 it->interceptor_address, 524 &thunks->thunks[dll_data->num_thunks], 525 thunk_bytes - dll_data->used_bytes, 526 NULL); 527 if (!NT_SUCCESS(ret)) 528 break; 529 530 DCHECK(!g_originals[it->id]); 531 g_originals[it->id] = &thunks->thunks[dll_data->num_thunks]; 532 533 dll_data->num_thunks++; 534 dll_data->used_bytes += sizeof(ThunkData); 535 } 536 537 delete(thunk); 538 539 #if SANDBOX_EXPORTS 540 if (NULL != local_interceptor) 541 ::FreeLibrary(local_interceptor); 542 #endif 543 544 if (it != interceptions_.end()) 545 return false; 546 547 return true; 548 } 549 550 } // namespace sandbox 551