Bug 1379643: When running under sandbox level >= 3, parent should retain IStream of marshaled interface to be destroyed later; r=jimm
authorAaron Klotz <aklotz@mozilla.com>
Wed, 19 Jul 2017 12:07:45 -0600
changeset 418451 cf1afed54d07d18524b35395d427cc840caf5019
parent 418450 24dc2160940436a8d5bfcf2a591c7936df0f00ee
child 418452 57400dd449ad5dd77f88054e06c647023eaa3866
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimm
bugs1379643
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1379643: When running under sandbox level >= 3, parent should retain IStream of marshaled interface to be destroyed later; r=jimm MozReview-Commit-ID: Egb6Yahdbxm
accessible/ipc/DocAccessibleParent.cpp
accessible/ipc/DocAccessibleParent.h
ipc/mscom/COMPtrHolder.h
ipc/mscom/EnsureMTA.h
ipc/mscom/ProxyStream.cpp
ipc/mscom/ProxyStream.h
ipc/mscom/Ptr.h
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -669,17 +669,23 @@ DocAccessibleParent::SendParentCOMProxy(
   }
 
   IAccessible* rawNative = nullptr;
   outerDoc->GetNativeInterface((void**) &rawNative);
   MOZ_ASSERT(rawNative);
 
   IAccessibleHolder::COMPtrType ptr(rawNative);
   IAccessibleHolder holder(Move(ptr));
-  Unused << PDocAccessibleParent::SendParentCOMProxy(holder);
+  if (!PDocAccessibleParent::SendParentCOMProxy(holder)) {
+    return;
+  }
+
+#if defined(MOZ_CONTENT_SANDBOX)
+  mParentProxyStream = Move(holder.GetPreservedStream());
+#endif // defined(MOZ_CONTENT_SANDBOX)
 }
 
 void
 DocAccessibleParent::SetEmulatedWindowHandle(HWND aWindowHandle)
 {
   if (!aWindowHandle && mEmulatedWindowHandle && IsTopLevel()) {
     ::DestroyWindow(mEmulatedWindowHandle);
   }
--- a/accessible/ipc/DocAccessibleParent.h
+++ b/accessible/ipc/DocAccessibleParent.h
@@ -231,17 +231,21 @@ private:
   xpcAccessibleGeneric* GetXPCAccessible(ProxyAccessible* aProxy);
 
   nsTArray<uint64_t> mChildDocs;
   uint64_t mParentDoc;
 
 #if defined(XP_WIN)
   // The handle associated with the emulated window that contains this document
   HWND mEmulatedWindowHandle;
-#endif
+
+#if defined(MOZ_CONTENT_SANDBOX)
+  mscom::PreservedStreamPtr mParentProxyStream;
+#endif // defined(MOZ_CONTENT_SANDBOX)
+#endif // defined(XP_WIN)
 
   /*
    * Conceptually this is a map from IDs to proxies, but we store the ID in the
    * proxy object so we can't use a real map.
    */
   nsTHashtable<ProxyEntry> mAccessibles;
   uint64_t mActorID;
   bool mTopLevel;
--- a/ipc/mscom/COMPtrHolder.h
+++ b/ipc/mscom/COMPtrHolder.h
@@ -2,20 +2,25 @@
 /* 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 mozilla_mscom_COMPtrHolder_h
 #define mozilla_mscom_COMPtrHolder_h
 
+#include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
 #include "mozilla/Move.h"
 #include "mozilla/mscom/ProxyStream.h"
 #include "mozilla/mscom/Ptr.h"
+#if defined(MOZ_CONTENT_SANDBOX)
+#include "mozilla/SandboxSettings.h"
+#endif // defined(MOZ_CONTENT_SANDBOX)
 
 namespace mozilla {
 namespace mscom {
 
 template<typename Interface, const IID& _IID>
 class COMPtrHolder
 {
 public:
@@ -43,16 +48,32 @@ public:
     return mPtr.release();
   }
 
   void Set(COMPtrType&& aPtr)
   {
     mPtr = Forward<COMPtrType>(aPtr);
   }
 
+#if defined(MOZ_CONTENT_SANDBOX)
+  // This method is const because we need to call it during IPC write, where
+  // we are passed as a const argument. At higher sandboxing levels we need to
+  // save this artifact from the serialization process for later deletion.
+  void PreserveStream(RefPtr<IStream>&& aPtr) const
+  {
+    MOZ_ASSERT(!mMarshaledStream);
+    mMarshaledStream = ToPreservedStreamPtr(Move(aPtr));
+  }
+
+  PreservedStreamPtr GetPreservedStream()
+  {
+    return Move(mMarshaledStream);
+  }
+#endif // defined(MOZ_CONTENT_SANDBOX)
+
   COMPtrHolder(const COMPtrHolder& aOther) = delete;
 
   COMPtrHolder(COMPtrHolder&& aOther)
     : mPtr(Move(aOther.mPtr))
   {
   }
 
   // COMPtrHolder is eventually added as a member of a struct that is declared
@@ -82,16 +103,22 @@ public:
   bool IsNull() const
   {
     return !mPtr;
   }
 
 private:
   // This is mutable to facilitate the above operator= hack
   mutable COMPtrType mPtr;
+
+#if defined(MOZ_CONTENT_SANDBOX)
+  // This is mutable so that we may optionally store a reference to a marshaled
+  // stream to be cleaned up later via PreserveStream().
+  mutable PreservedStreamPtr mMarshaledStream;
+#endif // defined(MOZ_CONTENT_SANDBOX)
 };
 
 } // namespace mscom
 } // namespace mozilla
 
 namespace IPC {
 
 template<typename Interface, const IID& _IID>
@@ -104,16 +131,34 @@ struct ParamTraits<mozilla::mscom::COMPt
     mozilla::mscom::ProxyStream proxyStream(_IID, aParam.Get());
     int bufLen;
     const BYTE* buf = proxyStream.GetBuffer(bufLen);
     MOZ_ASSERT(buf || !bufLen);
     aMsg->WriteInt(bufLen);
     if (bufLen) {
       aMsg->WriteBytes(reinterpret_cast<const char*>(buf), bufLen);
     }
+
+#if defined(MOZ_CONTENT_SANDBOX)
+    if (XRE_IsParentProcess()) {
+      static const bool sIsStreamPreservationNeeded =
+        mozilla::GetEffectiveContentSandboxLevel() >= 3;
+      if (sIsStreamPreservationNeeded) {
+        /**
+         * When we're sending a ProxyStream from parent to content and the
+         * content sandboxing level is >= 3, content is unable to communicate
+         * its releasing of its reference to the proxied object. We preserve the
+         * marshaled proxy data here and later manually release it on content's
+         * behalf.
+         */
+        RefPtr<IStream> stream(proxyStream.GetStream());
+        aParam.PreserveStream(mozilla::Move(stream));
+      }
+    }
+#endif // defined(MOZ_CONTENT_SANDBOX)
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
   {
     int length;
     if (!aMsg->ReadLength(aIter, &length)) {
       return false;
     }
--- a/ipc/mscom/EnsureMTA.h
+++ b/ipc/mscom/EnsureMTA.h
@@ -29,16 +29,18 @@ template <typename T>
 struct MTADelete;
 
 template <typename T>
 struct MTARelease;
 
 template <typename T>
 struct MTAReleaseInChildProcess;
 
+struct PreservedStreamDeleter;
+
 }
 
 // This class is OK to use as a temporary on the stack.
 class MOZ_STACK_CLASS EnsureMTA final
 {
 public:
   /**
    * This constructor just ensures that the MTA thread is up and running.
@@ -129,15 +131,17 @@ private:
   template <typename T>
   friend struct mozilla::mscom::detail::MTADelete;
 
   template <typename T>
   friend struct mozilla::mscom::detail::MTARelease;
 
   template <typename T>
   friend struct mozilla::mscom::detail::MTAReleaseInChildProcess;
+
+  friend struct mozilla::mscom::detail::PreservedStreamDeleter;
 };
 
 } // namespace mscom
 } // namespace mozilla
 
 #endif // mozilla_mscom_EnsureMTA_h
 
--- a/ipc/mscom/ProxyStream.cpp
+++ b/ipc/mscom/ProxyStream.cpp
@@ -184,16 +184,36 @@ ProxyStream::GetBuffer(int& aReturnedBuf
   }
   if (!mGlobalLockedBuf) {
     return nullptr;
   }
   aReturnedBufSize = mBufSize;
   return mGlobalLockedBuf;
 }
 
+RefPtr<IStream>
+ProxyStream::GetStream() const
+{
+  MOZ_ASSERT(mStream);
+  MOZ_ASSERT(mHGlobal);
+
+  if (!mStream) {
+    return nullptr;
+  }
+
+  // Ensure the stream is rewound. We do this because CoReleaseMarshalData needs
+  // the stream to be pointing to the beginning of the marshal data.
+  LARGE_INTEGER pos;
+  pos.QuadPart = 0LL;
+  DebugOnly<HRESULT> hr = mStream->Seek(pos, STREAM_SEEK_SET, nullptr);
+  MOZ_ASSERT(SUCCEEDED(hr));
+
+  return mStream;
+}
+
 bool
 ProxyStream::GetInterface(void** aOutInterface)
 {
   // We should not have a locked buffer on this side
   MOZ_ASSERT(!mGlobalLockedBuf);
   MOZ_ASSERT(aOutInterface);
 
   if (!aOutInterface) {
--- a/ipc/mscom/ProxyStream.h
+++ b/ipc/mscom/ProxyStream.h
@@ -34,16 +34,17 @@ public:
 
   inline bool IsValid() const
   {
     return !(mStream && mUnmarshaledProxy);
   }
 
   bool GetInterface(void** aOutInterface);
   const BYTE* GetBuffer(int& aReturnedBufSize) const;
+  RefPtr<IStream> GetStream() const;
 
   bool operator==(const ProxyStream& aOther) const
   {
     return this == &aOther;
   }
 
 private:
   static already_AddRefed<IStream> InitStream(const BYTE* aInitBuf,
--- a/ipc/mscom/Ptr.h
+++ b/ipc/mscom/Ptr.h
@@ -108,16 +108,44 @@ struct MTAReleaseInChildProcess
 struct InterceptorTargetDeleter
 {
   void operator()(IUnknown* aPtr)
   {
     // We intentionally do not touch the refcounts of interceptor targets!
   }
 };
 
+struct PreservedStreamDeleter
+{
+  void operator()(IStream* aPtr)
+  {
+    if (!aPtr) {
+      return;
+    }
+
+    // Static analysis doesn't recognize that, even though aPtr escapes the
+    // current scope, we are in effect moving our strong ref into the lambda.
+    void* ptr = aPtr;
+    auto cleanup = [ptr]() -> void {
+      DebugOnly<HRESULT> hr =
+        ::CoReleaseMarshalData(reinterpret_cast<LPSTREAM>(ptr));
+      MOZ_ASSERT(SUCCEEDED(hr));
+      reinterpret_cast<LPSTREAM>(ptr)->Release();
+    };
+
+    if (XRE_IsParentProcess()) {
+      MOZ_ASSERT(NS_IsMainThread());
+      cleanup();
+      return;
+    }
+
+    EnsureMTA::AsyncOperation(cleanup);
+  }
+};
+
 } // namespace detail
 
 template <typename T>
 using STAUniquePtr = mozilla::UniquePtr<T, detail::MainThreadRelease<T>>;
 
 template <typename T>
 using MTAUniquePtr = mozilla::UniquePtr<T, detail::MTARelease<T>>;
 
@@ -126,16 +154,19 @@ using MTADeletePtr = mozilla::UniquePtr<
 
 template <typename T>
 using ProxyUniquePtr = mozilla::UniquePtr<T, detail::MTAReleaseInChildProcess<T>>;
 
 template <typename T>
 using InterceptorTargetPtr =
   mozilla::UniquePtr<T, detail::InterceptorTargetDeleter>;
 
+using PreservedStreamPtr =
+  mozilla::UniquePtr<IStream, detail::PreservedStreamDeleter>;
+
 namespace detail {
 
 // We don't have direct access to UniquePtr's storage, so we use mPtrStorage
 // to receive the pointer and then set the target inside the destructor.
 template <typename T, typename Deleter>
 class UniquePtrGetterAddRefs
 {
 public:
@@ -267,16 +298,22 @@ ToProxyUniquePtr(T* aRawPtr)
 
 template <typename T, typename Deleter>
 inline InterceptorTargetPtr<T>
 ToInterceptorTargetPtr(const UniquePtr<T, Deleter>& aTargetPtr)
 {
   return InterceptorTargetPtr<T>(aTargetPtr.get());
 }
 
+inline PreservedStreamPtr
+ToPreservedStreamPtr(RefPtr<IStream>&& aStream)
+{
+  return PreservedStreamPtr(aStream.forget().take());
+}
+
 template <typename T, typename Deleter>
 inline detail::UniquePtrGetterAddRefs<T, Deleter>
 getter_AddRefs(UniquePtr<T, Deleter>& aSmartPtr)
 {
   return detail::UniquePtrGetterAddRefs<T, Deleter>(aSmartPtr);
 }
 
 } // namespace mscom
@@ -305,12 +342,18 @@ struct SmartPointerStorageClass<mozilla:
 };
 
 template<typename T>
 struct SmartPointerStorageClass<mozilla::mscom::InterceptorTargetPtr<T>>
 {
   typedef StoreCopyPassByRRef<mozilla::mscom::InterceptorTargetPtr<T>> Type;
 };
 
+template<>
+struct SmartPointerStorageClass<mozilla::mscom::PreservedStreamPtr>
+{
+  typedef StoreCopyPassByRRef<mozilla::mscom::PreservedStreamPtr> Type;
+};
+
 } // namespace detail
 
 #endif // mozilla_mscom_Ptr_h