Bug 1488439: Part 2 - Patch GetFileAttributesW for appdata parent folders in sandboxed plugin process (r=bobowen,jmathies)
☠☠ backed out by 32af88ce76da ☠ ☠
authorDavid Parks <dparks@mozilla.com>
Fri, 12 Oct 2018 17:41:29 +0000
changeset 489373 74b2087ee696eb7369b65727b81fc67121789f7d
parent 489372 53dfca556ff165528d145ebcaf64186ca274ccd3
child 489374 f464ecbaeeda2c62e78f5a3b862597f737601f8a
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersbobowen, jmathies
bugs1488439
milestone64.0a1
Bug 1488439: Part 2 - Patch GetFileAttributesW for appdata parent folders in sandboxed plugin process (r=bobowen,jmathies) Flash analyzes the parents of the path to its appdata folder on Windows using GetFileAttributesW. If it runs into an error, it makes some internal decisions that cause it to break DRM video. Our new sandbox hardening causes GetFileAttributesW to return an error for some components of the path. This patch alters the behavior of GetFileAttributesW so that it always reports FILE_ATTRIBUTE_DIRECTORY for any path that both 1) would otherwise return an error and 2) is an ancestor of the appdata folder. This may not always be 100% accurate (for instance, if the folder is a reparse point) but restores video functionality. Depends on D7532 Differential Revision: https://phabricator.services.mozilla.com/D7533
dom/plugins/ipc/FunctionBroker.h
dom/plugins/ipc/FunctionBrokerIPCUtils.h
dom/plugins/ipc/FunctionHook.cpp
dom/plugins/ipc/IpdlTuple.h
dom/plugins/ipc/PluginModuleChild.cpp
dom/plugins/ipc/PluginModuleChild.h
dom/plugins/ipc/PluginProcessChild.cpp
dom/plugins/ipc/PluginProcessParent.cpp
--- a/dom/plugins/ipc/FunctionBroker.h
+++ b/dom/plugins/ipc/FunctionBroker.h
@@ -284,16 +284,18 @@ inline bool ParameterEquality(const char
   * A type map _from_ the type of a parameter in the original function
   * we are brokering _to_ a type that we can marshal.  We must be able
   * to Copy() the marshaled type using the parameter type.
   * The default maps from type T back to type T.
   */
 template<typename OrigType> struct IPCTypeMap     { typedef OrigType ipc_type; };
 template<> struct IPCTypeMap<char*>               { typedef nsDependentCSubstring ipc_type; };
 template<> struct IPCTypeMap<const char*>         { typedef nsDependentCSubstring ipc_type; };
+template<> struct IPCTypeMap<wchar_t*>            { typedef nsString ipc_type; };
+template<> struct IPCTypeMap<const wchar_t*>      { typedef nsString ipc_type; };
 template<> struct IPCTypeMap<long>                { typedef int32_t ipc_type; };
 template<> struct IPCTypeMap<unsigned long>       { typedef uint32_t ipc_type; };
 
 #if defined(XP_WIN)
 template<> struct IPCTypeMap<PSecHandle>          { typedef uint64_t ipc_type; };
 template<> struct IPCTypeMap<PTimeStamp>          { typedef uint64_t ipc_type; };
 template<> struct IPCTypeMap<void*>               { typedef uint64_t ipc_type; }; // HANDLEs
 template<> struct IPCTypeMap<HWND>                { typedef NativeWindowHandle ipc_type; };
@@ -499,16 +501,27 @@ struct BaseEndpointHandler<CLIENT,SelfTy
     // In the client, we just bind to the caller's string
     if (aSrc) {
       aDest.Rebind(aSrc, strlen(aSrc));
     } else {
       aDest.SetIsVoid(true);
     }
   }
 
+  inline static void Copy(nsString& aDest, wchar_t* const& aSrc)
+  {
+    if (aSrc) {
+      // We are using nsString as a "raw" container for a wchar_t string.  We
+      // just use its data as a wchar_t* later (so the reinterpret_cast is safe).
+      aDest.Rebind(reinterpret_cast<char16_t*>(aSrc), wcslen(aSrc));
+    } else {
+      aDest.SetIsVoid(true);
+    }
+  }
+
   inline static void Copy(char*& aDest, const nsDependentCSubstring& aSrc)
   {
     MOZ_ASSERT_UNREACHABLE("Returning char* parameters is not yet suported.");
   }
 
 #if defined(XP_WIN)
   inline static void Copy(uint32_t& aDest, const LPDWORD& aSrc)
   {
@@ -576,16 +589,37 @@ struct BaseEndpointHandler<SERVER, SelfT
 
   inline static void Copy(ServerCallData* aScd, const char*& aDest, const nsDependentCSubstring& aSrc)
   {
     char* nonConstDest;
     Copy(aScd, nonConstDest, aSrc);
     aDest = nonConstDest;
   }
 
+  inline static void Copy(ServerCallData* aScd, wchar_t*& aDest, const nsString& aSrc)
+  {
+    // Allocating the string with aScd means it will last during the server call
+    // and be freed when the call is complete.
+    MOZ_ASSERT(aScd);
+    if (aSrc.IsVoid()) {
+      aDest = nullptr;
+      return;
+    }
+    aScd->AllocateMemory((aSrc.Length() + 1)*sizeof(wchar_t), aDest);
+    memcpy(aDest, aSrc.Data(), aSrc.Length() * sizeof(wchar_t));
+    aDest[aSrc.Length()] = L'\0';
+  }
+
+  inline static void Copy(ServerCallData* aScd, const wchar_t*& aDest, const nsString& aSrc)
+  {
+    wchar_t* nonConstDest;
+    Copy(aScd, nonConstDest, aSrc);
+    aDest = nonConstDest;
+  }
+
 #if defined(XP_WIN)
   inline static void Copy(uint32_t& aDest, const LPDWORD& aSrc)
   {
     aDest = *aSrc;
   }
 
   inline static void Copy(LPDWORD& aDest, const uint32_t& aSrc)
   {
--- a/dom/plugins/ipc/FunctionBrokerIPCUtils.h
+++ b/dom/plugins/ipc/FunctionBrokerIPCUtils.h
@@ -43,16 +43,17 @@ enum FunctionHookId
   , ID_HttpEndRequestA
   , ID_InternetQueryOptionA
   , ID_InternetErrorDlg
   , ID_AcquireCredentialsHandleA
   , ID_QueryCredentialsAttributesA
   , ID_FreeCredentialsHandle
   , ID_PrintDlgW
   , ID_CreateMutexW
+  , ID_GetFileAttributesW
   , ID_FunctionHookCount
 #else // defined(XP_WIN)
     ID_FunctionHookCount
 #endif // defined(XP_WIN)
 };
 
 // Max number of bytes to show when logging a blob of raw memory
 static const uint32_t MAX_BLOB_CHARS_TO_LOG = 12;
--- a/dom/plugins/ipc/FunctionHook.cpp
+++ b/dom/plugins/ipc/FunctionHook.cpp
@@ -6,16 +6,17 @@
 
 #include "FunctionHook.h"
 #include "FunctionBroker.h"
 #include "nsClassHashtable.h"
 #include "mozilla/ClearOnShutdown.h"
 
 #if defined(XP_WIN)
 #include <shlobj.h>
+#include "PluginModuleChild.h"
 #endif
 
 namespace mozilla {
 namespace plugins {
 
 StaticAutoPtr<FunctionHookArray> FunctionHook::sFunctionHooks;
 
 bool AlwaysHook(int) { return true; }
@@ -303,30 +304,66 @@ void FunctionHook::HookProtectedMode()
   // Legacy code.  Uses the nsWindowsDLLInterceptor directly instead of
   // using the FunctionHook
   sKernel32Intercept.Init("kernel32.dll");
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Plugin);
   sCreateFileWStub.Set(sKernel32Intercept, "CreateFileW", &CreateFileWHookFn);
   sCreateFileAStub.Set(sKernel32Intercept, "CreateFileA", &CreateFileAHookFn);
 }
 
+/* GetFileAttributesW */
+
+typedef BasicFunctionHook<ID_GetFileAttributesW, decltype(GetFileAttributesW)> GetFileAttributesWFH;
+
+DWORD WINAPI GetFileAttributesWHook(LPCWSTR aFilename)
+{
+  MOZ_ASSERT(ID_GetFileAttributesW < FunctionHook::GetHooks()->Length());
+  GetFileAttributesWFH* functionHook =
+    static_cast<GetFileAttributesWFH*>(FunctionHook::GetHooks()->ElementAt(ID_GetFileAttributesW));
+  if (!functionHook->OriginalFunction()) {
+    NS_ASSERTION(FALSE, "Something is horribly wrong in GetFileAttributesWHook!");
+    return FALSE;
+  }
+
+  DWORD ret = functionHook->OriginalFunction()(aFilename);
+  if (ret != INVALID_FILE_ATTRIBUTES) {
+    return ret;
+  }
+
+  // If aFilename is a parent of PluginModuleChild::GetFlashRoamingPath then
+  // assume it was blocked by the sandbox and just report it as a plain directory.
+  size_t len = wcslen(aFilename);
+  std::wstring roamingPath = PluginModuleChild::GetFlashRoamingPath();
+  bool isParent =
+    (len > 0) && (aFilename[len - 1] == L'\\') &&
+    (_wcsnicmp(aFilename, roamingPath.c_str(), len) == 0);
+  if (!isParent) {
+    return ret;
+  }
+  return FILE_ATTRIBUTE_DIRECTORY;
+}
+
 #endif // defined(XP_WIN)
 
 #define FUN_HOOK(x) static_cast<FunctionHook*>(x)
 
 void
 FunctionHook::AddFunctionHooks(FunctionHookArray& aHooks)
 {
   // We transfer ownership of the FunctionHook objects to the array.
 #if defined(XP_WIN)
   aHooks[ID_GetWindowInfo] =
     FUN_HOOK(new GetWindowInfoFH("user32.dll", "GetWindowInfo",
                                  &GetWindowInfo, &GetWindowInfoHook));
   aHooks[ID_PrintDlgW] =
     FUN_HOOK(new PrintDlgWFH("comdlg32.dll", "PrintDlgW", &PrintDlgW,
                              PrintDlgWHook));
+  aHooks[ID_GetFileAttributesW] =
+    FUN_HOOK(new GetFileAttributesWFH("kernel32.dll", "GetFileAttributesW",
+                                      &GetFileAttributesW,
+                                      &GetFileAttributesWHook));
 #endif // defined(XP_WIN)
 }
 
 #undef FUN_HOOK
 
 } // namespace plugins
 } // namespace mozilla
--- a/dom/plugins/ipc/IpdlTuple.h
+++ b/dom/plugins/ipc/IpdlTuple.h
@@ -34,23 +34,23 @@ public:
   const MaybeVariantType& GetVariant() const { return mValue; }
 
 private:
   MaybeVariantType mValue;
 };
 
 #if defined(XP_WIN)
 typedef MaybeVariant<int8_t,uint8_t,int16_t,uint16_t,int32_t,uint32_t,
-                     int64_t,uint64_t,nsCString,bool,OpenFileNameIPC,
+                     int64_t,uint64_t,nsCString,nsString,bool,OpenFileNameIPC,
                      OpenFileNameRetIPC,NativeWindowHandle,
                      IPCSchannelCred,IPCInternetBuffers,StringArray,
                      IPCPrintDlg> IpdlTupleElement;
 #else
 typedef MaybeVariant<int8_t,uint8_t,int16_t,uint16_t,int32_t,uint32_t,
-                     int64_t,uint64_t,nsCString,bool> IpdlTupleElement;
+                     int64_t,uint64_t,nsCString,nsString,bool> IpdlTupleElement;
 #endif // defined(XP_WIN)
 
 } // namespace internal
 } // namespace plugins
 } // namespace mozilla
 
 DECLARE_USE_COPY_CONSTRUCTORS(mozilla::plugins::internal::IpdlTupleElement)
 
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -60,16 +60,19 @@
 
 using namespace mozilla;
 using namespace mozilla::ipc;
 using namespace mozilla::plugins;
 using namespace mozilla::widget;
 
 #if defined(XP_WIN)
 const wchar_t * kFlashFullscreenClass = L"ShockwaveFlashFullScreen";
+#if defined(MOZ_SANDBOX)
+std::wstring sRoamingPath;
+#endif
 #endif
 
 namespace {
 // see PluginModuleChild::GetChrome()
 PluginModuleChild* gChromeInstance = nullptr;
 } // namespace
 
 #ifdef XP_WIN
@@ -203,24 +206,44 @@ PluginModuleChild::RecvDisableFlashProte
 void
 PluginModuleChild::EnableFlashSandbox(int aLevel, bool aShouldEnableLogging)
 {
     mFlashSandboxLevel = aLevel;
     mEnableFlashSandboxLogging = aShouldEnableLogging;
 }
 #endif
 
+#if defined(OS_WIN) && defined(MOZ_SANDBOX)
+/* static */ void
+PluginModuleChild::SetFlashRoamingPath(const std::wstring& aRoamingPath)
+{
+  MOZ_ASSERT(sRoamingPath.empty());
+  sRoamingPath = aRoamingPath;
+}
+
+/* static */ std::wstring
+PluginModuleChild::GetFlashRoamingPath()
+{
+  return sRoamingPath;
+}
+#endif
+
 bool
 PluginModuleChild::InitForChrome(const std::string& aPluginFilename,
                                  base::ProcessId aParentPid,
                                  MessageLoop* aIOLoop,
                                  IPC::Channel* aChannel)
 {
     NS_ASSERTION(aChannel, "need a channel");
 
+#if defined(OS_WIN) && defined(MOZ_SANDBOX)
+    MOZ_ASSERT(!sRoamingPath.empty(),
+               "Should have already called SetFlashRoamingPath");
+#endif
+
     if (!InitGraphics())
         return false;
 
     mPluginFilename = aPluginFilename.c_str();
     nsCOMPtr<nsIFile> localFile;
     NS_NewLocalFile(NS_ConvertUTF8toUTF16(mPluginFilename),
                     true,
                     getter_AddRefs(localFile));
--- a/dom/plugins/ipc/PluginModuleChild.h
+++ b/dom/plugins/ipc/PluginModuleChild.h
@@ -120,16 +120,23 @@ protected:
     virtual mozilla::ipc::IPCResult
     AnswerModuleSupportsAsyncRender(bool* aResult) override;
 public:
     explicit PluginModuleChild(bool aIsChrome);
     virtual ~PluginModuleChild();
 
     void CommonInit();
 
+#if defined(OS_WIN) && defined(MOZ_SANDBOX)
+    // Path to the roaming Flash Player folder.  This is used to restore some
+    // behavior blocked by the sandbox.
+    static void SetFlashRoamingPath(const std::wstring& aRoamingPath);
+    static std::wstring GetFlashRoamingPath();
+#endif
+
     // aPluginFilename is UTF8, not native-charset!
     bool InitForChrome(const std::string& aPluginFilename,
                        base::ProcessId aParentPid,
                        MessageLoop* aIOLoop,
                        IPC::Channel* aChannel);
 
     bool InitForContent(Endpoint<PPluginModuleChild>&& aEndpoint);
 
--- a/dom/plugins/ipc/PluginProcessChild.cpp
+++ b/dom/plugins/ipc/PluginProcessChild.cpp
@@ -134,32 +134,35 @@ PluginProcessChild::Init(int aArgc, char
 #elif defined(OS_WIN)
     std::vector<std::wstring> values =
         CommandLine::ForCurrentProcess()->GetLooseValues();
     MOZ_ASSERT(values.size() >= 1, "not enough loose args");
 
     // parameters are:
     // values[0] is path to plugin DLL
     // values[1] is path to folder that should be used for temp files
+    // values[2] is path to the Flash Player roaming folder
+    //   (this is always that Flash folder, regardless of what plugin is being run)
     pluginFilename = WideToUTF8(values[0]);
 
     // We don't initialize XPCOM but we need the thread manager and the
     // logging framework for the FunctionBroker.
     NS_SetMainThread();
     mozilla::TimeStamp::Startup();
     NS_LogInit();
     mozilla::LogModule::Init(aArgc, aArgv);
     nsThreadManager::get().Init();
 
 #if defined(MOZ_SANDBOX)
-    MOZ_ASSERT(values.size() >= 2, "not enough loose args for sandboxed plugin process");
+    MOZ_ASSERT(values.size() >= 3, "not enough loose args for sandboxed plugin process");
 
     // The sandbox closes off the default location temp file location so we set
     // a new one here (regardless of whether or not we are sandboxing).
     SetSandboxTempPath(values[1]);
+    PluginModuleChild::SetFlashRoamingPath(values[2]);
 
     // This is probably the earliest we would want to start the sandbox.
     // As we attempt to tighten the sandbox, we may need to consider moving this
     // to later in the plugin initialization.
     mozilla::SandboxTarget::Instance()->StartSandbox();
 #endif
 #else
 #  error Sorry
--- a/dom/plugins/ipc/PluginProcessParent.cpp
+++ b/dom/plugins/ipc/PluginProcessParent.cpp
@@ -109,16 +109,29 @@ PluginProcessParent::Launch(mozilla::Uni
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to get plugin process temp directory.");
       return false;
     }
 
     nsAutoString tempDir;
     MOZ_ALWAYS_SUCCEEDS(dir->GetPath(tempDir));
     args.push_back(NS_ConvertUTF16toUTF8(tempDir).get());
+
+    rv =
+      dirSvc->Get(NS_WIN_APPDATA_DIR, NS_GET_IID(nsIFile),
+                  getter_AddRefs(dir));
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Failed to get appdata directory.");
+      return false;
+    }
+
+    nsAutoString appdataDir;
+    MOZ_ALWAYS_SUCCEEDS(dir->GetPath(appdataDir));
+    appdataDir.Append(L"\\Adobe\\");
+    args.push_back(NS_ConvertUTF16toUTF8(appdataDir).get());
 #endif
 
     bool result = AsyncLaunch(args);
     if (!result) {
         mLaunchCompleteTask = nullptr;
     }
     return result;
 }