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.
5 // For information about interceptions as a whole see
6 // http://dev.chromium.org/developers/design-documents/sandbox .
10 #include "sandbox/win/src/interception.h"
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"
27 const char kMapViewOfSectionName
[] = "NtMapViewOfSection";
28 const char kUnmapViewOfSectionName
[] = "NtUnmapViewOfSection";
30 // Standard allocation granularity and page size for Windows.
31 const size_t kAllocGranularity
= 65536;
32 const size_t kPageSize
= 4096;
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
);
41 offset
&= (kAllocGranularity
- 1);
42 } while (offset
> (kAllocGranularity
- size
));
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
;
49 return offset
& ~(align_size
- 1);
56 SANDBOX_INTERCEPT SharedMemory
* g_interceptions
;
58 // Table of the unpatched functions that we intercept. Mapped from the parent.
59 SANDBOX_INTERCEPT OriginalFunctions g_originals
= { NULL
};
61 // Magic constant that identifies that this function is not to be patched.
62 const char kUnloadDLLDummyFunction
[] = "@";
64 InterceptionManager::InterceptionManager(TargetProcess
* child_process
,
66 : child_(child_process
), names_used_(false), relaxed_(relaxed
) {
69 InterceptionManager::~InterceptionManager() {
73 bool InterceptionManager::AddToPatchedFunctions(
74 const wchar_t* dll_name
, const char* function_name
,
75 InterceptionType interception_type
, const void* replacement_code_address
,
77 InterceptionData function
;
78 function
.type
= interception_type
;
80 function
.dll
= dll_name
;
81 function
.function
= function_name
;
82 function
.interceptor_address
= replacement_code_address
;
84 interceptions_
.push_back(function
);
88 bool InterceptionManager::AddToPatchedFunctions(
89 const wchar_t* dll_name
, const char* function_name
,
90 InterceptionType interception_type
, const char* replacement_function_name
,
92 InterceptionData function
;
93 function
.type
= interception_type
;
95 function
.dll
= dll_name
;
96 function
.function
= function_name
;
97 function
.interceptor
= replacement_function_name
;
98 function
.interceptor_address
= NULL
;
100 interceptions_
.push_back(function
);
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);
114 interceptions_
.push_back(module_to_unload
);
118 bool InterceptionManager::InitializeInterceptions() {
119 if (interceptions_
.empty())
120 return true; // Nothing to do here
122 size_t buffer_bytes
= GetBufferSize();
123 scoped_ptr
<char[]> local_buffer(new char[buffer_bytes
]);
125 if (!SetupConfigBuffer(local_buffer
.get(), buffer_bytes
))
129 if (!CopyDataToChild(local_buffer
.get(), buffer_bytes
, &remote_buffer
))
132 bool hot_patch_needed
= (0 != buffer_bytes
);
133 if (!PatchNtdll(hot_patch_needed
))
136 g_interceptions
= reinterpret_cast<SharedMemory
*>(remote_buffer
);
137 ResultCode rc
= child_
->TransferVariable("g_interceptions",
139 sizeof(g_interceptions
));
140 return (SBOX_ALL_OK
== rc
);
143 size_t InterceptionManager::GetBufferSize() const {
144 std::set
<std::wstring
> dlls
;
145 size_t buffer_bytes
= 0;
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
))
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);
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
);
163 // we have to NULL terminate the strings on the structure
164 size_t strings_chars
= it
->function
.size() + it
->interceptor
.size() + 2;
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
;
172 if (0 != buffer_bytes
)
173 // add the part of SharedMemory that we have not counted yet
174 buffer_bytes
+= offsetof(SharedMemory
, dll_list
);
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
)
186 DCHECK(buffer_bytes
> sizeof(SharedMemory
));
188 SharedMemory
* shared_memory
= reinterpret_cast<SharedMemory
*>(buffer
);
189 DllPatchInfo
* dll_info
= shared_memory
->dll_list
;
192 shared_memory
->interceptor_base
= names_used_
? child_
->MainModule() : NULL
;
194 buffer_bytes
-= offsetof(SharedMemory
, dll_list
);
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
)) {
205 const std::wstring dll
= it
->dll
;
206 if (!SetupDllInfo(*it
, &buffer
, &buffer_bytes
))
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
))
219 rest
= interceptions_
.erase(rest
);
224 dll_info
= reinterpret_cast<DllPatchInfo
*>(buffer
);
228 shared_memory
->num_intercepted_dlls
= num_dlls
;
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
,
236 size_t* buffer_bytes
) const {
237 DCHECK(buffer_bytes
);
241 DllPatchInfo
* dll_info
= reinterpret_cast<DllPatchInfo
*>(*buffer
);
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
)
250 *buffer_bytes
-= required
;
251 *buffer
= reinterpret_cast<char*>(*buffer
) + required
;
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';
264 bool InterceptionManager::SetupInterceptionInfo(const InterceptionData
& data
,
266 size_t* buffer_bytes
,
267 DllPatchInfo
* dll_info
) const {
268 DCHECK(buffer_bytes
);
272 if ((dll_info
->unload_module
) &&
273 (data
.function
!= kUnloadDLLDummyFunction
)) {
274 // Can't specify a dll for both patch and unload.
278 FunctionInfo
* function
= reinterpret_cast<FunctionInfo
*>(*buffer
);
280 size_t name_bytes
= data
.function
.size();
281 size_t interceptor_bytes
= data
.interceptor
.size();
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
)
290 // update the caller's values
291 *buffer_bytes
-= required
;
292 *buffer
= reinterpret_cast<char*>(*buffer
) + required
;
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
;
300 data
.function
._Copy_s(names
, name_bytes
, name_bytes
);
304 // interceptor follows the function_name
305 data
.interceptor
._Copy_s(names
, interceptor_bytes
, interceptor_bytes
);
306 names
+= interceptor_bytes
;
309 // update the dll table
310 dll_info
->num_functions
++;
311 dll_info
->record_bytes
+= required
;
316 bool InterceptionManager::CopyDataToChild(const void* local_buffer
,
318 void** remote_buffer
) const {
319 DCHECK(NULL
!= remote_buffer
);
320 if (0 == buffer_bytes
) {
321 *remote_buffer
= NULL
;
325 HANDLE child
= child_
->Process();
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
)
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
);
341 *remote_buffer
= remote_data
;
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
)
352 if (INTERCEPTION_SERVICE_CALL
== data
.type
)
355 if (data
.type
>= INTERCEPTION_LAST
)
358 std::wstring
ntdll(kNtdllName
);
359 if (ntdll
== data
.dll
)
360 return false; // ntdll has to be intercepted from the parent
365 bool InterceptionManager::PatchNtdll(bool hot_patch_needed
) {
366 // Maybe there is nothing to do
367 if (!hot_patch_needed
&& interceptions_
.empty())
370 if (hot_patch_needed
) {
372 // Make sure the functions are not excluded by the linker.
374 #pragma comment(linker, "/include:TargetNtMapViewOfSection64")
375 #pragma comment(linker, "/include:TargetNtUnmapViewOfSection64")
377 #pragma comment(linker, "/include:_TargetNtMapViewOfSection@44")
378 #pragma comment(linker, "/include:_TargetNtUnmapViewOfSection@12")
381 ADD_NT_INTERCEPTION(NtMapViewOfSection
, MAP_VIEW_OF_SECTION_ID
, 44);
382 ADD_NT_INTERCEPTION(NtUnmapViewOfSection
, UNMAP_VIEW_OF_SECTION_ID
, 12);
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
));
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
);
396 // Split the base and offset along page boundaries.
397 thunk_base
+= thunk_offset
& ~(kPageSize
- 1);
398 thunk_offset
&= kPageSize
- 1;
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
);
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
);
414 // Reset all helpers for a new child.
415 memset(g_originals
, 0, sizeof(g_originals
));
417 // this should write all the individual thunks to the child's memory
418 if (!PatchClientFunctions(thunks
, thunk_bytes
, &dll_data
))
421 // and now write the first part of the table to the child's memory
423 bool ok
= FALSE
!= ::WriteProcessMemory(child
, thunks
, &dll_data
,
424 offsetof(DllInterceptionData
, thunks
),
427 if (!ok
|| (offsetof(DllInterceptionData
, thunks
) != written
))
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
);
435 ResultCode ret
= child_
->TransferVariable("g_originals", g_originals
,
436 sizeof(g_originals
));
437 return (SBOX_ALL_OK
== ret
);
440 bool InterceptionManager::PatchClientFunctions(DllInterceptionData
* thunks
,
442 DllInterceptionData
* dll_data
) {
443 DCHECK(NULL
!= thunks
);
444 DCHECK(NULL
!= dll_data
);
446 HMODULE ntdll_base
= ::GetModuleHandle(kNtdllName
);
450 base::win::PEImage
ntdll_image(ntdll_base
);
452 // Bypass purify's interception.
453 wchar_t* loader_get
= reinterpret_cast<wchar_t*>(
454 ntdll_image
.GetProcAddress("LdrGetDllHandle"));
456 if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
|
457 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT
,
458 loader_get
, &ntdll_base
))
462 if (base::win::GetVersion() <= base::win::VERSION_VISTA
) {
463 Wow64
WowHelper(child_
, ntdll_base
);
464 if (!WowHelper
.WaitForNtdll())
468 char* interceptor_base
= NULL
;
471 interceptor_base
= reinterpret_cast<char*>(child_
->MainModule());
472 HMODULE local_interceptor
= ::LoadLibrary(child_
->Name());
475 ServiceResolverThunk
* thunk
;
477 thunk
= new ServiceResolverThunk(child_
->Process(), relaxed_
);
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_
);
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_
);
490 thunk
= new ServiceResolverThunk(child_
->Process(), relaxed_
);
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
)
500 if (INTERCEPTION_SERVICE_CALL
!= it
->type
)
504 // We may be trying to patch by function name.
505 if (NULL
== it
->interceptor_address
) {
507 NTSTATUS ret
= thunk
->ResolveInterceptor(local_interceptor
,
508 it
->interceptor
.c_str(),
509 reinterpret_cast<const void**>(
511 if (!NT_SUCCESS(ret
))
514 // Translate the local address to an address on the child.
515 it
->interceptor_address
= interceptor_base
+ (address
-
516 reinterpret_cast<char*>(local_interceptor
));
519 NTSTATUS ret
= thunk
->Setup(ntdll_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
,
527 if (!NT_SUCCESS(ret
))
530 DCHECK(!g_originals
[it
->id
]);
531 g_originals
[it
->id
] = &thunks
->thunks
[dll_data
->num_thunks
];
533 dll_data
->num_thunks
++;
534 dll_data
->used_bytes
+= sizeof(ThunkData
);
540 if (NULL
!= local_interceptor
)
541 ::FreeLibrary(local_interceptor
);
544 if (it
!= interceptions_
.end())
550 } // namespace sandbox