/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef dom_plugins_ipc_PluginHooksWin_h #define dom_plugins_ipc_PluginHooksWin_h 1 #include #include #include #include "base/task.h" #include "mozilla/ipc/ProcessChild.h" #include "FunctionBrokerChild.h" #include "transport/runnable_utils.h" #include "PluginMessageUtils.h" #include "mozilla/Logging.h" #include "FunctionHook.h" #include "FunctionBrokerIPCUtils.h" #if defined(XP_WIN) # define SECURITY_WIN32 # include # include # include # if defined(MOZ_SANDBOX) # include "sandboxPermissions.h" # endif #endif // defined(XP_WIN) /** * This functionality supports automatic method hooking (FunctionHook) and * brokering (FunctionBroker), which are used to intercept system calls * (using the nsDllInterceptor) and replace them with new functionality (hook) * or proxy them on another process (broker). * There isn't much of a public interface to this (see FunctionHook * for initialization functionality) since the majority of the behavior * comes from intercepting calls to DLL methods (making those DLL methods the * public interface). Generic RPC can be achieved without DLLs or function * interception by directly calling the FunctionBroker::InterceptorStub. * * The system supports the most common logic surrounding brokering by allowing * the client to supply strategies for them. Some examples of common tasks that * are supported by automatic brokering: * * * Intercepting a new Win32 method: * * Step 1: Add a typedef or subclass of either FunctionHook (non-brokering) or * FunctionBroker (automatic brokering) to FunctionBroker.cpp, using a new * FunctionHookID (added to that enum). * For example: * typedef FunctionBroker GetKeyStateFB * Use a subclass instead of a typedef if you need to maintain data or state. * * Step 2: Add an instance of that object to the FunctionHookList in * AddFunctionHook(FunctionHookList&) or * AddBrokeredFunctionHook(FunctionHookList&). * This typically just means calling the constructor with the correct info. * At a minimum, this means supplying the names of the DLL and method to * broker, and a pointer to the original version of the method. * For example: * aHooks[ID_GetKeyState] = * new GetKeyStateFB("user32.dll", "GetKeyState", &GetKeyState); * * Step 3: If brokering, make sure the system can (un)marshal the parameters, * either by the means below or by adding the type to IpdlTuple, which we use * for type-safely (un)marshaling the parameter list. * * * Only brokering _some_ calls to the method: * * FunctionBroker's constructor allows the user to supply a ShouldBroker * function, which takes the parameters of the method call and returns false * if we should use the original method instead of brokering. * * * Only passing _some_ parameters to the brokering process / returning * parameters to client: * * If a system call changes a parameter call-by-reference style then the * parameter's value needs to be returned to the client. The FunctionBroker * has "phase" (request/response) objects that it uses to determine which * parameters are sent/returned. This example tells InternetWriteFileFB to * return its third parameter: * template<> template<> * struct InternetWriteFileFB::Response::Info::ShouldMarshal<3> { * static const bool value = true; * }; * By default, all parameters have ShouldMarshal set in the request phase * and only the return value (parameter -1) has it set in the response phase. * * * Marshalling special parameter/return types: * * The IPCTypeMap in FunctionBroker maps a parameter or return type * to a type that IpdlTuple knows how to marshal. By default, the map is * the identity but some types need special handling. * The map is endpoint-specific (it is a member of the EndpointHandler), * so a different type can be used * for client -> server and for server -> client. Note that the * types must be able to Copy() from one another -- the default Copy() * implementation uses the type's assignment operator. * The EndpointHandler itself is a template parameter of the FunctionBroker. * The default EndpointHandler recognizes basic types. * See e.g. FileDlgEndpointHandler::IPCTypeMap * for an example of specialization. * * * Anything more complex involving parameter transmission: * * Sometimes marshaling parameters can require something more complex. In * those cases, you will need to specialize the Marshal and Unmarshal * methods of the request or response handler and perform your complex logic * there. A wise approach is to map your complex parameters into a simpler * parameter list and delegate the Marshal/Unmarshal calls to them. For * example, an API might take a void* and an int as a buffer and length. * Obviously a void* cannot generally be marshaled. However, we can delegate * this call to a parameter list that takes a string in place of the buffer and * length. Something like: * * typedef RequestHandler * HookedFuncDelegateReq; * * template<> * void HookedFuncFB::Request::Marshal(IpdlTuple& aTuple, const void*& aBuf, * const int& aBufLen) * { * MOZ_ASSERT(nWritten); * HookedFuncDelegateReq::Marshal(aTuple, * nsDependentCSubstring(aBuf, aBufLen)); * } * * template<> * bool HookedFuncFB::Request::Unmarshal(ServerCallData& aScd, const IpdlTuple& * aTuple, void*& aBuf, int& aBufLen) * { * nsDependentCSubstring str; * if (!HookedFuncDelegateReq::Unmarshal(aScd, aTuple, str)) { * return false; * } * * // Request phase unmarshal uses ServerCallData for dynamically-allocating * // memory. * aScd.AllocateString(str, aBuf, false); * aBufLen = str.Length(); * return true; * } * * See e.g. InternetWriteFileFB for a complete example of delegation. * * * Brokering but need the server to do more than just run the function: * * Specialize the FunctionBroker's RunFunction. By default, it just runs * the function. See GetSaveFileNameWFB for an example that does more. * */ #if defined(XP_WIN) && defined(__clang__) # if __has_declspec_attribute(guard) // Workaround for https://bugs.llvm.org/show_bug.cgi?id=47617 // Some of the brokered function thunks don't get properly marked as call // targets, so we have to disable CFG when returning to the original function. # define BROKER_DISABLE_CFGUARD __declspec(guard(nocf)) # else # define BROKER_DISABLE_CFGUARD /* nothing */ # endif #else # define BROKER_DISABLE_CFGUARD /* nothing */ #endif namespace mozilla { namespace plugins { #if defined(XP_WIN) // Currently, all methods we hook use the WINAPI calling convention. # define HOOK_CALL WINAPI typedef std::pair UlongPair; typedef std::map UlongPairToIdMap; extern UlongPairToIdMap sPairToIdMap; typedef std::map IdToUlongPairMap; extern IdToUlongPairMap sIdToPairMap; typedef std::map PtrToIdMap; extern PtrToIdMap sPtrToIdMap; typedef std::map IdToPtrMap; extern IdToPtrMap sIdToPtrMap; #else // defined(XP_WIN) // Any methods we hook use the default calling convention. # define HOOK_CALL #endif // defined(XP_WIN) inline bool IsOdd(uint64_t aVal) { return aVal & 1; } // This enum is used to track if this process is currently running the client // or server side of brokering. enum Endpoint { SERVER, CLIENT }; inline const char* EndpointMsg(Endpoint aVal) { return aVal == SERVER ? "SERVER" : "CLIENT"; } template inline void LogParameterValue(int aIndex, const ParamType& aParam) { // To avoid overhead, don't do this in release. #ifdef DEBUG if (!MOZ_LOG_TEST(sPluginHooksLog, LogLevel::Verbose)) { return; } std::wstring paramString; IPC::LogParam(aParam, ¶mString); HOOK_LOG(LogLevel::Verbose, ("Parameter %d: %S", aIndex, paramString.c_str())); #endif } // This specialization is needed to log the common pattern where null is used // as a fixed value for a pointer-type that is unknown to IPC. template inline void LogParameterValue(int aIndex, ParamType* const& aParam) { #ifdef DEBUG HOOK_LOG(LogLevel::Verbose, ("Parameter %d: pointer value - %p", aIndex, aParam)); #endif } template <> inline void LogParameterValue(int aIndex, const nsDependentCSubstring& aParam) { #ifdef DEBUG HOOK_LOG(LogLevel::Verbose, ("Parameter %d : %s", aIndex, FormatBlob(aParam).Data())); #endif } template <> inline void LogParameterValue(int aIndex, char* const& aParam) { #ifdef DEBUG // A char* can be a block of raw memory. nsDependentCSubstring str; if (aParam) { str.Rebind(const_cast(aParam), strnlen(aParam, MAX_BLOB_CHARS_TO_LOG)); } else { str.SetIsVoid(true); } LogParameterValue(aIndex, str); #endif } template <> inline void LogParameterValue(int aIndex, const char* const& aParam) { #ifdef DEBUG LogParameterValue(aIndex, const_cast(aParam)); #endif } #if defined(XP_WIN) template <> inline void LogParameterValue(int aIndex, const SEC_GET_KEY_FN& aParam) { # ifdef DEBUG MOZ_ASSERT(aParam == nullptr); HOOK_LOG(LogLevel::Verbose, ("Parameter %d: null function.", aIndex)); # endif } template <> inline void LogParameterValue(int aIndex, LPVOID* const& aParam) { # ifdef DEBUG MOZ_ASSERT(aParam == nullptr); HOOK_LOG(LogLevel::Verbose, ("Parameter %d: null void pointer.", aIndex)); # endif } #endif // defined(XP_WIN) // Used to check if a fixed parameter value is equal to the parameter given // in the original function call. template inline bool ParameterEquality(const ParamType& aParam1, const ParamType& aParam2) { return aParam1 == aParam2; } // Specialization: char* equality is string equality template <> inline bool ParameterEquality(char* const& aParam1, char* const& aParam2) { return ((!aParam1 && !aParam2) || (aParam1 && aParam2 && !strcmp(aParam1, aParam2))); } // Specialization: const char* const equality is string equality template <> inline bool ParameterEquality(const char* const& aParam1, const char* const& aParam2) { return ParameterEquality(const_cast(aParam1), const_cast(aParam2)); } /** * A type map _from_ the type of a parameter in the original function * we are brokering _to_ a type that we can marshal. We must be able * to Copy() the marshaled type using the parameter type. * The default maps from type T back to type T. */ template struct IPCTypeMap { typedef OrigType ipc_type; }; template <> struct IPCTypeMap { typedef nsDependentCSubstring ipc_type; }; template <> struct IPCTypeMap { typedef nsDependentCSubstring ipc_type; }; template <> struct IPCTypeMap { typedef nsString ipc_type; }; template <> struct IPCTypeMap { typedef nsString ipc_type; }; template <> struct IPCTypeMap { typedef int32_t ipc_type; }; template <> struct IPCTypeMap { typedef uint32_t ipc_type; }; #if defined(XP_WIN) template <> struct IPCTypeMap { typedef uint64_t ipc_type; }; template <> struct IPCTypeMap { typedef uint64_t ipc_type; }; template <> struct IPCTypeMap { typedef uint64_t ipc_type; }; // HANDLEs template <> struct IPCTypeMap { typedef NativeWindowHandle ipc_type; }; template <> struct IPCTypeMap { typedef IPCSchannelCred ipc_type; }; template <> struct IPCTypeMap { typedef IPCInternetBuffers ipc_type; }; template <> struct IPCTypeMap { typedef uint32_t ipc_type; }; #endif template static void DeleteDestructor(void* aObj) { delete static_cast(aObj); } extern void FreeDestructor(void* aObj); // The ServerCallData is a list of ServerCallItems that should be freed when // the server has completed a function call and marshaled a response. class ServerCallData { public: typedef void(DestructorType)(void*); // Allocate a certain type. template AllocType* Allocate( DestructorType* aDestructor = &DeleteDestructor) { AllocType* ret = new AllocType(); mList.AppendElement(FreeItem(ret, aDestructor)); return ret; } template AllocType* Allocate( const AllocType& aValueToCopy, DestructorType* aDestructor = &DeleteDestructor) { AllocType* ret = Allocate(aDestructor); *ret = aValueToCopy; return ret; } // Allocate memory, storing the pointer in buf. template void AllocateMemory(unsigned long aBufLen, PtrType& aBuf) { if (aBufLen) { aBuf = static_cast(malloc(aBufLen)); mList.AppendElement(FreeItem(aBuf, FreeDestructor)); } else { aBuf = nullptr; } } template void AllocateString(const nsACString& aStr, PtrType& aBuf, bool aCopyNullTerminator = true) { uint32_t nullByte = aCopyNullTerminator ? 1 : 0; char* tempBuf = static_cast(malloc(aStr.Length() + nullByte)); memcpy(tempBuf, aStr.Data(), aStr.Length() + nullByte); mList.AppendElement(FreeItem(tempBuf, FreeDestructor)); aBuf = tempBuf; } // Run the given destructor on the given memory, for special cases where // memory is allocated elsewhere but must still be freed. void PostDestructor(void* aMem, DestructorType* aDestructor) { mList.AppendElement(FreeItem(aMem, aDestructor)); } #if defined(XP_WIN) // Allocate memory and a DWORD block-length, storing them in the // corresponding parameters. template void AllocateMemory(DWORD aBufLen, PtrType& aBuf, LPDWORD& aBufLenCopy) { aBufLenCopy = static_cast(malloc(sizeof(DWORD))); *aBufLenCopy = aBufLen; mList.AppendElement(FreeItem(aBufLenCopy, FreeDestructor)); AllocateMemory(aBufLen, aBuf); } #endif // defined(XP_WIN) private: // FreeItems are used to free objects that were temporarily needed for // dispatch, such as buffers that are given as a parameter. class FreeItem { void* mPtr; DestructorType* mDestructor; FreeItem(FreeItem& aOther); // revoked public: explicit FreeItem(void* aPtr, DestructorType* aDestructor) : mPtr(aPtr), mDestructor(aDestructor) { MOZ_ASSERT(mDestructor || !aPtr); } FreeItem(FreeItem&& aOther) : mPtr(aOther.mPtr), mDestructor(aOther.mDestructor) { aOther.mPtr = nullptr; aOther.mDestructor = nullptr; } ~FreeItem() { if (mDestructor) { mDestructor(mPtr); } } }; typedef nsTArray FreeItemList; FreeItemList mList; }; // Holds an IpdlTuple and a ServerCallData. This is used by the phase handlers // (RequestHandler and ResponseHandler) in the Unmarshaling phase. // Server-side unmarshaling (during the request phase) uses a ServerCallData // to keep track of allocated memory. In the client, ServerCallDatas are // not used and that value will always be null. class IpdlTupleContext { public: explicit IpdlTupleContext(const IpdlTuple* aTuple, ServerCallData* aScd = nullptr) : mTuple(aTuple), mScd(aScd) { MOZ_ASSERT(aTuple); } ServerCallData* GetServerCallData() { return mScd; } const IpdlTuple* GetIpdlTuple() { return mTuple; } private: const IpdlTuple* mTuple; ServerCallData* mScd; }; template inline void Copy(DestType& aDest, const SrcType& aSrc) { aDest = (DestType)aSrc; } template <> inline void Copy(nsDependentCSubstring& aDest, const nsDependentCSubstring& aSrc) { if (aSrc.IsVoid()) { aDest.SetIsVoid(true); } else { aDest.Rebind(aSrc.Data(), aSrc.Length()); } } #if defined(XP_WIN) template <> inline void Copy(uint64_t& aDest, const PTimeStamp& aSrc) { aDest = static_cast(aSrc->QuadPart); } template <> inline void Copy(PTimeStamp& aDest, const uint64_t& aSrc) { aDest->QuadPart = static_cast(aSrc); } #endif // defined(XP_WIN) template struct BaseEndpointHandler; template struct BaseEndpointHandler { static const Endpoint OtherSide = SERVER; template inline static void Copy(ServerCallData* aScd, DestType& aDest, const SrcType& aSrc) { MOZ_ASSERT(!aScd); // never used in the CLIENT SelfType::Copy(aDest, aSrc); } template inline static void Copy(DestType& aDest, const SrcType& aSrc) { mozilla::plugins::Copy(aDest, aSrc); } // const char* should be null terminated but this is not always the case. // In those cases, we must override this default behavior. inline static void Copy(nsDependentCSubstring& aDest, const char* const& aSrc) { // In the client, we just bind to the caller's string if (aSrc) { aDest.Rebind(aSrc, strlen(aSrc)); } else { aDest.SetIsVoid(true); } } inline static void Copy(const char*& aDest, const nsDependentCSubstring& aSrc) { MOZ_ASSERT_UNREACHABLE("Cannot return const parameters."); } inline static void Copy(nsDependentCSubstring& aDest, char* const& aSrc) { // In the client, we just bind to the caller's string if (aSrc) { aDest.Rebind(aSrc, strlen(aSrc)); } else { aDest.SetIsVoid(true); } } inline static void Copy(nsString& aDest, wchar_t* const& aSrc) { if (aSrc) { // We are using nsString as a "raw" container for a wchar_t string. We // just use its data as a wchar_t* later (so the reinterpret_cast is // safe). aDest.Rebind(reinterpret_cast(aSrc), wcslen(aSrc)); } else { aDest.SetIsVoid(true); } } inline static void Copy(char*& aDest, const nsDependentCSubstring& aSrc) { MOZ_ASSERT_UNREACHABLE("Returning char* parameters is not yet suported."); } #if defined(XP_WIN) inline static void Copy(uint32_t& aDest, const LPDWORD& aSrc) { aDest = *aSrc; } inline static void Copy(LPDWORD& aDest, const uint32_t& aSrc) { *aDest = aSrc; } #endif // #if defined(XP_WIN) }; template struct BaseEndpointHandler { static const Endpoint OtherSide = CLIENT; // Specializations of this method may allocate memory for types that need it // during Unmarshaling. They record the allocation in the ServerCallData. // When copying values in the SERVER, we should be sure to carefully validate // the information that came from the client as the client may be compromised // by malicious code. template inline static void Copy(ServerCallData* aScd, DestType& aDest, const SrcType& aSrc) { SelfType::Copy(aDest, aSrc); } template inline static void Copy(DestType& aDest, const SrcType& aSrc) { mozilla::plugins::Copy(aDest, aSrc); } inline static void Copy(nsDependentCSubstring& aDest, const nsDependentCSubstring& aSrc) { aDest.Rebind(aSrc.Data(), aSrc.Length()); aDest.SetIsVoid(aSrc.IsVoid()); } // const char* should be null terminated but this is not always the case. // In those cases, we override this default behavior. inline static void Copy(nsDependentCSubstring& aDest, const char* const& aSrc) { MOZ_ASSERT_UNREACHABLE( "Const parameter cannot be returned by brokering process."); } inline static void Copy(nsDependentCSubstring& aDest, char* const& aSrc) { MOZ_ASSERT_UNREACHABLE("Returning char* parameters is not yet suported."); } inline static void Copy(ServerCallData* aScd, char*& aDest, const nsDependentCSubstring& aSrc) { // In the parent, we must allocate the string. MOZ_ASSERT(aScd); if (aSrc.IsVoid()) { aDest = nullptr; return; } aScd->AllocateMemory(aSrc.Length() + 1, aDest); memcpy(aDest, aSrc.Data(), aSrc.Length()); aDest[aSrc.Length()] = '\0'; } inline static void Copy(ServerCallData* aScd, const char*& aDest, const nsDependentCSubstring& aSrc) { char* nonConstDest; Copy(aScd, nonConstDest, aSrc); aDest = nonConstDest; } inline static void Copy(ServerCallData* aScd, wchar_t*& aDest, const nsString& aSrc) { // Allocating the string with aScd means it will last during the server call // and be freed when the call is complete. MOZ_ASSERT(aScd); if (aSrc.IsVoid()) { aDest = nullptr; return; } aScd->AllocateMemory((aSrc.Length() + 1) * sizeof(wchar_t), aDest); memcpy(aDest, aSrc.Data(), aSrc.Length() * sizeof(wchar_t)); aDest[aSrc.Length()] = L'\0'; } inline static void Copy(ServerCallData* aScd, const wchar_t*& aDest, const nsString& aSrc) { wchar_t* nonConstDest; Copy(aScd, nonConstDest, aSrc); aDest = nonConstDest; } #if defined(XP_WIN) inline static void Copy(uint32_t& aDest, const LPDWORD& aSrc) { aDest = *aSrc; } inline static void Copy(LPDWORD& aDest, const uint32_t& aSrc) { MOZ_RELEASE_ASSERT(aDest); *aDest = aSrc; } inline static void Copy(ServerCallData* aScd, PTimeStamp& aDest, const uint64_t& aSrc) { MOZ_ASSERT(!aDest); aDest = aScd->Allocate<::TimeStamp>(); Copy(aDest, aSrc); } #endif // defined(XP_WIN) }; // PhaseHandler is a RequestHandler or a ResponseHandler. template struct Marshaler { // Driver template static void Marshal(IpdlTuple& aMarshaledTuple, const VarParams&... aParams) { MarshalParameters(aMarshaledTuple, aParams...); } // Driver template static bool Unmarshal(IpdlTupleContext& aUnmarshaledTuple, VarParams&... aParams) { return UnmarshalParameters(aUnmarshaledTuple, 0, aParams...); } template ::value> struct MaybeMarshalParameter {}; /** * shouldMarshal = true case */ template struct MaybeMarshalParameter { template ::ipc_type> static void MarshalParameter(IpdlTuple& aMarshaledTuple, const OrigType& aParam) { HOOK_LOG(LogLevel::Verbose, ("%s marshaling parameter %d.", EndpointMsg(endpoint), paramIndex)); IPCType ipcObject; // EndpointHandler must be able to Copy() from OrigType to IPCType PhaseHandler::EHContainer::template EndpointHandler::Copy( ipcObject, aParam); LogParameterValue(paramIndex, ipcObject); aMarshaledTuple.AddElement(ipcObject); } }; /** * shouldMarshal = false case */ template struct MaybeMarshalParameter { static void MarshalParameter(IpdlTuple& aMarshaledTuple, const OrigType& aParam) { HOOK_LOG(LogLevel::Verbose, ("%s not marshaling parameter %d.", EndpointMsg(endpoint), paramIndex)); } }; /** * Recursive case: marshals aFirstParam to aMarshaledTuple (if desired), * then marshals the aRemainingParams. */ template static void MarshalParameters(IpdlTuple& aMarshaledTuple, const VarParam& aFirstParam, const VarParams&... aRemainingParams) { MaybeMarshalParameter::MarshalParameter( aMarshaledTuple, aFirstParam); MarshalParameters(aMarshaledTuple, aRemainingParams...); } /** * Base case: empty parameter list -- nothing to marshal. */ template static void MarshalParameters(IpdlTuple& aMarshaledTuple) {} template ::value, bool hasFixedValue = PhaseHandler::Info::template HasFixedValue::value> struct MaybeUnmarshalParameter {}; /** * ShouldMarshal = true case. HasFixedValue must be false in that case. */ template struct MaybeUnmarshalParameter { template ::ipc_type> static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, int& aNextTupleIdx, VarParam& aParam) { const IPCType* ipcObject = aUnmarshaledTuple.GetIpdlTuple()->Element(aNextTupleIdx); if (!ipcObject) { HOOK_LOG(LogLevel::Error, ("%s failed to unmarshal parameter %d.", EndpointMsg(endpoint), tupleIndex)); return false; } HOOK_LOG(LogLevel::Verbose, ("%s unmarshaled parameter %d.", EndpointMsg(endpoint), tupleIndex)); LogParameterValue(tupleIndex, *ipcObject); PhaseHandler::EHContainer::template EndpointHandler::Copy( aUnmarshaledTuple.GetServerCallData(), aParam, *ipcObject); ++aNextTupleIdx; return true; } }; /** * ShouldMarshal = true : nsDependentCSubstring specialization */ template struct MaybeUnmarshalParameter { static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, int& aNextTupleIdx, nsDependentCSubstring& aParam) { // Deserialize as an nsCString and then copy the info into the // nsDependentCSubstring const nsCString* ipcObject = aUnmarshaledTuple.GetIpdlTuple()->Element(aNextTupleIdx); if (!ipcObject) { HOOK_LOG(LogLevel::Error, ("%s failed to unmarshal parameter %d.", EndpointMsg(endpoint), tupleIndex)); return false; } HOOK_LOG(LogLevel::Verbose, ("%s unmarshaled parameter %d.", EndpointMsg(endpoint), tupleIndex)); aParam.Rebind(ipcObject->Data(), ipcObject->Length()); aParam.SetIsVoid(ipcObject->IsVoid()); LogParameterValue(tupleIndex, aParam); ++aNextTupleIdx; return true; } }; /** * ShouldMarshal = true : char* specialization */ template struct MaybeUnmarshalParameter { static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, int& aNextTupleIdx, char*& aParam) { nsDependentCSubstring tempStr; bool ret = MaybeUnmarshalParameter::UnmarshalParameter(aUnmarshaledTuple, aNextTupleIdx, tempStr); PhaseHandler::EHContainer::template EndpointHandler::Copy( aUnmarshaledTuple.GetServerCallData(), aParam, tempStr); return ret; } }; /** * ShouldMarshal = true : const char* specialization */ template struct MaybeUnmarshalParameter { static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, int& aNextTupleIdx, const char*& aParam) { char* tempStr; bool ret = MaybeUnmarshalParameter::UnmarshalParameter(aUnmarshaledTuple, aNextTupleIdx, tempStr); aParam = tempStr; return ret; } }; /** * ShouldMarshal = false, fixed parameter case */ template struct MaybeUnmarshalParameter { static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, int& aNextTupleIdx, VarParam& aParam) { // Copy default value if this is client->server communication (and if it // exists) PhaseHandler::template CopyFixedParam(aParam); HOOK_LOG(LogLevel::Verbose, ("%s parameter %d not unmarshaling -- using fixed value.", EndpointMsg(endpoint), tupleIndex)); LogParameterValue(tupleIndex, aParam); return true; } }; /** * ShouldMarshal = false, unfixed parameter case. Assume user has done * special handling. */ template struct MaybeUnmarshalParameter { static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, int& aNextTupleIdx, VarParam& aParam) { HOOK_LOG(LogLevel::Verbose, ("%s parameter %d not automatically unmarshaling.", EndpointMsg(endpoint), tupleIndex)); // DLP: TODO: specializations fail LogParameterValue(tupleIndex, aParam); return true; } }; /** * Recursive case: unmarshals aFirstParam to aUnmarshaledTuple (if desired), * then unmarshals the aRemainingParams. * The endpoint specifies the side this process is on: client or server. */ template static bool UnmarshalParameters(IpdlTupleContext& aUnmarshaledTuple, int aNextTupleIdx, VarParam& aFirstParam, VarParams&... aRemainingParams) { // TODO: DLP: I currently increment aNextTupleIdx in the method (its a // reference). This is awful. if (!MaybeUnmarshalParameter::UnmarshalParameter( aUnmarshaledTuple, aNextTupleIdx, aFirstParam)) { return false; } return UnmarshalParameters( aUnmarshaledTuple, aNextTupleIdx, aRemainingParams...); } /** * Base case: empty parameter list -- nothing to unmarshal. */ template static bool UnmarshalParameters(IpdlTupleContext& aUnmarshaledTuple, int aNextTupleIdx) { return true; } }; // The default marshals all parameters. template struct RequestInfo { template struct FixedValue; template struct HasFixedValue { static const bool value = false; }; template struct HasFixedValue::value, 0)> { static const bool value = true; }; // By default we the request should marshal any non-fixed parameters. template struct ShouldMarshal { static const bool value = !HasFixedValue::value; }; }; /** * This base stores the RequestHandler's IPCTypeMap. It really only * exists to circumvent the arbitrary C++ rule (enforced by mingw) forbidding * full class specialization of a class (IPCTypeMap) inside of an * unspecialized template class (RequestHandler). */ struct RequestHandlerBase { // Default to the namespace-level IPCTypeMap template struct IPCTypeMap { typedef typename mozilla::plugins::IPCTypeMap::ipc_type ipc_type; }; }; #if defined(XP_WIN) // Request phase uses OpenFileNameIPC for an LPOPENFILENAMEW parameter. template <> struct RequestHandlerBase::IPCTypeMap { typedef OpenFileNameIPC ipc_type; }; #endif // defined(XP_WIN) struct BaseEHContainer { template struct EndpointHandler : public BaseEndpointHandler> {}; }; template struct RequestHandler; template struct RequestHandler : public RequestHandlerBase { typedef ResultType(HOOK_CALL FunctionType)(ParamTypes...); typedef RequestHandler SelfType; typedef RequestInfo Info; typedef EHContainerType EHContainer; static void Marshal(IpdlTuple& aTuple, const ParamTypes&... aParams) { ReqMarshaler::Marshal(aTuple, aParams...); } static bool Unmarshal(ServerCallData& aScd, const IpdlTuple& aTuple, ParamTypes&... aParams) { IpdlTupleContext cxt(&aTuple, &aScd); return ReqUnmarshaler::Unmarshal(cxt, aParams...); } typedef Marshaler ReqMarshaler; typedef Marshaler ReqUnmarshaler; /** * Returns true if a call made with the given parameters should be * brokered (vs. passed-through to the original function). */ static bool ShouldBroker(Endpoint aEndpoint, const ParamTypes&... aParams) { // True if all filtered parameters match their filter value. return CheckFixedParams(aParams...); } template static void CopyFixedParam(VarParam& aParam) { aParam = Info::template FixedValue::value; } protected: // Returns true if filtered parameters match their filter value. static bool CheckFixedParams(const ParamTypes&... aParams) { return CheckFixedParamsHelper<0>(aParams...); } // If no FixedValue is defined and equal to FixedType then always // pass. template struct CheckFixedParam { template static inline bool Check(const ParamType& aParam) { return true; } }; // If FixedValue is defined then check equality. template struct CheckFixedParam< paramIndex, decltype(Info::template FixedValue::value, 0)> { template static inline bool Check(ParamType& aParam) { return ParameterEquality(aParam, Info::template FixedValue::value); } }; // Recursive case: Chcek head parameter, then tail parameters. template static bool CheckFixedParamsHelper(const VarParam& aParam, const VarParams&... aParams) { if (!CheckFixedParam::Check(aParam)) { return false; // didn't match a fixed parameter } return CheckFixedParamsHelper(aParams...); } // Base case: All fixed parameters matched. template static bool CheckFixedParamsHelper() { return true; } }; // The default returns no parameters -- only the return value. template struct ResponseInfo { template struct HasFixedValue { static const bool value = RequestInfo::template HasFixedValue::value; }; // Only the return value (index -1) is sent by default. template struct ShouldMarshal { static const bool value = (paramIndex == -1); }; // This is the condition on the function result that we use to determine if // the windows thread-local error state should be sent to the client. The // error is typically only relevant if the function did not succeed. template static bool ShouldTransmitError(const ResultType& aResult) { return !static_cast(aResult); } }; /** * Same rationale as for RequestHandlerBase. */ struct ResponseHandlerBase { // Default to the namespace-level IPCTypeMap template struct IPCTypeMap { typedef typename mozilla::plugins::IPCTypeMap::ipc_type ipc_type; }; }; #if defined(XP_WIN) // Response phase uses OpenFileNameRetIPC for an LPOPENFILENAMEW parameter. template <> struct ResponseHandlerBase::IPCTypeMap { typedef OpenFileNameRetIPC ipc_type; }; #endif template struct ResponseHandler; template struct ResponseHandler : public ResponseHandlerBase { typedef ResultType(HOOK_CALL FunctionType)(ParamTypes...); typedef ResponseHandler SelfType; typedef ResponseInfo Info; typedef EHContainerType EHContainer; static void Marshal(IpdlTuple& aTuple, const ResultType& aResult, const ParamTypes&... aParams) { // Note that this "trick" means that the first parameter we marshal is // considered to be parameter #-1 when checking the ResponseInfo. // The parameters in the list therefore start at index 0. RspMarshaler::template Marshal<-1>(aTuple, aResult, aParams...); } static bool Unmarshal(const IpdlTuple& aTuple, ResultType& aResult, ParamTypes&... aParams) { IpdlTupleContext cxt(&aTuple); return RspUnmarshaler::template Unmarshal<-1>(cxt, aResult, aParams...); } typedef Marshaler RspMarshaler; typedef Marshaler RspUnmarshaler; // Fixed parameters are not used in the response phase. template static void CopyFixedParam(VarParam& aParam) {} }; /** * Reference-counted monitor, used to synchronize communication between a * thread using a brokered API and the FunctionDispatch thread. */ class FDMonitor : public Monitor { public: FDMonitor() : Monitor("FunctionDispatchThread lock") {} NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FDMonitor) private: ~FDMonitor() = default; }; /** * Data for hooking a function that we automatically broker in a remote * process. */ template class FunctionBroker; template class FunctionBroker : public BasicFunctionHook { public: typedef Tuple TupleParamTypes; typedef Tuple...> TupleMaybeParamTypes; typedef Tuple TupleParamPtrTypes; typedef Tuple TupleParamRefTypes; static const size_t numParams = sizeof...(ParamTypes); typedef ResultType(HOOK_CALL FunctionType)(ParamTypes...); typedef FunctionBroker SelfType; typedef BasicFunctionHook FunctionHookInfoType; typedef FunctionHookInfoType BaseType; typedef RequestHandler Request; typedef ResponseHandler Response; template using RequestDelegate = RequestHandler; template using ResponseDelegate = ResponseHandler; FunctionBroker(const char* aModuleName, const char* aMethodName, FunctionType* aOriginalFunction) : BasicFunctionHook( aModuleName, aMethodName, aOriginalFunction, InterceptorStub) {} // This is the function used to replace the original DLL-intercepted function. static ResultType HOOK_CALL InterceptorStub(ParamTypes... aParams) { MOZ_ASSERT(functionId < FunctionHook::GetHooks()->Length()); FunctionHook* self = FunctionHook::GetHooks()->ElementAt(functionId); MOZ_ASSERT(self && self->FunctionId() == functionId); const SelfType* broker = static_cast(self); return broker->MaybeBrokerCallClient(aParams...); } /** * Handle a call by running the original version or brokering, depending on * ShouldBroker. All parameter types (including the result type) * must have IPDL ParamTraits specializations or appear in this object's * IPCTypeMap. If brokering fails for any reason then this falls back to * calling the original version of the function. */ ResultType MaybeBrokerCallClient(ParamTypes&... aParameters) const; /** * Called server-side to run the original function using aInTuple * as parameter values. The return value and returned parameters * (in that order) are added to aOutTuple. */ bool RunOriginalFunction(base::ProcessId aClientId, const IPC::IpdlTuple& aInTuple, IPC::IpdlTuple* aOutTuple) const override { return BrokerCallServer(aClientId, aInTuple, aOutTuple); } protected: bool BrokerCallServer(base::ProcessId aClientId, const IpdlTuple& aInTuple, IpdlTuple* aOutTuple) const { return BrokerCallServer(aClientId, aInTuple, aOutTuple, std::index_sequence_for{}); } bool BrokerCallClient(uint32_t& aWinError, ResultType& aResult, ParamTypes&... aParameters) const; bool PostToDispatchThread(uint32_t& aWinError, ResultType& aRet, ParamTypes&... aParameters) const; static void PostToDispatchHelper(const SelfType* bmhi, RefPtr monitor, bool* notified, bool* ok, uint32_t* winErr, ResultType* r, ParamTypes*... p) { // Note: p is also non-null... its just hard to assert that. MOZ_ASSERT(bmhi && monitor && notified && ok && winErr && r); MOZ_ASSERT(*notified == false); *ok = bmhi->BrokerCallClient(*winErr, *r, *p...); { // We need to grab the lock to make sure that Wait() has been // called in PostToDispatchThread. We need that since we wake it with // Notify(). MonitorAutoLock lock(*monitor); *notified = true; } monitor->Notify(); }; template BROKER_DISABLE_CFGUARD ResultType RunFunction(FunctionType* aFunction, base::ProcessId aClientId, VarParams&... aParams) const { return aFunction(aParams...); }; bool BrokerCallServer(base::ProcessId aClientId, const IpdlTuple& aInTuple, IpdlTuple* aOutTuple, ParamTypes&... aParams) const; template bool BrokerCallServer(base::ProcessId aClientId, const IpdlTuple& aInTuple, IpdlTuple* aOutTuple, std::index_sequence) const { TupleParamTypes paramTuple; return BrokerCallServer(aClientId, aInTuple, aOutTuple, Get(paramTuple)...); } }; template ResultType FunctionBroker< functionId, ResultType HOOK_CALL(ParamTypes...), EHContainer>::MaybeBrokerCallClient(ParamTypes&... aParameters) const { MOZ_ASSERT(FunctionBrokerChild::GetInstance()); // Broker the call if ShouldBroker says to. Otherwise, or if brokering // fails, then call the original implementation. if (!FunctionBrokerChild::GetInstance()) { HOOK_LOG(LogLevel::Error, ("[%s] Client attempted to broker call without actor.", FunctionHookInfoType::mFunctionName.Data())); } else if (Request::ShouldBroker(CLIENT, aParameters...)) { HOOK_LOG(LogLevel::Debug, ("[%s] Client attempting to broker call.", FunctionHookInfoType::mFunctionName.Data())); uint32_t winError; ResultType ret; bool success = BrokerCallClient(winError, ret, aParameters...); HOOK_LOG(LogLevel::Info, ("[%s] Client brokering %s.", FunctionHookInfoType::mFunctionName.Data(), SuccessMsg(success))); if (success) { #if defined(XP_WIN) if (Response::Info::ShouldTransmitError(ret)) { HOOK_LOG(LogLevel::Debug, ("[%s] Client setting thread error code: %08x.", FunctionHookInfoType::mFunctionName.Data(), winError)); ::SetLastError(winError); } #endif return ret; } } HOOK_LOG(LogLevel::Info, ("[%s] Client could not broker. Running original version.", FunctionHookInfoType::mFunctionName.Data())); return FunctionHookInfoType::mOldFunction(aParameters...); } template bool FunctionBroker::BrokerCallClient(uint32_t& aWinError, ResultType& aResult, ParamTypes&... aParameters) const { if (!FunctionBrokerChild::GetInstance()->IsDispatchThread()) { return PostToDispatchThread(aWinError, aResult, aParameters...); } if (FunctionBrokerChild::GetInstance()) { IpdlTuple sending, returned; HOOK_LOG(LogLevel::Debug, ("[%s] Client marshaling parameters.", FunctionHookInfoType::mFunctionName.Data())); Request::Marshal(sending, aParameters...); HOOK_LOG(LogLevel::Info, ("[%s] Client sending broker message.", FunctionHookInfoType::mFunctionName.Data())); if (FunctionBrokerChild::GetInstance()->SendBrokerFunction( FunctionHookInfoType::FunctionId(), sending, &returned)) { HOOK_LOG(LogLevel::Debug, ("[%s] Client received broker message response.", FunctionHookInfoType::mFunctionName.Data())); bool success = Response::Unmarshal(returned, aResult, aParameters...); HOOK_LOG(LogLevel::Info, ("[%s] Client response unmarshaling: %s.", FunctionHookInfoType::mFunctionName.Data(), SuccessMsg(success))); #if defined(XP_WIN) if (success && Response::Info::ShouldTransmitError(aResult)) { uint32_t* winError = returned.Element(returned.NumElements() - 1); if (!winError) { HOOK_LOG(LogLevel::Error, ("[%s] Client failed to unmarshal error code.", FunctionHookInfoType::mFunctionName.Data())); return false; } HOOK_LOG(LogLevel::Debug, ("[%s] Client response unmarshaled error code: %08x.", FunctionHookInfoType::mFunctionName.Data(), *winError)); aWinError = *winError; } #endif return success; } } HOOK_LOG(LogLevel::Error, ("[%s] Client failed to broker call.", FunctionHookInfoType::mFunctionName.Data())); return false; } template bool FunctionBroker::BrokerCallServer(base::ProcessId aClientId, const IpdlTuple& aInTuple, IpdlTuple* aOutTuple, ParamTypes&... aParams) const { HOOK_LOG(LogLevel::Info, ("[%s] Server brokering function.", FunctionHookInfoType::mFunctionName.Data())); ServerCallData scd; if (!Request::Unmarshal(scd, aInTuple, aParams...)) { HOOK_LOG(LogLevel::Info, ("[%s] Server failed to unmarshal.", FunctionHookInfoType::mFunctionName.Data())); return false; } // Make sure that this call was legal -- do not execute a call that // shouldn't have been brokered in the first place. if (!Request::ShouldBroker(SERVER, aParams...)) { HOOK_LOG(LogLevel::Error, ("[%s] Server rejected brokering request.", FunctionHookInfoType::mFunctionName.Data())); return false; } // Run the function we are brokering. HOOK_LOG(LogLevel::Info, ("[%s] Server broker running function.", FunctionHookInfoType::mFunctionName.Data())); ResultType ret = RunFunction(FunctionHookInfoType::mOldFunction, aClientId, aParams...); #if defined(XP_WIN) // Record the thread-local error state (before it is changed) if needed. uint32_t err = UINT_MAX; bool transmitError = Response::Info::ShouldTransmitError(ret); if (transmitError) { err = ::GetLastError(); HOOK_LOG(LogLevel::Info, ("[%s] Server returning thread error code: %08x.", FunctionHookInfoType::mFunctionName.Data(), err)); } #endif // Add the result, win thread error and any returned parameters to the // returned tuple. Response::Marshal(*aOutTuple, ret, aParams...); #if defined(XP_WIN) if (transmitError) { aOutTuple->AddElement(err); } #endif return true; } template bool FunctionBroker< functionId, ResultType HOOK_CALL(ParamTypes...), EHContainer>::PostToDispatchThread(uint32_t& aWinError, ResultType& aRet, ParamTypes&... aParameters) const { MOZ_ASSERT(!FunctionBrokerChild::GetInstance()->IsDispatchThread()); HOOK_LOG(LogLevel::Debug, ("Posting broker task '%s' to dispatch thread", FunctionHookInfoType::mFunctionName.Data())); // Run PostToDispatchHelper on the dispatch thread. It will notify our // waiting monitor when it is done. RefPtr monitor(new FDMonitor()); MonitorAutoLock lock(*monitor); bool success = false; bool notified = false; FunctionBrokerChild::GetInstance()->PostToDispatchThread(NewRunnableFunction( "FunctionDispatchThreadRunnable", &PostToDispatchHelper, this, monitor, ¬ified, &success, &aWinError, &aRet, &aParameters...)); // We wait to be notified, testing that notified was actually set to make // sure this isn't a spurious wakeup. while (!notified) { monitor->Wait(); } return success; } void AddBrokeredFunctionHooks(FunctionHookArray& aHooks); } // namespace plugins } // namespace mozilla #endif // dom_plugins_ipc_PluginHooksWin_h