Bug 1386832 - Part 2 - Make Linux dev build security check exception not depend on MOZ_DEVELOPER_REPO draft
authorHaik Aftandilian <haftandilian@mozilla.com>
Mon, 07 Aug 2017 14:11:57 -0700
changeset 642617 1b1c86be606ec65879ff98cfa21208d689eb45f4
parent 642616 bf2e3bea76f7630b69e6590fedd89905e1c0a8b4
child 725062 066e35956ecf2204fb52594e81e0037f2bff734b
push id72827
push userhaftandilian@mozilla.com
push dateTue, 08 Aug 2017 16:16:55 +0000
bugs1386832
milestone57.0a1
Bug 1386832 - Part 2 - Make Linux dev build security check exception not depend on MOZ_DEVELOPER_REPO For Linux dev builds, change the developer build unpacked security check exception to not depend on knowing the repo dir because MOZ_DEVELOPER_REPO isn't reliably set whenever the firefox binary is run. Instead, make sure the extension root directory is within NS_GRE_DIR. Use both checks on Mac. MozReview-Commit-ID: IsbbNS58yf8
netwerk/protocol/res/ExtensionProtocolHandler.cpp
netwerk/protocol/res/ExtensionProtocolHandler.h
--- a/netwerk/protocol/res/ExtensionProtocolHandler.cpp
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
@@ -2,30 +2,31 @@
 /* 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/. */
 
 #include "ExtensionProtocolHandler.h"
 
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/ExtensionPolicyService.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/ipc/IPCStreamUtils.h"
 #include "mozilla/ipc/URIParams.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/RefPtr.h"
 
 #include "FileDescriptor.h"
 #include "FileDescriptorFile.h"
 #include "LoadInfo.h"
 #include "nsContentUtils.h"
 #include "nsServiceManagerUtils.h"
-#include "nsContentUtils.h"
+#include "nsDirectoryServiceDefs.h"
 #include "nsIFile.h"
 #include "nsIFileChannel.h"
 #include "nsIFileStreams.h"
 #include "nsIFileURL.h"
 #include "nsIJARChannel.h"
 #include "nsIMIMEService.h"
 #include "nsIURL.h"
 #include "nsIChannel.h"
@@ -40,20 +41,16 @@
 #include "prio.h"
 #include "SimpleChannel.h"
 
 #if defined(XP_WIN)
 #include "nsILocalFileWin.h"
 #include "WinUtils.h"
 #endif
 
-#if !defined(XP_WIN) && defined(MOZ_CONTENT_SANDBOX)
-#include "mozilla/SandboxSettings.h"
-#endif
-
 #define EXTENSION_SCHEME "moz-extension"
 using mozilla::ipc::FileDescriptor;
 using OptionalIPCStream = mozilla::ipc::OptionalIPCStream;
 
 namespace mozilla {
 
 template <>
 class MOZ_MUST_USE_TYPE GenericErrorResult<nsresult>
@@ -362,19 +359,22 @@ ExtensionProtocolHandler::GetSingleton()
     sSingleton = new ExtensionProtocolHandler();
     ClearOnShutdown(&sSingleton);
   }
   return do_AddRef(sSingleton.get());
 }
 
 ExtensionProtocolHandler::ExtensionProtocolHandler()
   : SubstitutingProtocolHandler(EXTENSION_SCHEME)
-#if !defined(XP_WIN) && defined(MOZ_CONTENT_SANDBOX)
+#if !defined(XP_WIN)
+#if defined(XP_MACOSX)
   , mAlreadyCheckedDevRepo(false)
-#endif
+#endif /* XP_MACOSX */
+  , mAlreadyCheckedAppDir(false)
+#endif /* ! XP_WIN */
 {
   mUseRemoteFileChannels = IsNeckoChild() &&
     Preferences::GetBool("extensions.webextensions.protocol.remote");
 }
 
 static inline ExtensionPolicyService&
 EPS()
 {
@@ -519,42 +519,126 @@ ExtensionProtocolHandler::SubstituteChan
     (*result)->SetLoadInfo(loadInfo);
   }
 
   channel.swap(*result);
 
   return NS_OK;
 }
 
-#if !defined(XP_WIN) && defined(MOZ_CONTENT_SANDBOX)
+Result<Ok, nsresult>
+ExtensionProtocolHandler::AllowExternalResource(nsIFile* aExtensionDir,
+                                                nsIFile* aRequestedFile,
+                                                bool* aResult)
+{
+  *aResult = false;
+
+#if defined(XP_WIN)
+  // On Windows, dev builds don't use symlinks so we never need to
+  // allow a resource from outside of the extension dir.
+  return Ok();
+#else
+  if (!mozilla::IsDevelopmentBuild()) {
+    return Ok();
+  }
+
+  // On Mac and Linux unpackaged dev builds, system extensions use
+  // symlinks to point to resources in the repo dir which we have to
+  // allow loading. Before we allow an unpacked extension to load a
+  // resource outside of the extension dir, we make sure the extension
+  // dir is within the app directory.
+  MOZ_TRY(AppDirContains(aExtensionDir, aResult));
+  if (!*aResult) {
+    return Ok();
+  }
+
+#if defined(XP_MACOSX)
+  // Additionally, on Mac dev builds, we make sure that the requested
+  // resource is within the repo dir. We don't perform this check on Linux
+  // because we don't have a reliable path to the repo dir on Linux.
+  MOZ_TRY(DevRepoContains(aRequestedFile, aResult));
+#endif /* XP_MACOSX */
+
+  return Ok();
+#endif /* defined(XP_WIN) */
+}
+
+#if defined(XP_MACOSX)
 // The |aRequestedFile| argument must already be Normalize()'d
 Result<Ok, nsresult>
 ExtensionProtocolHandler::DevRepoContains(nsIFile* aRequestedFile,
-                                          bool *aResult)
+                                          bool* aResult)
 {
+  MOZ_ASSERT(mozilla::IsDevelopmentBuild());
   MOZ_ASSERT(!IsNeckoChild());
   MOZ_ASSERT(aResult);
   *aResult = false;
 
-  // On the first invocation, set mDevRepo if this is a development build
+  // On the first invocation, set mDevRepo
   if (!mAlreadyCheckedDevRepo) {
     mAlreadyCheckedDevRepo = true;
-    if (mozilla::IsDevelopmentBuild()) {
-      NS_TRY(mozilla::GetRepoDir(getter_AddRefs(mDevRepo)));
+    NS_TRY(mozilla::GetRepoDir(getter_AddRefs(mDevRepo)));
+    if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) {
+      nsAutoCString repoPath;
+      Unused << mDevRepo->GetNativePath(repoPath);
+      LOG("Repo path: %s", repoPath.get());
     }
   }
 
   if (mDevRepo) {
-    // This is a development build
     NS_TRY(mDevRepo->Contains(aRequestedFile, aResult));
   }
 
   return Ok();
 }
-#endif /* !defined(XP_WIN) && defined(MOZ_CONTENT_SANDBOX) */
+#endif /* XP_MACOSX */
+
+#if !defined(XP_WIN)
+Result<Ok, nsresult>
+ExtensionProtocolHandler::AppDirContains(nsIFile* aExtensionDir,
+                                         bool* aResult)
+{
+  MOZ_ASSERT(mozilla::IsDevelopmentBuild());
+  MOZ_ASSERT(!IsNeckoChild());
+  MOZ_ASSERT(aResult);
+  *aResult = false;
+
+  // On the first invocation, set mAppDir
+  if (!mAlreadyCheckedAppDir) {
+    mAlreadyCheckedAppDir = true;
+    NS_TRY(NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(mAppDir)));
+    if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) {
+      nsAutoCString appDirPath;
+      Unused << mAppDir->GetNativePath(appDirPath);
+      LOG("AppDir path: %s", appDirPath.get());
+    }
+  }
+
+  if (mAppDir) {
+    NS_TRY(mAppDir->Contains(aExtensionDir, aResult));
+  }
+
+  return Ok();
+}
+#endif /* !defined(XP_WIN) */
+
+static void
+LogExternalResourceError(nsIFile* aExtensionDir, nsIFile* aRequestedFile)
+{
+  MOZ_ASSERT(aExtensionDir);
+  MOZ_ASSERT(aRequestedFile);
+
+  nsAutoCString extensionDirPath, requestedFilePath;
+  Unused << aExtensionDir->GetNativePath(extensionDirPath);
+  Unused << aRequestedFile->GetNativePath(requestedFilePath);
+
+  LOG("Rejecting external unpacked extension resource [%s] from "
+      "extension directory [%s]", requestedFilePath.get(),
+      extensionDirPath.get());
+}
 
 Result<nsCOMPtr<nsIInputStream>, nsresult>
 ExtensionProtocolHandler::NewStream(nsIURI* aChildURI, bool* aTerminateSender)
 {
   MOZ_ASSERT(!IsNeckoChild());
   NS_TRY(aChildURI ? NS_OK : NS_ERROR_INVALID_ARG);
   NS_TRY(aTerminateSender ? NS_OK : NS_ERROR_INVALID_ARG);
 
@@ -654,27 +738,22 @@ ExtensionProtocolHandler::NewStream(nsIU
       !widget::WinUtils::ResolveJunctionPointsAndSymLinks(requestedFile)) {
     return Err(NS_ERROR_FILE_ACCESS_DENIED);
   }
 #endif
 
   bool isResourceFromExtensionDir = false;
   NS_TRY(extensionDir->Contains(requestedFile, &isResourceFromExtensionDir));
   if (!isResourceFromExtensionDir) {
-#if defined(XP_WIN)
-    return Err(NS_ERROR_FILE_ACCESS_DENIED);
-#elif defined(MOZ_CONTENT_SANDBOX)
-    // On a dev build, we allow an unpacked resource that isn't
-    // from the extension directory as long as it is from the repo.
-    bool isResourceFromDevRepo = false;
-    MOZ_TRY(DevRepoContains(requestedFile, &isResourceFromDevRepo));
-    if (!isResourceFromDevRepo) {
+    bool isAllowed = false;
+    MOZ_TRY(AllowExternalResource(extensionDir, requestedFile, &isAllowed));
+    if (!isAllowed) {
+      LogExternalResourceError(extensionDir, requestedFile);
       return Err(NS_ERROR_FILE_ACCESS_DENIED);
     }
-#endif /* defined(XP_WIN) */
   }
 
   nsCOMPtr<nsIInputStream> inputStream;
   NS_TRY(NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
                                     requestedFile,
                                     PR_RDONLY,
                                     -1,
                                     nsIFileInputStream::DEFER_OPEN));
--- a/netwerk/protocol/res/ExtensionProtocolHandler.h
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.h
@@ -139,39 +139,83 @@ private:
    *        that remotes the JAR file access. The new channel encapsulates
    *        a request to the parent for the JAR file FD.
    */
   Result<Ok, nsresult> SubstituteRemoteJarChannel(nsIURI* aURI,
                                                   nsILoadInfo* aLoadinfo,
                                                   nsACString& aResolvedSpec,
                                                   nsIChannel** aRetVal);
 
-#if !defined(XP_WIN) && defined(MOZ_CONTENT_SANDBOX)
+  /**
+   * Sets the aResult outparam to true if this unpacked extension load of
+   * a resource that is outside the extension dir should be allowed. This
+   * is only allowed for system extensions on Mac and Linux dev builds.
+   *
+   * @param aExtensionDir the extension directory. Argument must be an
+   *        nsIFile for which Normalize() has already been called.
+   * @param aRequestedFile the requested web-accessible resource file. Argument
+   *        must be an nsIFile for which Normalize() has already been called.
+   * @param aResult outparam set to true when the load of the requested file
+   *        should be allowed.
+   */
+  Result<Ok, nsresult> AllowExternalResource(nsIFile* aExtensionDir,
+                                             nsIFile* aRequestedFile,
+                                             bool* aResult);
+
+#if defined(XP_MACOSX)
   /**
    * Sets the aResult outparam to true if we are a developer build with the
    * repo dir environment variable set and the requested file resides in the
    * repo dir. Developer builds may load system extensions with web-accessible
    * resources that are symlinks to files in the repo dir. This method is for
    * checking if an unpacked resource requested by the child is from the repo.
-   * The requested file must be already Normalized().
+   * The requested file must be already Normalized(). Only compile this for
+   * Mac because the repo dir isn't always available on Linux.
    *
    * @param aRequestedFile the requested web-accessible resource file. Argument
    *        must be an nsIFile for which Normalize() has already been called.
    * @param aResult outparam set to true on development builds when the
-   *        requested file resides in the repo
+   *        requested file resides in the repo.
    */
-  Result<Ok, nsresult> DevRepoContains(nsIFile* aRequestedFile, bool *aResult);
+  Result<Ok, nsresult> DevRepoContains(nsIFile* aRequestedFile, bool* aResult);
 
   // On development builds, this points to development repo. Lazily set.
   nsCOMPtr<nsIFile> mDevRepo;
 
   // Set to true once we've already tried to load the dev repo path,
   // allowing for lazy initialization of |mDevRepo|.
   bool mAlreadyCheckedDevRepo;
-#endif /* !defined(XP_WIN) && defined(MOZ_CONTENT_SANDBOX) */
+#endif /* XP_MACOSX */
+
+#if !defined(XP_WIN)
+  /**
+   * Sets the aResult outparam to true if we are a developer build and the
+   * provided directory is within the NS_GRE_DIR directory. Developer builds
+   * may load system extensions with web-accessible resources that are symlinks
+   * to files outside of the extension dir to the repo dir. This method is for
+   * checking if an extension directory is within NS_GRE_DIR. In that case, we
+   * consider the extension a system extension and allow it to use symlinks to
+   * resources outside of the extension dir. This exception is only applied
+   * to loads for unpacked extensions in unpackaged developer builds.
+   * The requested dir must be already Normalized().
+   *
+   * @param aExtensionDir the extension directory. Argument must be an
+   *        nsIFile for which Normalize() has already been called.
+   * @param aResult outparam set to true on development builds when the
+   *        requested file resides in the repo.
+   */
+  Result<Ok, nsresult> AppDirContains(nsIFile* aExtensionDir, bool* aResult);
+
+  // On development builds, cache the NS_GRE_DIR repo. Lazily set.
+  nsCOMPtr<nsIFile> mAppDir;
+
+  // Set to true once we've already read the AppDir, allowing for lazy
+  // initialization of |mAppDir|.
+  bool mAlreadyCheckedAppDir;
+#endif /* !defined(XP_WIN) */
 
   // Used for opening JAR files off the main thread when we just need to
   // obtain a file descriptor to send back to the child.
   RefPtr<mozilla::LazyIdleThread> mFileOpenerThread;
 
   // To allow parent IPDL actors to invoke methods on this handler when
   // handling moz-extension requests from the child.
   static StaticRefPtr<ExtensionProtocolHandler> sSingleton;