1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #define INITGUID
8 
9 #include "mozilla/mscom/Interceptor.h"
10 
11 #include <utility>
12 
13 #include "MainThreadUtils.h"
14 #include "mozilla/Assertions.h"
15 #include "mozilla/ThreadLocal.h"
16 #include "mozilla/Unused.h"
17 #include "mozilla/mscom/DispatchForwarder.h"
18 #include "mozilla/mscom/FastMarshaler.h"
19 #include "mozilla/mscom/InterceptorLog.h"
20 #include "mozilla/mscom/MainThreadInvoker.h"
21 #include "mozilla/mscom/Objref.h"
22 #include "mozilla/mscom/Registration.h"
23 #include "mozilla/mscom/Utils.h"
24 #include "nsDirectoryServiceDefs.h"
25 #include "nsDirectoryServiceUtils.h"
26 #include "nsExceptionHandler.h"
27 #include "nsPrintfCString.h"
28 #include "nsRefPtrHashtable.h"
29 #include "nsThreadUtils.h"
30 #include "nsXULAppAPI.h"
31 
32 #define ENSURE_HR_SUCCEEDED(hr)       \
33   MOZ_ASSERT(SUCCEEDED((HRESULT)hr)); \
34   if (FAILED((HRESULT)hr)) {          \
35     return hr;                        \
36   }
37 
38 namespace mozilla {
39 namespace mscom {
40 namespace detail {
41 
42 class LiveSet final {
43  public:
LiveSet()44   LiveSet() : mMutex("mozilla::mscom::LiveSet::mMutex") {}
45 
Lock()46   void Lock() { mMutex.Lock(); }
47 
Unlock()48   void Unlock() { mMutex.Unlock(); }
49 
Put(IUnknown * aKey,already_AddRefed<IWeakReference> aValue)50   void Put(IUnknown* aKey, already_AddRefed<IWeakReference> aValue) {
51     mMutex.AssertCurrentThreadOwns();
52     mLiveSet.InsertOrUpdate(aKey, RefPtr<IWeakReference>{std::move(aValue)});
53   }
54 
Get(IUnknown * aKey)55   RefPtr<IWeakReference> Get(IUnknown* aKey) {
56     mMutex.AssertCurrentThreadOwns();
57     RefPtr<IWeakReference> result;
58     mLiveSet.Get(aKey, getter_AddRefs(result));
59     return result;
60   }
61 
Remove(IUnknown * aKey)62   void Remove(IUnknown* aKey) {
63     mMutex.AssertCurrentThreadOwns();
64     mLiveSet.Remove(aKey);
65   }
66 
67  private:
68   Mutex mMutex;
69   nsRefPtrHashtable<nsPtrHashKey<IUnknown>, IWeakReference> mLiveSet;
70 };
71 
72 /**
73  * We don't use the normal XPCOM BaseAutoLock because we need the ability
74  * to explicitly Unlock.
75  */
76 class MOZ_RAII LiveSetAutoLock final {
77  public:
LiveSetAutoLock(LiveSet & aLiveSet)78   explicit LiveSetAutoLock(LiveSet& aLiveSet) : mLiveSet(&aLiveSet) {
79     aLiveSet.Lock();
80   }
81 
~LiveSetAutoLock()82   ~LiveSetAutoLock() {
83     if (mLiveSet) {
84       mLiveSet->Unlock();
85     }
86   }
87 
Unlock()88   void Unlock() {
89     MOZ_ASSERT(mLiveSet);
90     if (mLiveSet) {
91       mLiveSet->Unlock();
92       mLiveSet = nullptr;
93     }
94   }
95 
96   LiveSetAutoLock(const LiveSetAutoLock& aOther) = delete;
97   LiveSetAutoLock(LiveSetAutoLock&& aOther) = delete;
98   LiveSetAutoLock& operator=(const LiveSetAutoLock& aOther) = delete;
99   LiveSetAutoLock& operator=(LiveSetAutoLock&& aOther) = delete;
100 
101  private:
102   LiveSet* mLiveSet;
103 };
104 
105 class MOZ_RAII ReentrySentinel final {
106  public:
ReentrySentinel(Interceptor * aCurrent)107   explicit ReentrySentinel(Interceptor* aCurrent) : mCurInterceptor(aCurrent) {
108     static const bool kHasTls = tlsSentinelStackTop.init();
109     MOZ_RELEASE_ASSERT(kHasTls);
110 
111     mPrevSentinel = tlsSentinelStackTop.get();
112     tlsSentinelStackTop.set(this);
113   }
114 
~ReentrySentinel()115   ~ReentrySentinel() { tlsSentinelStackTop.set(mPrevSentinel); }
116 
IsOutermost() const117   bool IsOutermost() const {
118     return !(mPrevSentinel && mPrevSentinel->IsMarshaling(mCurInterceptor));
119   }
120 
121   ReentrySentinel(const ReentrySentinel&) = delete;
122   ReentrySentinel(ReentrySentinel&&) = delete;
123   ReentrySentinel& operator=(const ReentrySentinel&) = delete;
124   ReentrySentinel& operator=(ReentrySentinel&&) = delete;
125 
126  private:
IsMarshaling(Interceptor * aTopInterceptor) const127   bool IsMarshaling(Interceptor* aTopInterceptor) const {
128     return aTopInterceptor == mCurInterceptor ||
129            (mPrevSentinel && mPrevSentinel->IsMarshaling(aTopInterceptor));
130   }
131 
132  private:
133   Interceptor* mCurInterceptor;
134   ReentrySentinel* mPrevSentinel;
135 
136   static MOZ_THREAD_LOCAL(ReentrySentinel*) tlsSentinelStackTop;
137 };
138 
139 MOZ_THREAD_LOCAL(ReentrySentinel*) ReentrySentinel::tlsSentinelStackTop;
140 
141 class MOZ_RAII LoggedQIResult final {
142  public:
LoggedQIResult(REFIID aIid)143   explicit LoggedQIResult(REFIID aIid)
144       : mIid(aIid),
145         mHr(E_UNEXPECTED),
146         mTarget(nullptr),
147         mInterceptor(nullptr),
148         mBegin(TimeStamp::Now()) {}
149 
~LoggedQIResult()150   ~LoggedQIResult() {
151     if (!mTarget) {
152       return;
153     }
154 
155     TimeStamp end(TimeStamp::Now());
156     TimeDuration total(end - mBegin);
157     TimeDuration overhead(total - mNonOverheadDuration);
158 
159     InterceptorLog::QI(mHr, mTarget, mIid, mInterceptor, &overhead,
160                        &mNonOverheadDuration);
161   }
162 
Log(IUnknown * aTarget,IUnknown * aInterceptor)163   void Log(IUnknown* aTarget, IUnknown* aInterceptor) {
164     mTarget = aTarget;
165     mInterceptor = aInterceptor;
166   }
167 
operator =(HRESULT aHr)168   void operator=(HRESULT aHr) { mHr = aHr; }
169 
operator HRESULT()170   operator HRESULT() { return mHr; }
171 
operator TimeDuration*()172   operator TimeDuration*() { return &mNonOverheadDuration; }
173 
174   LoggedQIResult(const LoggedQIResult&) = delete;
175   LoggedQIResult(LoggedQIResult&&) = delete;
176   LoggedQIResult& operator=(const LoggedQIResult&) = delete;
177   LoggedQIResult& operator=(LoggedQIResult&&) = delete;
178 
179  private:
180   REFIID mIid;
181   HRESULT mHr;
182   IUnknown* mTarget;
183   IUnknown* mInterceptor;
184   TimeDuration mNonOverheadDuration;
185   TimeStamp mBegin;
186 };
187 
188 }  // namespace detail
189 
GetLiveSet()190 static detail::LiveSet& GetLiveSet() {
191   static detail::LiveSet sLiveSet;
192   return sLiveSet;
193 }
194 
195 MOZ_THREAD_LOCAL(bool) Interceptor::tlsCreatingStdMarshal;
196 
197 /* static */
Create(STAUniquePtr<IUnknown> aTarget,IInterceptorSink * aSink,REFIID aInitialIid,void ** aOutInterface)198 HRESULT Interceptor::Create(STAUniquePtr<IUnknown> aTarget,
199                             IInterceptorSink* aSink, REFIID aInitialIid,
200                             void** aOutInterface) {
201   MOZ_ASSERT(aOutInterface && aTarget && aSink);
202   if (!aOutInterface) {
203     return E_INVALIDARG;
204   }
205 
206   detail::LiveSetAutoLock lock(GetLiveSet());
207 
208   RefPtr<IWeakReference> existingWeak(GetLiveSet().Get(aTarget.get()));
209   if (existingWeak) {
210     RefPtr<IWeakReferenceSource> existingStrong;
211     if (SUCCEEDED(existingWeak->ToStrongRef(getter_AddRefs(existingStrong)))) {
212       // QI on existingStrong may touch other threads. Since we now hold a
213       // strong ref on the interceptor, we may now release the lock.
214       lock.Unlock();
215       return existingStrong->QueryInterface(aInitialIid, aOutInterface);
216     }
217   }
218 
219   *aOutInterface = nullptr;
220 
221   if (!aTarget || !aSink) {
222     return E_INVALIDARG;
223   }
224 
225   RefPtr<Interceptor> intcpt(new Interceptor(aSink));
226   return intcpt->GetInitialInterceptorForIID(lock, aInitialIid,
227                                              std::move(aTarget), aOutInterface);
228 }
229 
Interceptor(IInterceptorSink * aSink)230 Interceptor::Interceptor(IInterceptorSink* aSink)
231     : WeakReferenceSupport(WeakReferenceSupport::Flags::eDestroyOnMainThread),
232       mEventSink(aSink),
233       mInterceptorMapMutex("mozilla::mscom::Interceptor::mInterceptorMapMutex"),
234       mStdMarshalMutex("mozilla::mscom::Interceptor::mStdMarshalMutex"),
235       mStdMarshal(nullptr) {
236   static const bool kHasTls = tlsCreatingStdMarshal.init();
237   MOZ_ASSERT(kHasTls);
238   Unused << kHasTls;
239 
240   MOZ_ASSERT(aSink);
241   RefPtr<IWeakReference> weakRef;
242   if (SUCCEEDED(GetWeakReference(getter_AddRefs(weakRef)))) {
243     aSink->SetInterceptor(weakRef);
244   }
245 }
246 
~Interceptor()247 Interceptor::~Interceptor() {
248   {  // Scope for lock
249     detail::LiveSetAutoLock lock(GetLiveSet());
250     GetLiveSet().Remove(mTarget.get());
251   }
252 
253   // This needs to run on the main thread because it releases target interface
254   // reference counts which may not be thread-safe.
255   MOZ_ASSERT(NS_IsMainThread());
256   for (uint32_t index = 0, len = mInterceptorMap.Length(); index < len;
257        ++index) {
258     MapEntry& entry = mInterceptorMap[index];
259     entry.mInterceptor = nullptr;
260     entry.mTargetInterface->Release();
261   }
262 }
263 
264 HRESULT
GetClassForHandler(DWORD aDestContext,void * aDestContextPtr,CLSID * aHandlerClsid)265 Interceptor::GetClassForHandler(DWORD aDestContext, void* aDestContextPtr,
266                                 CLSID* aHandlerClsid) {
267   if (aDestContextPtr || !aHandlerClsid ||
268       aDestContext == MSHCTX_DIFFERENTMACHINE) {
269     return E_INVALIDARG;
270   }
271 
272   MOZ_ASSERT(mEventSink);
273   return mEventSink->GetHandler(WrapNotNull(aHandlerClsid));
274 }
275 
276 REFIID
MarshalAs(REFIID aIid) const277 Interceptor::MarshalAs(REFIID aIid) const {
278 #if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
279   return IsCallerExternalProcess() ? aIid : mEventSink->MarshalAs(aIid);
280 #else
281   return mEventSink->MarshalAs(aIid);
282 #endif  // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
283 }
284 
285 HRESULT
GetUnmarshalClass(REFIID riid,void * pv,DWORD dwDestContext,void * pvDestContext,DWORD mshlflags,CLSID * pCid)286 Interceptor::GetUnmarshalClass(REFIID riid, void* pv, DWORD dwDestContext,
287                                void* pvDestContext, DWORD mshlflags,
288                                CLSID* pCid) {
289   return mStdMarshal->GetUnmarshalClass(MarshalAs(riid), pv, dwDestContext,
290                                         pvDestContext, mshlflags, pCid);
291 }
292 
293 HRESULT
GetMarshalSizeMax(REFIID riid,void * pv,DWORD dwDestContext,void * pvDestContext,DWORD mshlflags,DWORD * pSize)294 Interceptor::GetMarshalSizeMax(REFIID riid, void* pv, DWORD dwDestContext,
295                                void* pvDestContext, DWORD mshlflags,
296                                DWORD* pSize) {
297   detail::ReentrySentinel sentinel(this);
298 
299   HRESULT hr = mStdMarshal->GetMarshalSizeMax(
300       MarshalAs(riid), pv, dwDestContext, pvDestContext, mshlflags, pSize);
301   if (FAILED(hr) || !sentinel.IsOutermost()) {
302     return hr;
303   }
304 
305 #if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
306   if (XRE_IsContentProcess() && IsCallerExternalProcess()) {
307     // The caller isn't our chrome process, so we do not provide a handler
308     // payload. Even though we're only getting the size here, calculating the
309     // payload size might actually require building the payload.
310     return hr;
311   }
312 #endif  // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
313 
314   DWORD payloadSize = 0;
315   hr = mEventSink->GetHandlerPayloadSize(WrapNotNull(this),
316                                          WrapNotNull(&payloadSize));
317   if (hr == E_NOTIMPL) {
318     return S_OK;
319   }
320 
321   if (SUCCEEDED(hr)) {
322     *pSize += payloadSize;
323   }
324   return hr;
325 }
326 
327 HRESULT
MarshalInterface(IStream * pStm,REFIID riid,void * pv,DWORD dwDestContext,void * pvDestContext,DWORD mshlflags)328 Interceptor::MarshalInterface(IStream* pStm, REFIID riid, void* pv,
329                               DWORD dwDestContext, void* pvDestContext,
330                               DWORD mshlflags) {
331   detail::ReentrySentinel sentinel(this);
332 
333   HRESULT hr;
334 
335 #if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
336   // Save the current stream position
337   LARGE_INTEGER seekTo;
338   seekTo.QuadPart = 0;
339 
340   ULARGE_INTEGER objrefPos;
341 
342   hr = pStm->Seek(seekTo, STREAM_SEEK_CUR, &objrefPos);
343   if (FAILED(hr)) {
344     return hr;
345   }
346 
347 #endif  // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
348 
349   hr = mStdMarshal->MarshalInterface(pStm, MarshalAs(riid), pv, dwDestContext,
350                                      pvDestContext, mshlflags);
351   if (FAILED(hr) || !sentinel.IsOutermost()) {
352     return hr;
353   }
354 
355 #if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
356   if (XRE_IsContentProcess() && IsCallerExternalProcess()) {
357     // The caller isn't our chrome process, so do not provide a handler.
358 
359     // First, save the current position that marks the current end of the
360     // OBJREF in the stream.
361     ULARGE_INTEGER endPos;
362     hr = pStm->Seek(seekTo, STREAM_SEEK_CUR, &endPos);
363     if (FAILED(hr)) {
364       return hr;
365     }
366 
367     // Now strip out the handler.
368     if (!StripHandlerFromOBJREF(WrapNotNull(pStm), objrefPos.QuadPart,
369                                 endPos.QuadPart)) {
370       return E_FAIL;
371     }
372 
373     return S_OK;
374   }
375 #endif  // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
376 
377   hr = mEventSink->WriteHandlerPayload(WrapNotNull(this), WrapNotNull(pStm));
378   if (hr == E_NOTIMPL) {
379     return S_OK;
380   }
381 
382   return hr;
383 }
384 
385 HRESULT
UnmarshalInterface(IStream * pStm,REFIID riid,void ** ppv)386 Interceptor::UnmarshalInterface(IStream* pStm, REFIID riid, void** ppv) {
387   return mStdMarshal->UnmarshalInterface(pStm, riid, ppv);
388 }
389 
390 HRESULT
ReleaseMarshalData(IStream * pStm)391 Interceptor::ReleaseMarshalData(IStream* pStm) {
392   return mStdMarshal->ReleaseMarshalData(pStm);
393 }
394 
395 HRESULT
DisconnectObject(DWORD dwReserved)396 Interceptor::DisconnectObject(DWORD dwReserved) {
397   mEventSink->DisconnectHandlerRemotes();
398   return mStdMarshal->DisconnectObject(dwReserved);
399 }
400 
Lookup(REFIID aIid)401 Interceptor::MapEntry* Interceptor::Lookup(REFIID aIid) {
402   mInterceptorMapMutex.AssertCurrentThreadOwns();
403 
404   for (uint32_t index = 0, len = mInterceptorMap.Length(); index < len;
405        ++index) {
406     if (mInterceptorMap[index].mIID == aIid) {
407       return &mInterceptorMap[index];
408     }
409   }
410   return nullptr;
411 }
412 
413 HRESULT
GetTargetForIID(REFIID aIid,InterceptorTargetPtr<IUnknown> & aTarget)414 Interceptor::GetTargetForIID(REFIID aIid,
415                              InterceptorTargetPtr<IUnknown>& aTarget) {
416   MutexAutoLock lock(mInterceptorMapMutex);
417   MapEntry* entry = Lookup(aIid);
418   if (entry) {
419     aTarget.reset(entry->mTargetInterface);
420     return S_OK;
421   }
422 
423   return E_NOINTERFACE;
424 }
425 
426 // CoGetInterceptor requires type metadata to be able to generate its emulated
427 // vtable. If no registered metadata is available, CoGetInterceptor returns
428 // kFileNotFound.
429 static const HRESULT kFileNotFound = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
430 
431 HRESULT
CreateInterceptor(REFIID aIid,IUnknown * aOuter,IUnknown ** aOutput)432 Interceptor::CreateInterceptor(REFIID aIid, IUnknown* aOuter,
433                                IUnknown** aOutput) {
434   // In order to aggregate, we *must* request IID_IUnknown as the initial
435   // interface for the interceptor, as that IUnknown is non-delegating.
436   // This is a fundamental rule for creating aggregated objects in COM.
437   HRESULT hr = ::CoGetInterceptor(aIid, aOuter, IID_IUnknown, (void**)aOutput);
438   if (hr != kFileNotFound) {
439     return hr;
440   }
441 
442   // In the case that CoGetInterceptor returns kFileNotFound, we can try to
443   // explicitly load typelib data from our runtime registration facility and
444   // pass that into CoGetInterceptorFromTypeInfo.
445 
446   RefPtr<ITypeInfo> typeInfo;
447   bool found = RegisteredProxy::Find(aIid, getter_AddRefs(typeInfo));
448   // If this assert fires then we have omitted registering the typelib for a
449   // required interface. To fix this, review our calls to mscom::RegisterProxy
450   // and mscom::RegisterTypelib, and add the additional typelib as necessary.
451   MOZ_ASSERT(found);
452   if (!found) {
453     return kFileNotFound;
454   }
455 
456   hr = ::CoGetInterceptorFromTypeInfo(aIid, aOuter, typeInfo, IID_IUnknown,
457                                       (void**)aOutput);
458   // If this assert fires then the interceptor doesn't like something about
459   // the format of the typelib. One thing in particular that it doesn't like
460   // is complex types that contain unions.
461   MOZ_ASSERT(SUCCEEDED(hr));
462   return hr;
463 }
464 
465 HRESULT
PublishTarget(detail::LiveSetAutoLock & aLiveSetLock,RefPtr<IUnknown> aInterceptor,REFIID aTargetIid,STAUniquePtr<IUnknown> aTarget)466 Interceptor::PublishTarget(detail::LiveSetAutoLock& aLiveSetLock,
467                            RefPtr<IUnknown> aInterceptor, REFIID aTargetIid,
468                            STAUniquePtr<IUnknown> aTarget) {
469   RefPtr<IWeakReference> weakRef;
470   HRESULT hr = GetWeakReference(getter_AddRefs(weakRef));
471   if (FAILED(hr)) {
472     return hr;
473   }
474 
475   // mTarget is a weak reference to aTarget. This is safe because we transfer
476   // ownership of aTarget into mInterceptorMap which remains live for the
477   // lifetime of this Interceptor.
478   mTarget = ToInterceptorTargetPtr(aTarget);
479   GetLiveSet().Put(mTarget.get(), weakRef.forget());
480 
481   // Now we transfer aTarget's ownership into mInterceptorMap.
482   mInterceptorMap.AppendElement(
483       MapEntry(aTargetIid, aInterceptor, aTarget.release()));
484 
485   // Release the live set lock because subsequent operations may post work to
486   // the main thread, creating potential for deadlocks.
487   aLiveSetLock.Unlock();
488   return S_OK;
489 }
490 
491 HRESULT
GetInitialInterceptorForIID(detail::LiveSetAutoLock & aLiveSetLock,REFIID aTargetIid,STAUniquePtr<IUnknown> aTarget,void ** aOutInterceptor)492 Interceptor::GetInitialInterceptorForIID(detail::LiveSetAutoLock& aLiveSetLock,
493                                          REFIID aTargetIid,
494                                          STAUniquePtr<IUnknown> aTarget,
495                                          void** aOutInterceptor) {
496   MOZ_ASSERT(aOutInterceptor);
497   MOZ_ASSERT(aTargetIid != IID_IMarshal);
498   MOZ_ASSERT(!IsProxy(aTarget.get()));
499 
500   HRESULT hr = E_UNEXPECTED;
501 
502   auto hasFailed = [&hr]() -> bool { return FAILED(hr); };
503 
504   auto cleanup = [&aLiveSetLock]() -> void { aLiveSetLock.Unlock(); };
505 
506   ExecuteWhen<decltype(hasFailed), decltype(cleanup)> onFail(hasFailed,
507                                                              cleanup);
508 
509   if (aTargetIid == IID_IUnknown) {
510     // We must lock mInterceptorMapMutex so that nothing can race with us once
511     // we have been published to the live set.
512     MutexAutoLock lock(mInterceptorMapMutex);
513 
514     hr = PublishTarget(aLiveSetLock, nullptr, aTargetIid, std::move(aTarget));
515     ENSURE_HR_SUCCEEDED(hr);
516 
517     hr = QueryInterface(aTargetIid, aOutInterceptor);
518     ENSURE_HR_SUCCEEDED(hr);
519     return hr;
520   }
521 
522   // Raise the refcount for stabilization purposes during aggregation
523   WeakReferenceSupport::StabilizeRefCount stabilizer(*this);
524 
525   RefPtr<IUnknown> unkInterceptor;
526   hr = CreateInterceptor(aTargetIid, static_cast<WeakReferenceSupport*>(this),
527                          getter_AddRefs(unkInterceptor));
528   ENSURE_HR_SUCCEEDED(hr);
529 
530   RefPtr<ICallInterceptor> interceptor;
531   hr = unkInterceptor->QueryInterface(IID_ICallInterceptor,
532                                       getter_AddRefs(interceptor));
533   ENSURE_HR_SUCCEEDED(hr);
534 
535   hr = interceptor->RegisterSink(mEventSink);
536   ENSURE_HR_SUCCEEDED(hr);
537 
538   // We must lock mInterceptorMapMutex so that nothing can race with us once we
539   // have been published to the live set.
540   MutexAutoLock lock(mInterceptorMapMutex);
541 
542   hr = PublishTarget(aLiveSetLock, unkInterceptor, aTargetIid,
543                      std::move(aTarget));
544   ENSURE_HR_SUCCEEDED(hr);
545 
546   if (MarshalAs(aTargetIid) == aTargetIid) {
547     hr = unkInterceptor->QueryInterface(aTargetIid, aOutInterceptor);
548     ENSURE_HR_SUCCEEDED(hr);
549     return hr;
550   }
551 
552   hr = GetInterceptorForIID(aTargetIid, aOutInterceptor, &lock);
553   ENSURE_HR_SUCCEEDED(hr);
554   return hr;
555 }
556 
557 HRESULT
GetInterceptorForIID(REFIID aIid,void ** aOutInterceptor)558 Interceptor::GetInterceptorForIID(REFIID aIid, void** aOutInterceptor) {
559   return GetInterceptorForIID(aIid, aOutInterceptor, nullptr);
560 }
561 
562 /**
563  * This method contains the core guts of the handling of QueryInterface calls
564  * that are delegated to us from the ICallInterceptor.
565  *
566  * @param aIid ID of the desired interface
567  * @param aOutInterceptor The resulting emulated vtable that corresponds to
568  * the interface specified by aIid.
569  * @param aAlreadyLocked Proof of an existing lock on |mInterceptorMapMutex|,
570  *                       if present.
571  */
572 HRESULT
GetInterceptorForIID(REFIID aIid,void ** aOutInterceptor,MutexAutoLock * aAlreadyLocked)573 Interceptor::GetInterceptorForIID(REFIID aIid, void** aOutInterceptor,
574                                   MutexAutoLock* aAlreadyLocked) {
575   detail::LoggedQIResult result(aIid);
576 
577   if (!aOutInterceptor) {
578     return E_INVALIDARG;
579   }
580 
581   if (aIid == IID_IUnknown) {
582     // Special case: When we see IUnknown, we just provide a reference to this
583     RefPtr<IInterceptor> intcpt(this);
584     intcpt.forget(aOutInterceptor);
585     return S_OK;
586   }
587 
588   REFIID interceptorIid = MarshalAs(aIid);
589 
590   RefPtr<IUnknown> unkInterceptor;
591   IUnknown* interfaceForQILog = nullptr;
592 
593   // (1) Check to see if we already have an existing interceptor for
594   // interceptorIid.
595   auto doLookup = [&]() -> void {
596     MapEntry* entry = Lookup(interceptorIid);
597     if (entry) {
598       unkInterceptor = entry->mInterceptor;
599       interfaceForQILog = entry->mTargetInterface;
600     }
601   };
602 
603   if (aAlreadyLocked) {
604     doLookup();
605   } else {
606     MutexAutoLock lock(mInterceptorMapMutex);
607     doLookup();
608   }
609 
610   // (1a) A COM interceptor already exists for this interface, so all we need
611   // to do is run a QI on it.
612   if (unkInterceptor) {
613     // Technically we didn't actually execute a QI on the target interface, but
614     // for logging purposes we would like to record the fact that this interface
615     // was requested.
616     result.Log(mTarget.get(), interfaceForQILog);
617     result = unkInterceptor->QueryInterface(interceptorIid, aOutInterceptor);
618     ENSURE_HR_SUCCEEDED(result);
619     return result;
620   }
621 
622   // (2) Obtain a new target interface.
623 
624   // (2a) First, make sure that the target interface is available
625   // NB: We *MUST* query the correct interface! ICallEvents::Invoke casts its
626   // pvReceiver argument directly to the required interface! DO NOT assume
627   // that COM will use QI or upcast/downcast!
628   HRESULT hr;
629 
630   STAUniquePtr<IUnknown> targetInterface;
631   IUnknown* rawTargetInterface = nullptr;
632   hr =
633       QueryInterfaceTarget(interceptorIid, (void**)&rawTargetInterface, result);
634   targetInterface.reset(rawTargetInterface);
635   result = hr;
636   result.Log(mTarget.get(), targetInterface.get());
637   MOZ_ASSERT(SUCCEEDED(hr) || hr == E_NOINTERFACE);
638   if (hr == E_NOINTERFACE) {
639     return hr;
640   }
641   ENSURE_HR_SUCCEEDED(hr);
642 
643   // We *really* shouldn't be adding interceptors to proxies
644   MOZ_ASSERT(aIid != IID_IMarshal);
645 
646   // (3) Create a new COM interceptor to that interface that delegates its
647   // IUnknown to |this|.
648 
649   // Raise the refcount for stabilization purposes during aggregation
650   WeakReferenceSupport::StabilizeRefCount stabilizer(*this);
651 
652   hr = CreateInterceptor(interceptorIid,
653                          static_cast<WeakReferenceSupport*>(this),
654                          getter_AddRefs(unkInterceptor));
655   ENSURE_HR_SUCCEEDED(hr);
656 
657   // (4) Obtain the interceptor's ICallInterceptor interface and register our
658   // event sink.
659   RefPtr<ICallInterceptor> interceptor;
660   hr = unkInterceptor->QueryInterface(IID_ICallInterceptor,
661                                       (void**)getter_AddRefs(interceptor));
662   ENSURE_HR_SUCCEEDED(hr);
663 
664   hr = interceptor->RegisterSink(mEventSink);
665   ENSURE_HR_SUCCEEDED(hr);
666 
667   // (5) Now that we have this new COM interceptor, insert it into the map.
668   auto doInsertion = [&]() -> void {
669     // We might have raced with another thread, so first check that we don't
670     // already have an entry for this
671     MapEntry* entry = Lookup(interceptorIid);
672     if (entry && entry->mInterceptor) {
673       // Bug 1433046: Because of aggregation, the QI for |interceptor|
674       // AddRefed |this|, not |unkInterceptor|. Thus, releasing |unkInterceptor|
675       // will destroy the object. Before we do that, we must first release
676       // |interceptor|. Otherwise, |interceptor| would be invalidated when
677       // |unkInterceptor| is destroyed.
678       interceptor = nullptr;
679       unkInterceptor = entry->mInterceptor;
680     } else {
681       // MapEntry has a RefPtr to unkInterceptor, OTOH we must not touch the
682       // refcount for the target interface because we are just moving it into
683       // the map and its refcounting might not be thread-safe.
684       IUnknown* rawTargetInterface = targetInterface.release();
685       mInterceptorMap.AppendElement(
686           MapEntry(interceptorIid, unkInterceptor, rawTargetInterface));
687     }
688   };
689 
690   if (aAlreadyLocked) {
691     doInsertion();
692   } else {
693     MutexAutoLock lock(mInterceptorMapMutex);
694     doInsertion();
695   }
696 
697   hr = unkInterceptor->QueryInterface(interceptorIid, aOutInterceptor);
698   ENSURE_HR_SUCCEEDED(hr);
699   return hr;
700 }
701 
702 HRESULT
QueryInterfaceTarget(REFIID aIid,void ** aOutput,TimeDuration * aOutDuration)703 Interceptor::QueryInterfaceTarget(REFIID aIid, void** aOutput,
704                                   TimeDuration* aOutDuration) {
705   // NB: This QI needs to run on the main thread because the target object
706   // is probably Gecko code that is not thread-safe. Note that this main
707   // thread invocation is *synchronous*.
708   if (!NS_IsMainThread() && tlsCreatingStdMarshal.get()) {
709     mStdMarshalMutex.AssertCurrentThreadOwns();
710     // COM queries for special interfaces such as IFastRundown when creating a
711     // marshaler. We don't want these being dispatched to the main thread,
712     // since this would cause a deadlock on mStdMarshalMutex if the main
713     // thread is also querying for IMarshal. If we do need to respond to these
714     // special interfaces, this should be done before this point; e.g. in
715     // Interceptor::QueryInterface like we do for INoMarshal.
716     return E_NOINTERFACE;
717   }
718 
719   if (mEventSink->IsInterfaceMaybeSupported(aIid) == E_NOINTERFACE) {
720     return E_NOINTERFACE;
721   }
722 
723   MainThreadInvoker invoker;
724   HRESULT hr;
725   auto runOnMainThread = [&]() -> void {
726     MOZ_ASSERT(NS_IsMainThread());
727     hr = mTarget->QueryInterface(aIid, aOutput);
728   };
729   if (!invoker.Invoke(NS_NewRunnableFunction("Interceptor::QueryInterface",
730                                              runOnMainThread))) {
731     return E_FAIL;
732   }
733   if (aOutDuration) {
734     *aOutDuration = invoker.GetDuration();
735   }
736   return hr;
737 }
738 
739 HRESULT
QueryInterface(REFIID riid,void ** ppv)740 Interceptor::QueryInterface(REFIID riid, void** ppv) {
741   if (riid == IID_INoMarshal) {
742     // This entire library is designed around marshaling, so there's no point
743     // propagating this QI request all over the place!
744     return E_NOINTERFACE;
745   }
746 
747   return WeakReferenceSupport::QueryInterface(riid, ppv);
748 }
749 
750 HRESULT
WeakRefQueryInterface(REFIID aIid,IUnknown ** aOutInterface)751 Interceptor::WeakRefQueryInterface(REFIID aIid, IUnknown** aOutInterface) {
752   if (aIid == IID_IStdMarshalInfo) {
753     detail::ReentrySentinel sentinel(this);
754 
755     if (!sentinel.IsOutermost()) {
756       return E_NOINTERFACE;
757     }
758 
759     // Do not indicate that this interface is available unless we actually
760     // support it. We'll check that by looking for a successful call to
761     // IInterceptorSink::GetHandler()
762     CLSID dummy;
763     if (FAILED(mEventSink->GetHandler(WrapNotNull(&dummy)))) {
764       return E_NOINTERFACE;
765     }
766 
767     RefPtr<IStdMarshalInfo> std(this);
768     std.forget(aOutInterface);
769     return S_OK;
770   }
771 
772   if (aIid == IID_IMarshal) {
773     MutexAutoLock lock(mStdMarshalMutex);
774 
775     HRESULT hr;
776 
777     if (!mStdMarshalUnk) {
778       MOZ_ASSERT(!tlsCreatingStdMarshal.get());
779       tlsCreatingStdMarshal.set(true);
780       if (XRE_IsContentProcess()) {
781         hr = FastMarshaler::Create(static_cast<IWeakReferenceSource*>(this),
782                                    getter_AddRefs(mStdMarshalUnk));
783       } else {
784         hr = ::CoGetStdMarshalEx(static_cast<IWeakReferenceSource*>(this),
785                                  SMEXF_SERVER, getter_AddRefs(mStdMarshalUnk));
786       }
787       tlsCreatingStdMarshal.set(false);
788 
789       ENSURE_HR_SUCCEEDED(hr);
790     }
791 
792     if (!mStdMarshal) {
793       hr = mStdMarshalUnk->QueryInterface(IID_IMarshal, (void**)&mStdMarshal);
794       ENSURE_HR_SUCCEEDED(hr);
795 
796       // mStdMarshal is weak, so drop its refcount
797       mStdMarshal->Release();
798     }
799 
800     RefPtr<IMarshal> marshal(this);
801     marshal.forget(aOutInterface);
802     return S_OK;
803   }
804 
805   if (aIid == IID_IInterceptor) {
806     RefPtr<IInterceptor> intcpt(this);
807     intcpt.forget(aOutInterface);
808     return S_OK;
809   }
810 
811   if (aIid == IID_IDispatch) {
812     STAUniquePtr<IDispatch> disp;
813     IDispatch* rawDisp = nullptr;
814     HRESULT hr = QueryInterfaceTarget(aIid, (void**)&rawDisp);
815     ENSURE_HR_SUCCEEDED(hr);
816 
817     disp.reset(rawDisp);
818     return DispatchForwarder::Create(this, disp, aOutInterface);
819   }
820 
821   return GetInterceptorForIID(aIid, (void**)aOutInterface, nullptr);
822 }
823 
824 ULONG
AddRef()825 Interceptor::AddRef() { return WeakReferenceSupport::AddRef(); }
826 
827 ULONG
Release()828 Interceptor::Release() { return WeakReferenceSupport::Release(); }
829 
830 /* static */
DisconnectRemotesForTarget(IUnknown * aTarget)831 HRESULT Interceptor::DisconnectRemotesForTarget(IUnknown* aTarget) {
832   MOZ_ASSERT(aTarget);
833 
834   detail::LiveSetAutoLock lock(GetLiveSet());
835 
836   // It is not an error if the interceptor doesn't exist, so we return
837   // S_FALSE instead of an error in that case.
838   RefPtr<IWeakReference> existingWeak(GetLiveSet().Get(aTarget));
839   if (!existingWeak) {
840     return S_FALSE;
841   }
842 
843   RefPtr<IWeakReferenceSource> existingStrong;
844   if (FAILED(existingWeak->ToStrongRef(getter_AddRefs(existingStrong)))) {
845     return S_FALSE;
846   }
847   // Since we now hold a strong ref on the interceptor, we may now release the
848   // lock.
849   lock.Unlock();
850 
851   return ::CoDisconnectObject(existingStrong, 0);
852 }
853 
854 }  // namespace mscom
855 }  // namespace mozilla
856