Bug 1386832 - Part 2 - Make Linux dev build security check exception not depend on MOZ_DEVELOPER_REPO. r=jimm
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
--- 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,128 @@ ExtensionProtocolHandler::SubstituteChan
(*result)->SetLoadInfo(loadInfo);
}
channel.swap(*result);
return NS_OK;
}
-#if !defined(XP_WIN) && defined(MOZ_CONTENT_SANDBOX)
-// The |aRequestedFile| argument must already be Normalize()'d
Result<Ok, nsresult>
-ExtensionProtocolHandler::DevRepoContains(nsIFile* aRequestedFile,
- bool *aResult)
+ExtensionProtocolHandler::AllowExternalResource(nsIFile* aExtensionDir,
+ nsIFile* aRequestedFile,
+ bool* aResult)
{
MOZ_ASSERT(!IsNeckoChild());
MOZ_ASSERT(aResult);
*aResult = false;
- // On the first invocation, set mDevRepo if this is a development build
+#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)
+{
+ MOZ_ASSERT(mozilla::IsDevelopmentBuild());
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_ASSERT(aResult);
+ *aResult = false;
+
+ // 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 +740,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;