michael@0: // Copyright (c) 2011 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: // Defines InterceptionManager, the class in charge of setting up interceptions michael@0: // for the sandboxed process. For more details see michael@0: // http://dev.chromium.org/developers/design-documents/sandbox . michael@0: michael@0: #ifndef SANDBOX_SRC_INTERCEPTION_H_ michael@0: #define SANDBOX_SRC_INTERCEPTION_H_ michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "base/basictypes.h" michael@0: #include "base/gtest_prod_util.h" michael@0: #include "sandbox/win/src/sandbox_types.h" michael@0: michael@0: namespace sandbox { michael@0: michael@0: class TargetProcess; michael@0: enum InterceptorId; michael@0: michael@0: // Internal structures used for communication between the broker and the target. michael@0: struct DllPatchInfo; michael@0: struct DllInterceptionData; michael@0: michael@0: // The InterceptionManager executes on the parent application, and it is in michael@0: // charge of setting up the desired interceptions, and placing the Interception michael@0: // Agent into the child application. michael@0: // michael@0: // The exposed API consists of two methods: AddToPatchedFunctions to set up a michael@0: // particular interception, and InitializeInterceptions to actually go ahead and michael@0: // perform all interceptions and transfer data to the child application. michael@0: // michael@0: // The typical usage is something like this: michael@0: // michael@0: // InterceptionManager interception_manager(child); michael@0: // if (!interception_manager.AddToPatchedFunctions( michael@0: // L"ntdll.dll", "NtCreateFile", michael@0: // sandbox::INTERCEPTION_SERVICE_CALL, &MyNtCreateFile, MY_ID_1)) michael@0: // return false; michael@0: // michael@0: // if (!interception_manager.AddToPatchedFunctions( michael@0: // L"kernel32.dll", "CreateDirectoryW", michael@0: // sandbox::INTERCEPTION_EAT, L"MyCreateDirectoryW@12", MY_ID_2)) michael@0: // return false; michael@0: // michael@0: // if (!interception_manager.InitializeInterceptions()) { michael@0: // DWORD error = ::GetLastError(); michael@0: // return false; michael@0: // } michael@0: // michael@0: // Any required syncronization must be performed outside this class. Also, it is michael@0: // not possible to perform further interceptions after InitializeInterceptions michael@0: // is called. michael@0: // michael@0: class InterceptionManager { michael@0: // The unit test will access private members. michael@0: // Allow tests to be marked DISABLED_. Note that FLAKY_ and FAILS_ prefixes michael@0: // do not work with sandbox tests. michael@0: FRIEND_TEST_ALL_PREFIXES(InterceptionManagerTest, BufferLayout1); michael@0: FRIEND_TEST_ALL_PREFIXES(InterceptionManagerTest, BufferLayout2); michael@0: michael@0: public: michael@0: // An interception manager performs interceptions on a given child process. michael@0: // If we are allowed to intercept functions that have been patched by somebody michael@0: // else, relaxed should be set to true. michael@0: // Note: We increase the child's reference count internally. michael@0: InterceptionManager(TargetProcess* child_process, bool relaxed); michael@0: ~InterceptionManager(); michael@0: michael@0: // Patches function_name inside dll_name to point to replacement_code_address. michael@0: // function_name has to be an exported symbol of dll_name. michael@0: // Returns true on success. michael@0: // michael@0: // The new function should match the prototype and calling convention of the michael@0: // function to intercept except for one extra argument (the first one) that michael@0: // contains a pointer to the original function, to simplify the development michael@0: // of interceptors (for IA32). In x64, there is no extra argument to the michael@0: // interceptor, so the provided InterceptorId is used to keep a table of michael@0: // intercepted functions so that the interceptor can index that table to get michael@0: // the pointer that would have been the first argument (g_originals[id]). michael@0: // michael@0: // For example, to intercept NtClose, the following code could be used: michael@0: // michael@0: // typedef NTSTATUS (WINAPI *NtCloseFunction) (IN HANDLE Handle); michael@0: // NTSTATUS WINAPI MyNtCose(IN NtCloseFunction OriginalClose, michael@0: // IN HANDLE Handle) { michael@0: // // do something michael@0: // // call the original function michael@0: // return OriginalClose(Handle); michael@0: // } michael@0: // michael@0: // And in x64: michael@0: // michael@0: // typedef NTSTATUS (WINAPI *NtCloseFunction) (IN HANDLE Handle); michael@0: // NTSTATUS WINAPI MyNtCose64(IN HANDLE Handle) { michael@0: // // do something michael@0: // // call the original function michael@0: // NtCloseFunction OriginalClose = g_originals[NT_CLOSE_ID]; michael@0: // return OriginalClose(Handle); michael@0: // } michael@0: bool AddToPatchedFunctions(const wchar_t* dll_name, michael@0: const char* function_name, michael@0: InterceptionType interception_type, michael@0: const void* replacement_code_address, michael@0: InterceptorId id); michael@0: michael@0: // Patches function_name inside dll_name to point to michael@0: // replacement_function_name. michael@0: bool AddToPatchedFunctions(const wchar_t* dll_name, michael@0: const char* function_name, michael@0: InterceptionType interception_type, michael@0: const char* replacement_function_name, michael@0: InterceptorId id); michael@0: michael@0: // The interception agent will unload the dll with dll_name. michael@0: bool AddToUnloadModules(const wchar_t* dll_name); michael@0: michael@0: // Initializes all interceptions on the client. michael@0: // Returns true on success. michael@0: // michael@0: // The child process must be created suspended, and cannot be resumed until michael@0: // after this method returns. In addition, no action should be performed on michael@0: // the child that may cause it to resume momentarily, such as injecting michael@0: // threads or APCs. michael@0: // michael@0: // This function must be called only once, after all interceptions have been michael@0: // set up using AddToPatchedFunctions. michael@0: bool InitializeInterceptions(); michael@0: michael@0: private: michael@0: // Used to store the interception information until the actual set-up. michael@0: struct InterceptionData { michael@0: InterceptionType type; // Interception type. michael@0: InterceptorId id; // Interceptor id. michael@0: std::wstring dll; // Name of dll to intercept. michael@0: std::string function; // Name of function to intercept. michael@0: std::string interceptor; // Name of interceptor function. michael@0: const void* interceptor_address; // Interceptor's entry point. michael@0: }; michael@0: michael@0: // Calculates the size of the required configuration buffer. michael@0: size_t GetBufferSize() const; michael@0: michael@0: // Rounds up the size of a given buffer, considering alignment (padding). michael@0: // value is the current size of the buffer, and alignment is specified in michael@0: // bytes. michael@0: static inline size_t RoundUpToMultiple(size_t value, size_t alignment) { michael@0: return ((value + alignment -1) / alignment) * alignment; michael@0: } michael@0: michael@0: // Sets up a given buffer with all the information that has to be transfered michael@0: // to the child. michael@0: // Returns true on success. michael@0: // michael@0: // The buffer size should be at least the value returned by GetBufferSize michael@0: bool SetupConfigBuffer(void* buffer, size_t buffer_bytes); michael@0: michael@0: // Fills up the part of the transfer buffer that corresponds to information michael@0: // about one dll to patch. michael@0: // data is the first recorded interception for this dll. michael@0: // Returns true on success. michael@0: // michael@0: // On successful return, buffer will be advanced from it's current position michael@0: // to the point where the next block of configuration data should be written michael@0: // (the actual interception info), and the current size of the buffer will michael@0: // decrease to account the space used by this method. michael@0: bool SetupDllInfo(const InterceptionData& data, michael@0: void** buffer, size_t* buffer_bytes) const; michael@0: michael@0: // Fills up the part of the transfer buffer that corresponds to a single michael@0: // function to patch. michael@0: // dll_info points to the dll being updated with the interception stored on michael@0: // data. The buffer pointer and remaining size are updated by this call. michael@0: // Returns true on success. michael@0: bool SetupInterceptionInfo(const InterceptionData& data, void** buffer, michael@0: size_t* buffer_bytes, michael@0: DllPatchInfo* dll_info) const; michael@0: michael@0: // Returns true if this interception is to be performed by the child michael@0: // as opposed to from the parent. michael@0: bool IsInterceptionPerformedByChild(const InterceptionData& data) const; michael@0: michael@0: // Allocates a buffer on the child's address space (returned on michael@0: // remote_buffer), and fills it with the contents of a local buffer. michael@0: // Returns true on success. michael@0: bool CopyDataToChild(const void* local_buffer, size_t buffer_bytes, michael@0: void** remote_buffer) const; michael@0: michael@0: // Performs the cold patch (from the parent) of ntdll. michael@0: // Returns true on success. michael@0: // michael@0: // This method will insert additional interceptions to launch the interceptor michael@0: // agent on the child process, if there are additional interceptions to do. michael@0: bool PatchNtdll(bool hot_patch_needed); michael@0: michael@0: // Peforms the actual interceptions on ntdll. michael@0: // thunks is the memory to store all the thunks for this dll (on the child), michael@0: // and dll_data is a local buffer to hold global dll interception info. michael@0: // Returns true on success. michael@0: bool PatchClientFunctions(DllInterceptionData* thunks, michael@0: size_t thunk_bytes, michael@0: DllInterceptionData* dll_data); michael@0: michael@0: // The process to intercept. michael@0: TargetProcess* child_; michael@0: // Holds all interception info until the call to initialize (perform the michael@0: // actual patch). michael@0: std::list interceptions_; michael@0: michael@0: // Keep track of patches added by name. michael@0: bool names_used_; michael@0: michael@0: // true if we are allowed to patch already-patched functions. michael@0: bool relaxed_; michael@0: michael@0: DISALLOW_COPY_AND_ASSIGN(InterceptionManager); michael@0: }; michael@0: michael@0: // This macro simply calls interception_manager.AddToPatchedFunctions with michael@0: // the given service to intercept (INTERCEPTION_SERVICE_CALL), and assumes that michael@0: // the interceptor is called "TargetXXX", where XXX is the name of the service. michael@0: // Note that num_params is the number of bytes to pop out of the stack for michael@0: // the exported interceptor, following the calling convention of a service call michael@0: // (WINAPI = with the "C" underscore). michael@0: #if SANDBOX_EXPORTS michael@0: #if defined(_WIN64) michael@0: #define MAKE_SERVICE_NAME(service, params) "Target" # service "64" michael@0: #else michael@0: #define MAKE_SERVICE_NAME(service, params) "_Target" # service "@" # params michael@0: #endif michael@0: michael@0: #define ADD_NT_INTERCEPTION(service, id, num_params) \ michael@0: AddToPatchedFunctions(kNtdllName, #service, \ michael@0: sandbox::INTERCEPTION_SERVICE_CALL, \ michael@0: MAKE_SERVICE_NAME(service, num_params), id) michael@0: michael@0: #define INTERCEPT_NT(manager, service, id, num_params) \ michael@0: ((&Target##service) ? \ michael@0: manager->ADD_NT_INTERCEPTION(service, id, num_params) : false) michael@0: michael@0: #define INTERCEPT_EAT(manager, dll, function, id, num_params) \ michael@0: ((&Target##function) ? \ michael@0: manager->AddToPatchedFunctions(dll, #function, sandbox::INTERCEPTION_EAT, \ michael@0: MAKE_SERVICE_NAME(function, num_params), \ michael@0: id) : \ michael@0: false) michael@0: #else // SANDBOX_EXPORTS michael@0: #if defined(_WIN64) michael@0: #define MAKE_SERVICE_NAME(service) &Target##service##64 michael@0: #else michael@0: #define MAKE_SERVICE_NAME(service) &Target##service michael@0: #endif michael@0: michael@0: #define ADD_NT_INTERCEPTION(service, id, num_params) \ michael@0: AddToPatchedFunctions(kNtdllName, #service, \ michael@0: sandbox::INTERCEPTION_SERVICE_CALL, \ michael@0: MAKE_SERVICE_NAME(service), id) michael@0: michael@0: #define INTERCEPT_NT(manager, service, id, num_params) \ michael@0: manager->ADD_NT_INTERCEPTION(service, id, num_params) michael@0: michael@0: #define INTERCEPT_EAT(manager, dll, function, id, num_params) \ michael@0: manager->AddToPatchedFunctions(dll, #function, sandbox::INTERCEPTION_EAT, \ michael@0: MAKE_SERVICE_NAME(function), id) michael@0: #endif // SANDBOX_EXPORTS michael@0: michael@0: } // namespace sandbox michael@0: michael@0: #endif // SANDBOX_SRC_INTERCEPTION_H_