forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			215 lines
		
	
	
	
		
			6.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			215 lines
		
	
	
	
		
			6.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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 "nsIScriptError.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;
 | |
|     }
 | |
| 
 | |
|     nsAutoCString allow;
 | |
|     allow.Assign(',');
 | |
|     nsAutoCString pref;
 | |
|     Preferences::GetCString("dom.ipc.cpows.allow-cpows-in-compat-addons", pref);
 | |
|     allow.Append(pref);
 | |
|     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()
 | |
| {
 | |
|     // CPOWs 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();
 | |
|     }
 | |
| }
 | 
