Bug 1284897 - Hook GetSaveFileNameW/GetOpenFileNameW to record and grant a sandboxed process permission to access user-chosen files. r=jimm
authorDavid Parks <dparks@mozilla.com>
Wed, 08 Feb 2017 11:38:40 -0800
changeset 373431 8804a3f8fb1589b244b14528cca8d3ec5a331c68
parent 373430 6ecd19d2582250b42ff890660eb33109c3bf062c
child 373432 6b718178f43f0f80ad1c8c36540d4e5112c0cc05
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimm
bugs1284897
milestone54.0a1
Bug 1284897 - Hook GetSaveFileNameW/GetOpenFileNameW to record and grant a sandboxed process permission to access user-chosen files. r=jimm
dom/plugins/ipc/PPluginModule.ipdl
dom/plugins/ipc/PluginMessageUtils.cpp
dom/plugins/ipc/PluginMessageUtils.h
dom/plugins/ipc/PluginModuleChild.cpp
dom/plugins/ipc/PluginModuleParent.cpp
dom/plugins/ipc/PluginModuleParent.h
dom/plugins/ipc/PluginProcessParent.cpp
dom/plugins/ipc/moz.build
ipc/ipdl/sync-messages.ini
--- a/dom/plugins/ipc/PPluginModule.ipdl
+++ b/dom/plugins/ipc/PPluginModule.ipdl
@@ -9,16 +9,19 @@ include protocol PContent;
 include ProfilerTypes;
 
 using NPError from "npapi.h";
 using NPNVariable from "npapi.h";
 using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
 using class mac_plugin_interposing::NSCursorInfo from "mozilla/plugins/PluginMessageUtils.h";
 using struct nsID from "nsID.h";
 using struct mozilla::plugins::NPAudioDeviceChangeDetailsIPC from "mozilla/plugins/PluginMessageUtils.h";
+using mozilla::plugins::GetFileNameFunc from "mozilla/plugins/PluginMessageUtils.h";
+using mozilla::plugins::OpenFileNameIPC from "mozilla/plugins/PluginMessageUtils.h";
+using mozilla::plugins::OpenFileNameRetIPC from "mozilla/plugins/PluginMessageUtils.h";
 
 namespace mozilla {
 namespace plugins {
 
 struct PluginSettings
 {
   // These settings correspond to NPNVariable. They are fetched from
   // mozilla::plugins::parent::_getvalue.
@@ -158,12 +161,16 @@ parent:
 
   async ReturnSitesWithData(nsCString[] aSites, uint64_t aCallbackId);
 
   intr GetKeyState(int32_t aVirtKey)
     returns (int16_t aState);
 
   intr NPN_SetValue_NPPVpluginRequiresAudioDeviceChanges(bool shouldRegister)
     returns (NPError result);
+
+  // Used to broker the GetOpenFileName/GetSaveFileName file pickers on Windows.
+  intr GetFileName(GetFileNameFunc aFunc, OpenFileNameIPC aOfnIn)
+    returns (OpenFileNameRetIPC aOfnOut, bool aResult);
 };
 
 } // namespace plugins
 } // namespace mozilla
--- a/dom/plugins/ipc/PluginMessageUtils.cpp
+++ b/dom/plugins/ipc/PluginMessageUtils.cpp
@@ -146,10 +146,197 @@ void DeferNPVariantLastRelease(const NPN
   if (!NPVARIANT_IS_OBJECT(*v)) {
     f->releasevariantvalue(v);
     return;
   }
   DeferNPObjectLastRelease(f, v->value.objectValue);
   VOID_TO_NPVARIANT(*v);
 }
 
+#ifdef XP_WIN
+void
+OpenFileNameIPC::CopyFromOfn(LPOPENFILENAMEW aLpofn)
+{
+  mHwndOwner = nullptr;
+
+  // Filter is double-NULL terminated.  mFilter should include the double-NULL.
+  mHasFilter = aLpofn->lpstrFilter != nullptr;
+  if (mHasFilter) {
+    uint32_t dNullIdx = 0;
+    while (aLpofn->lpstrFilter[dNullIdx] != L'\0' ||
+           aLpofn->lpstrFilter[dNullIdx+1] != L'\0') {
+      dNullIdx++;
+    }
+    mFilter.assign(aLpofn->lpstrFilter, dNullIdx+2);
+  }
+  mHasCustomFilter = aLpofn->lpstrCustomFilter != nullptr;
+  if (mHasCustomFilter) {
+    mCustomFilterIn = std::wstring(aLpofn->lpstrCustomFilter);
+    mNMaxCustFilterOut =
+      aLpofn->nMaxCustFilter - (wcslen(aLpofn->lpstrCustomFilter) + 1);
+  }
+  else {
+    mNMaxCustFilterOut = 0;
+  }
+  mFilterIndex = aLpofn->nFilterIndex;
+  mFile = std::wstring(aLpofn->lpstrFile);
+  mNMaxFile = aLpofn->nMaxFile;
+  mNMaxFileTitle =
+    aLpofn->lpstrFileTitle != nullptr ? aLpofn->nMaxFileTitle : 0;
+  mHasInitialDir = aLpofn->lpstrInitialDir != nullptr;
+  if (mHasInitialDir) {
+    mInitialDir = std::wstring(aLpofn->lpstrInitialDir);
+  }
+  mHasTitle = aLpofn->lpstrTitle != nullptr;
+  if (mHasTitle) {
+    mTitle = std::wstring(aLpofn->lpstrTitle);
+  }
+  mHasDefExt = aLpofn->lpstrDefExt != nullptr;
+  if (mHasDefExt) {
+    mDefExt = std::wstring(aLpofn->lpstrDefExt);
+  }
+
+  mFlags = aLpofn->Flags;
+  // If the user sets OFN_ALLOWMULTISELECT then we require OFN_EXPLORER
+  // as well.  Without OFN_EXPLORER, the method has ancient legacy
+  // behavior that we don't support.
+  MOZ_ASSERT((mFlags & OFN_EXPLORER) || !(mFlags & OFN_ALLOWMULTISELECT));
+
+  // We ignore any visual customization and callbacks that the user set.
+  mFlags &= ~(OFN_ENABLEHOOK | OFN_ENABLETEMPLATEHANDLE | OFN_ENABLETEMPLATE);
+
+  mFlagsEx = aLpofn->FlagsEx;
+}
+
+void
+OpenFileNameIPC::AddToOfn(LPOPENFILENAMEW aLpofn) const
+{
+  aLpofn->lStructSize = sizeof(OPENFILENAMEW);
+  aLpofn->hwndOwner = mHwndOwner;
+  if (mHasFilter) {
+    memcpy(const_cast<LPWSTR>(aLpofn->lpstrFilter),
+           mFilter.data(), mFilter.size() * sizeof(wchar_t));
+  }
+  if (mHasCustomFilter) {
+    aLpofn->nMaxCustFilter = mCustomFilterIn.size() + 1 + mNMaxCustFilterOut;
+    wcscpy(aLpofn->lpstrCustomFilter, mCustomFilterIn.c_str());
+    memset(aLpofn->lpstrCustomFilter + mCustomFilterIn.size() + 1, 0,
+           mNMaxCustFilterOut * sizeof(wchar_t));
+  }
+  else {
+    aLpofn->nMaxCustFilter = 0;
+  }
+  aLpofn->nFilterIndex = mFilterIndex;
+  wcscpy(aLpofn->lpstrFile, mFile.c_str());
+  aLpofn->nMaxFile = mNMaxFile;
+  aLpofn->nMaxFileTitle = mNMaxFileTitle;
+  if (mHasInitialDir) {
+    wcscpy(const_cast<LPWSTR>(aLpofn->lpstrInitialDir), mInitialDir.c_str());
+  }
+  if (mHasTitle) {
+    wcscpy(const_cast<LPWSTR>(aLpofn->lpstrTitle), mTitle.c_str());
+  }
+  aLpofn->Flags = mFlags;  /* TODO: Consider adding OFN_NOCHANGEDIR */
+  if (mHasDefExt) {
+    wcscpy(const_cast<LPWSTR>(aLpofn->lpstrDefExt), mDefExt.c_str());
+  }
+  aLpofn->FlagsEx = mFlagsEx;
+}
+
+void
+OpenFileNameIPC::AllocateOfnStrings(LPOPENFILENAMEW aLpofn) const
+{
+  if (mHasFilter) {
+    // mFilter is double-NULL terminated and it includes the double-NULL in its length.
+    aLpofn->lpstrFilter =
+      static_cast<LPCTSTR>(moz_xmalloc(sizeof(wchar_t) * (mFilter.size())));
+  }
+  if (mHasCustomFilter) {
+    aLpofn->lpstrCustomFilter =
+      static_cast<LPTSTR>(moz_xmalloc(sizeof(wchar_t) * (mCustomFilterIn.size() + 1) + mNMaxCustFilterOut));
+  }
+  aLpofn->lpstrFile =
+    static_cast<LPTSTR>(moz_xmalloc(sizeof(wchar_t) * mNMaxFile));
+  if (mNMaxFileTitle > 0) {
+    aLpofn->lpstrFileTitle =
+      static_cast<LPTSTR>(moz_xmalloc(sizeof(wchar_t) * mNMaxFileTitle));
+  }
+  if (mHasInitialDir) {
+    aLpofn->lpstrInitialDir =
+      static_cast<LPCTSTR>(moz_xmalloc(sizeof(wchar_t) * (mInitialDir.size() + 1)));
+  }
+  if (mHasTitle) {
+    aLpofn->lpstrTitle =
+      static_cast<LPCTSTR>(moz_xmalloc(sizeof(wchar_t) * (mTitle.size() + 1)));
+  }
+  if (mHasDefExt) {
+    aLpofn->lpstrDefExt =
+      static_cast<LPCTSTR>(moz_xmalloc(sizeof(wchar_t) * (mDefExt.size() + 1)));
+  }
+}
+
+void
+OpenFileNameIPC::FreeOfnStrings(LPOPENFILENAMEW aLpofn) const
+{
+  if (aLpofn->lpstrFilter) {
+    free(const_cast<LPWSTR>(aLpofn->lpstrFilter));
+  }
+  if (aLpofn->lpstrCustomFilter) {
+    free(aLpofn->lpstrCustomFilter);
+  }
+  if (aLpofn->lpstrFile) {
+    free(aLpofn->lpstrFile);
+  }
+  if (aLpofn->lpstrFileTitle) {
+    free(aLpofn->lpstrFileTitle);
+  }
+  if (aLpofn->lpstrInitialDir) {
+    free(const_cast<LPWSTR>(aLpofn->lpstrInitialDir));
+  }
+  if (aLpofn->lpstrTitle) {
+    free(const_cast<LPWSTR>(aLpofn->lpstrTitle));
+  }
+  if (aLpofn->lpstrDefExt) {
+    free(const_cast<LPWSTR>(aLpofn->lpstrDefExt));
+  }
+}
+
+void
+OpenFileNameRetIPC::CopyFromOfn(LPOPENFILENAMEW aLpofn)
+{
+  if (aLpofn->lpstrCustomFilter != nullptr) {
+    mCustomFilterOut =
+      std::wstring(aLpofn->lpstrCustomFilter + wcslen(aLpofn->lpstrCustomFilter) + 1);
+  }
+  mFile.assign(aLpofn->lpstrFile, aLpofn->nMaxFile);
+  if (aLpofn->lpstrFileTitle != nullptr) {
+    mFileTitle.assign(aLpofn->lpstrFileTitle, wcslen(aLpofn->lpstrFileTitle) + 1);
+  }
+  mFileOffset = aLpofn->nFileOffset;
+  mFileExtension = aLpofn->nFileExtension;
+}
+
+void
+OpenFileNameRetIPC::AddToOfn(LPOPENFILENAMEW aLpofn) const
+{
+  if (aLpofn->lpstrCustomFilter) {
+    LPWSTR secondString =
+      aLpofn->lpstrCustomFilter + wcslen(aLpofn->lpstrCustomFilter) + 1;
+    const wchar_t* customFilterOut = mCustomFilterOut.c_str();
+    MOZ_ASSERT(wcslen(aLpofn->lpstrCustomFilter) + 1 +
+               wcslen(customFilterOut) + 1 + 1 <= aLpofn->nMaxCustFilter);
+    wcscpy(secondString, customFilterOut);
+    secondString[wcslen(customFilterOut) + 1] = L'\0';  // terminated with two NULLs
+  }
+  MOZ_ASSERT(mFile.size() <= aLpofn->nMaxFile);
+  memcpy(aLpofn->lpstrFile,
+         mFile.data(), mFile.size() * sizeof(wchar_t));
+  if (aLpofn->lpstrFileTitle != nullptr) {
+    MOZ_ASSERT(mFileTitle.size() + 1 < aLpofn->nMaxFileTitle);
+    wcscpy(aLpofn->lpstrFileTitle, mFileTitle.c_str());
+  }
+  aLpofn->nFileOffset = mFileOffset;
+  aLpofn->nFileExtension = mFileExtension;
+}
+#endif  // XP_WIN
+
 } // namespace plugins
 } // namespace mozilla
--- a/dom/plugins/ipc/PluginMessageUtils.h
+++ b/dom/plugins/ipc/PluginMessageUtils.h
@@ -27,16 +27,19 @@
 #  include "nsExceptionHandler.h"
 #endif
 #ifdef XP_MACOSX
 #include "PluginInterposeOSX.h"
 #else
 namespace mac_plugin_interposing { class NSCursorInfo { }; }
 #endif
 using mac_plugin_interposing::NSCursorInfo;
+#ifdef XP_WIN
+#include "commdlg.h"
+#endif
 
 namespace mozilla {
 namespace plugins {
 
 using layers::SurfaceDescriptorX11;
 
 enum ScriptableObjectType
 {
@@ -118,19 +121,69 @@ typedef XID NativeWindowHandle;
 typedef intptr_t NativeWindowHandle; // never actually used, will always be 0
 #else
 #error Need NativeWindowHandle for this platform
 #endif
 
 #ifdef XP_WIN
 typedef base::SharedMemoryHandle WindowsSharedMemoryHandle;
 typedef HANDLE DXGISharedSurfaceHandle;
-#else
+
+// Values indicate GetOpenFileNameW and GetSaveFileNameW.
+enum GetFileNameFunc { OPEN_FUNC, SAVE_FUNC };
+
+// IPC-capable version of the Windows OPENFILENAMEW struct.
+typedef struct _OpenFileNameIPC
+{
+  // Allocates memory for the strings in this object.  This should usually
+  // be used with a zeroed out OPENFILENAMEW structure.
+  void AllocateOfnStrings(LPOPENFILENAMEW aLpofn) const;
+  void FreeOfnStrings(LPOPENFILENAMEW aLpofn) const;
+  void AddToOfn(LPOPENFILENAMEW aLpofn) const;
+  void CopyFromOfn(LPOPENFILENAMEW aLpofn);
+
+  NativeWindowHandle mHwndOwner;
+  std::wstring mFilter;    // Double-NULL terminated (i.e. L"\0\0") if mHasFilter is true
+  bool mHasFilter;
+  std::wstring mCustomFilterIn;
+  bool mHasCustomFilter;
+  uint32_t mNMaxCustFilterOut;
+  uint32_t mFilterIndex;
+  std::wstring mFile;
+  uint32_t mNMaxFile;
+  uint32_t mNMaxFileTitle;
+  std::wstring mInitialDir;
+  bool mHasInitialDir;
+  std::wstring mTitle;
+  bool mHasTitle;
+  uint32_t mFlags;
+  std::wstring mDefExt;
+  bool mHasDefExt;
+  uint32_t mFlagsEx;
+} OpenFileNameIPC;
+
+// GetOpenFileNameW and GetSaveFileNameW overwrite fields of their OPENFILENAMEW
+// parameter.  This represents those values so that they can be returned via IPC.
+typedef struct _OpenFileNameRetIPC
+{
+  void CopyFromOfn(LPOPENFILENAMEW aLpofn);
+  void AddToOfn(LPOPENFILENAMEW aLpofn) const;
+
+  std::wstring mCustomFilterOut;
+  std::wstring mFile;    // Double-NULL terminated (i.e. L"\0\0")
+  std::wstring mFileTitle;
+  uint16_t mFileOffset;
+  uint16_t mFileExtension;
+} OpenFileNameRetIPC;
+#else  // XP_WIN
 typedef mozilla::null_t WindowsSharedMemoryHandle;
 typedef mozilla::null_t DXGISharedSurfaceHandle;
+typedef mozilla::null_t GetFileNameFunc;
+typedef mozilla::null_t OpenFileNameIPC;
+typedef mozilla::null_t OpenFileNameRetIPC;
 #endif
 
 // XXX maybe not the best place for these. better one?
 
 #define VARSTR(v_)  case v_: return #v_
 inline const char*
 NPPVariableToString(NPPVariable aVar)
 {
@@ -718,16 +771,139 @@ struct ParamTraits<mozilla::plugins::NPA
 
   static void Log(const paramType& aParam, std::wstring* aLog)
   {
     aLog->append(StringPrintf(L"[%d, %d, %S]", aParam.flow, aParam.role,
                               aParam.defaultDevice.c_str()));
   }
 };
 
+#ifdef XP_WIN
+template <>
+struct ParamTraits<mozilla::plugins::_OpenFileNameIPC>
+{
+  typedef mozilla::plugins::_OpenFileNameIPC paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mHwndOwner);
+    WriteParam(aMsg, aParam.mFilter);
+    WriteParam(aMsg, aParam.mHasFilter);
+    WriteParam(aMsg, aParam.mCustomFilterIn);
+    WriteParam(aMsg, aParam.mHasCustomFilter);
+    WriteParam(aMsg, aParam.mNMaxCustFilterOut);
+    WriteParam(aMsg, aParam.mFilterIndex);
+    WriteParam(aMsg, aParam.mFile);
+    WriteParam(aMsg, aParam.mNMaxFile);
+    WriteParam(aMsg, aParam.mNMaxFileTitle);
+    WriteParam(aMsg, aParam.mInitialDir);
+    WriteParam(aMsg, aParam.mHasInitialDir);
+    WriteParam(aMsg, aParam.mTitle);
+    WriteParam(aMsg, aParam.mHasTitle);
+    WriteParam(aMsg, aParam.mFlags);
+    WriteParam(aMsg, aParam.mDefExt);
+    WriteParam(aMsg, aParam.mHasDefExt);
+    WriteParam(aMsg, aParam.mFlagsEx);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    if (ReadParam(aMsg, aIter, &aResult->mHwndOwner) &&
+        ReadParam(aMsg, aIter, &aResult->mFilter) &&
+        ReadParam(aMsg, aIter, &aResult->mHasFilter) &&
+        ReadParam(aMsg, aIter, &aResult->mCustomFilterIn) &&
+        ReadParam(aMsg, aIter, &aResult->mHasCustomFilter) &&
+        ReadParam(aMsg, aIter, &aResult->mNMaxCustFilterOut) &&
+        ReadParam(aMsg, aIter, &aResult->mFilterIndex) &&
+        ReadParam(aMsg, aIter, &aResult->mFile) &&
+        ReadParam(aMsg, aIter, &aResult->mNMaxFile) &&
+        ReadParam(aMsg, aIter, &aResult->mNMaxFileTitle) &&
+        ReadParam(aMsg, aIter, &aResult->mInitialDir) &&
+        ReadParam(aMsg, aIter, &aResult->mHasInitialDir) &&
+        ReadParam(aMsg, aIter, &aResult->mTitle) &&
+        ReadParam(aMsg, aIter, &aResult->mHasTitle) &&
+        ReadParam(aMsg, aIter, &aResult->mFlags) &&
+        ReadParam(aMsg, aIter, &aResult->mDefExt) &&
+        ReadParam(aMsg, aIter, &aResult->mHasDefExt) &&
+        ReadParam(aMsg, aIter, &aResult->mFlagsEx)) {
+      return true;
+    }
+    return false;
+  }
+
+  static void Log(const paramType& aParam, std::wstring* aLog)
+  {
+    aLog->append(StringPrintf(L"[%S, %S, %S, %S]", aParam.mFilter.c_str(),
+                              aParam.mCustomFilterIn.c_str(), aParam.mFile.c_str(),
+                              aParam.mTitle.c_str()));
+  }
+};
+
+template <>
+struct ParamTraits<mozilla::plugins::_OpenFileNameRetIPC>
+{
+  typedef mozilla::plugins::_OpenFileNameRetIPC paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mCustomFilterOut);
+    WriteParam(aMsg, aParam.mFile);
+    WriteParam(aMsg, aParam.mFileTitle);
+    WriteParam(aMsg, aParam.mFileOffset);
+    WriteParam(aMsg, aParam.mFileExtension);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    if (ReadParam(aMsg, aIter, &aResult->mCustomFilterOut) &&
+        ReadParam(aMsg, aIter, &aResult->mFile) &&
+        ReadParam(aMsg, aIter, &aResult->mFileTitle) &&
+        ReadParam(aMsg, aIter, &aResult->mFileOffset) &&
+        ReadParam(aMsg, aIter, &aResult->mFileExtension)) {
+      return true;
+    }
+    return false;
+  }
+
+  static void Log(const paramType& aParam, std::wstring* aLog)
+  {
+    aLog->append(StringPrintf(L"[%S, %S, %S, %d, %d]", aParam.mCustomFilterOut.c_str(),
+                              aParam.mFile.c_str(), aParam.mFileTitle.c_str(),
+                              aParam.mFileOffset, aParam.mFileExtension));
+  }
+};
+
+template <>
+struct ParamTraits<mozilla::plugins::GetFileNameFunc>
+{
+  typedef mozilla::plugins::GetFileNameFunc paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, static_cast<uint32_t>(aParam));
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    uint32_t result;
+    if (ReadParam(aMsg, aIter, &result)) {
+      *aResult = static_cast<paramType>(result);
+      return true;
+    }
+    return false;
+  }
+
+  static void Log(const paramType& aParam, std::wstring* aLog)
+  {
+    aLog->append(StringPrintf(L"[%S]",
+                 aParam == mozilla::plugins::OPEN_FUNC ? "GetOpenFileName" : "GetSaveFileName"));
+  }
+};
+#endif  // XP_WIN
+
 } /* namespace IPC */
 
 
 // Serializing NPEvents is completely platform-specific and can be rather
 // intricate depending on the platform.  So for readability we split it
 // into separate files and have the only macro crud live here.
 // 
 // NB: these guards are based on those where struct NPEvent is defined
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -91,16 +91,27 @@ static bool gDelayFlashFocusReplyUntilEv
 // Used to fix GetWindowInfo problems with internal flash settings dialogs
 static WindowsDllInterceptor sUser32Intercept;
 typedef BOOL (WINAPI *GetWindowInfoPtr)(HWND hwnd, PWINDOWINFO pwi);
 static GetWindowInfoPtr sGetWindowInfoPtrStub = nullptr;
 static HWND sBrowserHwnd = nullptr;
 // sandbox process doesn't get current key states.  So we need get it on chrome.
 typedef SHORT (WINAPI *GetKeyStatePtr)(int);
 static GetKeyStatePtr sGetKeyStatePtrStub = nullptr;
+
+static WindowsDllInterceptor sComDlg32Intercept;
+
+// proxy GetSaveFileName/GetOpenFileName on chrome so that we can know which
+// files the user has given permission to access
+// We count on GetOpenFileNameA/GetSaveFileNameA calling
+// GetOpenFileNameW/GetSaveFileNameW so we don't proxy them explicitly.
+typedef BOOL (WINAPI *GetOpenFileNameWPtr)(LPOPENFILENAMEW lpofn);
+static GetOpenFileNameWPtr sGetOpenFileNameWPtrStub = nullptr;
+typedef BOOL (WINAPI *GetSaveFileNameWPtr)(LPOPENFILENAMEW lpofn);
+static GetSaveFileNameWPtr sGetSaveFileNameWPtrStub = nullptr;
 #endif
 
 /* static */
 bool
 PluginModuleChild::CreateForContentProcess(Endpoint<PPluginModuleChild>&& aEndpoint)
 {
     auto* child = new PluginModuleChild(false);
     return child->InitForContent(Move(aEndpoint));
@@ -2106,16 +2117,134 @@ PMCGetKeyState(int aVirtKey)
     if (chromeInstance) {
         int16_t ret = 0;
         if (chromeInstance->CallGetKeyState(aVirtKey, &ret)) {
           return ret;
         }
     }
     return sGetKeyStatePtrStub(aVirtKey);
 }
+
+BOOL WINAPI PMCGetSaveFileNameW(LPOPENFILENAMEW lpofn);
+BOOL WINAPI PMCGetOpenFileNameW(LPOPENFILENAMEW lpofn);
+
+// Runnable that performs GetOpenFileNameW and GetSaveFileNameW
+// on the main thread so that the call can be
+// synchronously run on the PluginModuleParent via IPC.
+// The task alerts the given semaphore when it is finished.
+class GetFileNameTask : public Runnable
+{
+    BOOL* mReturnValue;
+    void* mLpOpenFileName;
+    HANDLE mSemaphore;
+    GetFileNameFunc mFunc;
+
+public:
+    explicit GetFileNameTask(GetFileNameFunc func, void* aLpOpenFileName,
+                             HANDLE aSemaphore, BOOL* aReturnValue) :
+        mLpOpenFileName(aLpOpenFileName), mSemaphore(aSemaphore),
+        mReturnValue(aReturnValue), mFunc(func)
+    {}
+
+    NS_IMETHOD Run() override
+    {
+        PLUGIN_LOG_DEBUG_METHOD;
+        AssertPluginThread();
+        switch (mFunc) {
+        case OPEN_FUNC:
+            *mReturnValue =
+                PMCGetOpenFileNameW(static_cast<LPOPENFILENAMEW>(mLpOpenFileName));
+            break;
+        case SAVE_FUNC:
+            *mReturnValue =
+                PMCGetSaveFileNameW(static_cast<LPOPENFILENAMEW>(mLpOpenFileName));
+            break;
+        }
+        if (!ReleaseSemaphore(mSemaphore, 1, nullptr)) {
+            return NS_ERROR_FAILURE;
+        }
+        return NS_OK;
+    }
+};
+
+// static
+BOOL
+PostToPluginThread(GetFileNameFunc aFunc, void* aLpofn)
+{
+    MOZ_ASSERT(!IsPluginThread());
+
+    // Synchronously run GetFileNameTask from the main thread.
+    // Start a semaphore at 0.  We release the semaphore (bringing its
+    // count to 1) when the synchronous call is done.
+    nsAutoHandle semaphore(CreateSemaphore(NULL, 0, 1, NULL));
+    if (semaphore == nullptr) {
+        MOZ_ASSERT(semaphore != nullptr);
+        return FALSE;
+    }
+
+    BOOL returnValue = FALSE;
+    RefPtr<GetFileNameTask> task =
+        new GetFileNameTask(aFunc, aLpofn, semaphore, &returnValue);
+    ProcessChild::message_loop()->PostTask(task.forget());
+    DWORD err = WaitForSingleObject(semaphore, INFINITE);
+    if (err != WAIT_FAILED) {
+        return returnValue;
+    }
+    PLUGIN_LOG_DEBUG(("Error while waiting for semaphore: %d",
+                      GetLastError()));
+    MOZ_ASSERT(err != WAIT_FAILED);
+    return FALSE;
+}
+
+// static
+BOOL WINAPI
+PMCGetFileNameW(GetFileNameFunc aFunc, LPOPENFILENAMEW aLpofn)
+{
+    if (!IsPluginThread()) {
+        return PostToPluginThread(aFunc, aLpofn);
+    }
+
+    PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome();
+    if (chromeInstance) {
+        bool ret = FALSE;
+        OpenFileNameIPC inputOfn;
+        inputOfn.CopyFromOfn(aLpofn);
+        OpenFileNameRetIPC outputOfn;
+        if (chromeInstance->CallGetFileName(aFunc, inputOfn,
+                                            &outputOfn, &ret)) {
+            if (ret) {
+                outputOfn.AddToOfn(aLpofn);
+            }
+        }
+        return ret;
+    }
+
+    switch (aFunc) {
+    case OPEN_FUNC:
+        return sGetOpenFileNameWPtrStub(aLpofn);
+    case SAVE_FUNC:
+        return sGetSaveFileNameWPtrStub(aLpofn);
+    }
+
+    MOZ_ASSERT_UNREACHABLE("Illegal GetFileNameFunc value");
+    return FALSE;
+}
+
+// static
+BOOL WINAPI
+PMCGetSaveFileNameW(LPOPENFILENAMEW aLpofn)
+{
+    return PMCGetFileNameW(SAVE_FUNC, aLpofn);
+}
+// static
+BOOL WINAPI
+PMCGetOpenFileNameW(LPOPENFILENAMEW aLpofn)
+{
+    return PMCGetFileNameW(OPEN_FUNC, aLpofn);
+}
 #endif
 
 PPluginInstanceChild*
 PluginModuleChild::AllocPPluginInstanceChild(const nsCString& aMimeType,
                                              const uint16_t& aMode,
                                              const InfallibleTArray<nsCString>& aNames,
                                              const InfallibleTArray<nsCString>& aValues)
 {
@@ -2138,16 +2267,27 @@ PluginModuleChild::AllocPPluginInstanceC
                                  (void**) &sGetWindowInfoPtrStub);
     }
 
     if ((mQuirks & QUIRK_FLASH_HOOK_GETKEYSTATE) &&
         !sGetKeyStatePtrStub) {
         sUser32Intercept.AddHook("GetKeyState", reinterpret_cast<intptr_t>(PMCGetKeyState),
                                  (void**) &sGetKeyStatePtrStub);
     }
+
+    sComDlg32Intercept.Init("comdlg32.dll");
+    if (!sGetSaveFileNameWPtrStub) {
+        sComDlg32Intercept.AddHook("GetSaveFileNameW", reinterpret_cast<intptr_t>(PMCGetSaveFileNameW),
+                                 (void**) &sGetSaveFileNameWPtrStub);
+    }
+
+    if (!sGetOpenFileNameWPtrStub) {
+        sComDlg32Intercept.AddHook("GetOpenFileNameW", reinterpret_cast<intptr_t>(PMCGetOpenFileNameW),
+                                 (void**) &sGetOpenFileNameWPtrStub);
+    }
 #endif
 
     return new PluginInstanceChild(&mFunctions, aMimeType, aMode, aNames,
                                    aValues);
 }
 
 void
 PluginModuleChild::InitQuirksModes(const nsCString& aMimeType)
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -805,16 +805,20 @@ PluginModuleChromeParent::~PluginModuleC
 #endif
 
 #ifdef XP_WIN
     // If we registered for audio notifications, stop.
     mozilla::plugins::PluginUtilsWin::RegisterForAudioDeviceChanges(this,
                                                                     false);
 #endif
 
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+    mSandboxPermissions.RemovePermissionsForProcess(OtherPid());
+#endif
+
     if (!mShutdown) {
         NS_WARNING("Plugin host deleted the module without shutting down.");
         NPError err;
         NP_Shutdown(&err);
     }
 
     NS_ASSERTION(mShutdown, "NP_Shutdown didn't");
 
@@ -3428,8 +3432,74 @@ PluginModuleChromeParent::AnswerGetKeySt
 {
 #if defined(XP_WIN)
     *aRet = ::GetKeyState(aVirtKey);
     return IPC_OK();
 #else
     return PluginModuleParent::AnswerGetKeyState(aVirtKey, aRet);
 #endif
 }
+
+mozilla::ipc::IPCResult
+PluginModuleChromeParent::AnswerGetFileName(const GetFileNameFunc& aFunc,
+                                            const OpenFileNameIPC& aOfnIn,
+                                            OpenFileNameRetIPC* aOfnOut,
+                                            bool* aResult)
+{
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+    OPENFILENAMEW ofn;
+    memset(&ofn, 0, sizeof(ofn));
+    aOfnIn.AllocateOfnStrings(&ofn);
+    aOfnIn.AddToOfn(&ofn);
+    switch (aFunc) {
+    case OPEN_FUNC:
+        *aResult = GetOpenFileName(&ofn);
+        break;
+    case SAVE_FUNC:
+        *aResult = GetSaveFileName(&ofn);
+        break;
+    }
+    if (*aResult) {
+        if (ofn.Flags & OFN_ALLOWMULTISELECT) {
+            // We only support multiselect with the OFN_EXPLORER flag.
+            // This guarantees that ofn.lpstrFile follows the pattern below.
+            MOZ_ASSERT(ofn.Flags & OFN_EXPLORER);
+
+            // lpstrFile is one of two things:
+            // 1. A null terminated full path to a file, or
+            // 2. A path to a folder, followed by a NULL, followed by a
+            // list of file names, each NULL terminated, followed by an
+            // additional NULL (so it is also double-NULL terminated).
+            std::wstring path = std::wstring(ofn.lpstrFile);
+            MOZ_ASSERT(ofn.nFileOffset > 0);
+            // For condition #1, nFileOffset points to the file name in the path.
+            // It will be preceeded by a non-NULL character from the path.
+            if (ofn.lpstrFile[ofn.nFileOffset-1] != L'\0') {
+                mSandboxPermissions.GrantFileAccess(OtherPid(), path.c_str(),
+                                                          aFunc == SAVE_FUNC);
+            }
+            else {
+                // This is condition #2
+                wchar_t* nextFile = ofn.lpstrFile + path.size() + 1;
+                while (*nextFile != L'\0') {
+                    std::wstring nextFileStr(nextFile);
+                    std::wstring fullPath =
+                        path + std::wstring(L"\\") + nextFileStr;
+                    mSandboxPermissions.GrantFileAccess(OtherPid(), fullPath.c_str(),
+                                                              aFunc == SAVE_FUNC);
+                    nextFile += nextFileStr.size() + 1;
+                }
+            }
+        }
+        else {
+            mSandboxPermissions.GrantFileAccess(OtherPid(), ofn.lpstrFile,
+                                                 aFunc == SAVE_FUNC);
+        }
+        aOfnOut->CopyFromOfn(&ofn);
+    }
+    aOfnIn.FreeOfnStrings(&ofn);
+    return IPC_OK();
+#else
+    MOZ_ASSERT_UNREACHABLE("GetFileName IPC message is only available on "
+                           "Windows builds with sandbox.");
+    return IPC_FAIL_NO_REASON(this);
+#endif
+}
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -19,16 +19,17 @@
 #include "mozilla/TimeStamp.h"
 #include "npapi.h"
 #include "npfunctions.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "nsIObserver.h"
 #ifdef XP_WIN
 #include "nsWindowsHelpers.h"
+#include "sandboxPermissions.h"
 #endif
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 
 class nsIProfileSaveEvent;
 class nsPluginTag;
@@ -189,16 +190,24 @@ protected:
     static BrowserStreamParent* StreamCast(NPP instance, NPStream* s,
                                            PluginAsyncSurrogate** aSurrogate = nullptr);
 
     virtual mozilla::ipc::IPCResult
     AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges(
                                         const bool& shouldRegister,
                                         NPError* result) override;
 
+    virtual mozilla::ipc::IPCResult
+    AnswerGetFileName(const GetFileNameFunc& aFunc,
+                      const OpenFileNameIPC& aOfnIn,
+                      OpenFileNameRetIPC* aOfnOut, bool* aResult) override
+    {
+      return IPC_FAIL_NO_REASON(this);
+    }
+
 protected:
     void SetChildTimeout(const int32_t aChildTimeout);
     static void TimeoutChanged(const char* aPref, void* aModule);
 
     virtual void UpdatePluginTimeout() {}
 
     virtual mozilla::ipc::IPCResult RecvNotifyContentModuleDestroyed() override { return IPC_OK(); }
 
@@ -504,16 +513,22 @@ class PluginModuleChromeParent
 #endif
 
     virtual mozilla::ipc::IPCResult
     RecvProfile(const nsCString& aProfile) override;
 
     virtual mozilla::ipc::IPCResult
     AnswerGetKeyState(const int32_t& aVirtKey, int16_t* aRet) override;
 
+    // Proxy GetOpenFileName/GetSaveFileName on Windows.
+    virtual mozilla::ipc::IPCResult
+    AnswerGetFileName(const GetFileNameFunc& aFunc,
+                      const OpenFileNameIPC& aOfnIn,
+                      OpenFileNameRetIPC* aOfnOut, bool* aResult) override;
+
 private:
     virtual void
     EnteredCxxStack() override;
 
     void
     ExitedCxxStack() override;
 
     mozilla::ipc::IProtocol* GetInvokingProtocol();
@@ -657,14 +672,17 @@ private:
     dom::ContentParent* mContentParent;
     nsCOMPtr<nsIObserver> mPluginOfflineObserver;
 #ifdef MOZ_GECKO_PROFILER
     RefPtr<mozilla::ProfileGatherer> mGatherer;
 #endif
     nsCString mProfile;
     bool mIsBlocklisted;
     static bool sInstantiated;
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+    mozilla::SandboxPermissions mSandboxPermissions;
+#endif
 };
 
 } // namespace plugins
 } // namespace mozilla
 
 #endif // mozilla_plugins_PluginModuleParent_h
--- a/dom/plugins/ipc/PluginProcessParent.cpp
+++ b/dom/plugins/ipc/PluginProcessParent.cpp
@@ -92,23 +92,16 @@ AddSandboxAllowedFiles(int32_t aSandboxL
 
     nsresult rv;
     nsCOMPtr<nsIProperties> dirSvc =
         do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
     if (NS_WARN_IF(NS_FAILED(rv))) {
         return;
     }
 
-    // Higher than level 2 currently removes the users own rights.
-    if (aSandboxLevel > 2) {
-        AddSandboxAllowedFile(aAllowedFilesRead, dirSvc, NS_WIN_HOME_DIR);
-        AddSandboxAllowedFile(aAllowedFilesRead, dirSvc, NS_WIN_HOME_DIR,
-                              NS_LITERAL_STRING("\\*"));
-    }
-
     // Level 2 and above is now using low integrity, so we need to give write
     // access to the Flash directories.
     // This should be made Flash specific (Bug 1171396).
     AddSandboxAllowedFile(aAllowedFilesReadWrite, dirSvc, NS_WIN_APPDATA_DIR,
                           NS_LITERAL_STRING("\\Macromedia\\Flash Player\\*"));
     AddSandboxAllowedFile(aAllowedFilesReadWrite, dirSvc, NS_WIN_LOCAL_APPDATA_DIR,
                           NS_LITERAL_STRING("\\Macromedia\\Flash Player\\*"));
     AddSandboxAllowedFile(aAllowedFilesReadWrite, dirSvc, NS_WIN_APPDATA_DIR,
--- a/dom/plugins/ipc/moz.build
+++ b/dom/plugins/ipc/moz.build
@@ -124,16 +124,17 @@ LOCAL_INCLUDES += [
     '../base',
     '/xpcom/base/',
 ]
 
 if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_ARCH'] == 'WINNT':
     LOCAL_INCLUDES += [
         '/security/sandbox/chromium',
         '/security/sandbox/chromium-shim',
+        '/security/sandbox/win/src/sandboxpermissions',
     ]
 
 DEFINES['FORCE_PR_LOG'] = True
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gtk3':
     CXXFLAGS += CONFIG['TK_CFLAGS']
 else:
     # Force build against gtk+2 for struct offsets and such.
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -652,16 +652,18 @@ description =
 [PPluginModule::NPN_SetException]
 description =
 [PPluginModule::GetKeyState]
 description =
 [PPluginModule::NPN_SetValue_NPPVpluginRequiresAudioDeviceChanges]
 description =
 [PPluginModule::InitCrashReporter]
 description =
+[PPluginModule::GetFileName]
+description =
 [PPluginScriptableObject::NPN_Evaluate]
 description =
 [PPluginScriptableObject::Invalidate]
 description =
 [PPluginScriptableObject::HasMethod]
 description =
 [PPluginScriptableObject::Invoke]
 description =