Bug 1284897 - Hook GetSaveFileNameW/GetOpenFileNameW to record and grant a sandboxed process permission to access user-chosen files. r=jimm
☠☠ backed out by 19f1d4210d56 ☠ ☠
authorDavid Parks <dparks@mozilla.com>
Wed, 08 Feb 2017 11:38:40 -0800
changeset 344176 c35afe490583c88e2c6a7041aad3d1451b3e82bc
parent 344175 0740284125d33ce825023ae66fdf07484f62f0c4
child 344177 001bc0207dbcf4b008d9a23030e9b3adc5bdd7ab
push id31402
push usercbook@mozilla.com
push dateWed, 22 Feb 2017 13:33:50 +0000
treeherdermozilla-central@f5372cb6c3c7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimm
bugs1284897
milestone54.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 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
--- 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.