summaryrefslogtreecommitdiffstats
path: root/js/ipc/JavaScriptParent.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/ipc/JavaScriptParent.cpp')
-rw-r--r--js/ipc/JavaScriptParent.cpp212
1 files changed, 212 insertions, 0 deletions
diff --git a/js/ipc/JavaScriptParent.cpp b/js/ipc/JavaScriptParent.cpp
new file mode 100644
index 000000000..7fe92d662
--- /dev/null
+++ b/js/ipc/JavaScriptParent.cpp
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et 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 "JavaScriptParent.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsJSUtils.h"
+#include "jsfriendapi.h"
+#include "jswrapper.h"
+#include "js/Proxy.h"
+#include "js/HeapAPI.h"
+#include "xpcprivate.h"
+#include "mozilla/Casting.h"
+#include "mozilla/Telemetry.h"
+#include "nsAutoPtr.h"
+
+using namespace js;
+using namespace JS;
+using namespace mozilla;
+using namespace mozilla::jsipc;
+using namespace mozilla::dom;
+
+static void
+TraceParent(JSTracer* trc, void* data)
+{
+ static_cast<JavaScriptParent*>(data)->trace(trc);
+}
+
+JavaScriptParent::~JavaScriptParent()
+{
+ JS_RemoveExtraGCRootsTracer(danger::GetJSContext(), TraceParent, this);
+}
+
+bool
+JavaScriptParent::init()
+{
+ if (!WrapperOwner::init())
+ return false;
+
+ JS_AddExtraGCRootsTracer(danger::GetJSContext(), TraceParent, this);
+ return true;
+}
+
+static bool
+ForbidUnsafeBrowserCPOWs()
+{
+ static bool result;
+ static bool cached = false;
+ if (!cached) {
+ cached = true;
+ Preferences::AddBoolVarCache(&result, "dom.ipc.cpows.forbid-unsafe-from-browser", false);
+ }
+ return result;
+}
+
+// Should we allow CPOWs in aAddonId, even though it's marked as multiprocess
+// compatible? This is controlled by two prefs:
+// If dom.ipc.cpows.forbid-cpows-in-compat-addons is false, then we allow the CPOW.
+// If dom.ipc.cpows.forbid-cpows-in-compat-addons is true:
+// We check if aAddonId is listed in dom.ipc.cpows.allow-cpows-in-compat-addons
+// (which should be a comma-separated string). If it's present there, we allow
+// the CPOW. Otherwise we forbid the CPOW.
+static bool
+ForbidCPOWsInCompatibleAddon(const nsACString& aAddonId)
+{
+ bool forbid = Preferences::GetBool("dom.ipc.cpows.forbid-cpows-in-compat-addons", false);
+ if (!forbid) {
+ return false;
+ }
+
+ nsCString allow;
+ allow.Assign(',');
+ allow.Append(Preferences::GetCString("dom.ipc.cpows.allow-cpows-in-compat-addons"));
+ allow.Append(',');
+
+ nsCString searchString(",");
+ searchString.Append(aAddonId);
+ searchString.Append(',');
+ return allow.Find(searchString) == kNotFound;
+}
+
+bool
+JavaScriptParent::allowMessage(JSContext* cx)
+{
+ // If we're running browser code, then we allow all safe CPOWs and forbid
+ // unsafe CPOWs based on a pref (which defaults to forbidden). We also allow
+ // CPOWs unconditionally in selected globals (based on
+ // Cu.permitCPOWsInScope).
+ //
+ // If we're running add-on code, then we check if the add-on is multiprocess
+ // compatible (which eventually translates to a given setting of allowCPOWs
+ // on the scopw). If it's not compatible, then we allow the CPOW but
+ // warn. If it is marked as compatible, then we check the
+ // ForbidCPOWsInCompatibleAddon; see the comment there.
+
+ MessageChannel* channel = GetIPCChannel();
+ bool isSafe = channel->IsInTransaction();
+
+ bool warn = !isSafe;
+ nsIGlobalObject* global = dom::GetIncumbentGlobal();
+ JSObject* jsGlobal = global ? global->GetGlobalJSObject() : nullptr;
+ if (jsGlobal) {
+ JSAutoCompartment ac(cx, jsGlobal);
+ JSAddonId* addonId = JS::AddonIdOfObject(jsGlobal);
+
+ if (!xpc::CompartmentPrivate::Get(jsGlobal)->allowCPOWs) {
+ if (!addonId && ForbidUnsafeBrowserCPOWs() && !isSafe) {
+ Telemetry::Accumulate(Telemetry::BROWSER_SHIM_USAGE_BLOCKED, 1);
+ JS_ReportErrorASCII(cx, "unsafe CPOW usage forbidden");
+ return false;
+ }
+
+ if (addonId) {
+ JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(addonId));
+ nsString addonIdString;
+ AssignJSFlatString(addonIdString, flat);
+ NS_ConvertUTF16toUTF8 addonIdCString(addonIdString);
+ Telemetry::Accumulate(Telemetry::ADDON_FORBIDDEN_CPOW_USAGE, addonIdCString);
+
+ if (ForbidCPOWsInCompatibleAddon(addonIdCString)) {
+ JS_ReportErrorASCII(cx, "CPOW usage forbidden in this add-on");
+ return false;
+ }
+
+ warn = true;
+ }
+ }
+ }
+
+ if (!warn)
+ return true;
+
+ static bool disableUnsafeCPOWWarnings = PR_GetEnv("DISABLE_UNSAFE_CPOW_WARNINGS");
+ if (!disableUnsafeCPOWWarnings) {
+ nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ if (console && cx) {
+ nsAutoString filename;
+ uint32_t lineno = 0, column = 0;
+ nsJSUtils::GetCallingLocation(cx, filename, &lineno, &column);
+ nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ error->Init(NS_LITERAL_STRING("unsafe/forbidden CPOW usage"), filename,
+ EmptyString(), lineno, column,
+ nsIScriptError::warningFlag, "chrome javascript");
+ console->LogMessage(error);
+ } else {
+ NS_WARNING("Unsafe synchronous IPC message");
+ }
+ }
+
+ return true;
+}
+
+void
+JavaScriptParent::trace(JSTracer* trc)
+{
+ objects_.trace(trc);
+ unwaivedObjectIds_.trace(trc);
+ waivedObjectIds_.trace(trc);
+}
+
+JSObject*
+JavaScriptParent::scopeForTargetObjects()
+{
+ // CPWOWs from the child need to point into the parent's unprivileged junk
+ // scope so that a compromised child cannot compromise the parent. In
+ // practice, this means that a child process can only (a) hold parent
+ // objects alive and (b) invoke them if they are callable.
+ return xpc::UnprivilegedJunkScope();
+}
+
+void
+JavaScriptParent::afterProcessTask()
+{
+ if (savedNextCPOWNumber_ == nextCPOWNumber_)
+ return;
+
+ savedNextCPOWNumber_ = nextCPOWNumber_;
+
+ MOZ_ASSERT(nextCPOWNumber_ > 0);
+ if (active())
+ Unused << SendDropTemporaryStrongReferences(nextCPOWNumber_ - 1);
+}
+
+PJavaScriptParent*
+mozilla::jsipc::NewJavaScriptParent()
+{
+ JavaScriptParent* parent = new JavaScriptParent();
+ if (!parent->init()) {
+ delete parent;
+ return nullptr;
+ }
+ return parent;
+}
+
+void
+mozilla::jsipc::ReleaseJavaScriptParent(PJavaScriptParent* parent)
+{
+ static_cast<JavaScriptParent*>(parent)->decref();
+}
+
+void
+mozilla::jsipc::AfterProcessTask()
+{
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ if (PJavaScriptParent* p = LoneManagedOrNullAsserts(cp->ManagedPJavaScriptParent()))
+ static_cast<JavaScriptParent*>(p)->afterProcessTask();
+ }
+}