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