Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=4 sw=4 et tw=80:
3 : *
4 : * This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 :
8 : #include "JavaScriptParent.h"
9 : #include "mozilla/dom/ContentParent.h"
10 : #include "mozilla/dom/ScriptSettings.h"
11 : #include "nsJSUtils.h"
12 : #include "nsIScriptError.h"
13 : #include "jsfriendapi.h"
14 : #include "jswrapper.h"
15 : #include "js/Proxy.h"
16 : #include "js/HeapAPI.h"
17 : #include "xpcprivate.h"
18 : #include "mozilla/Casting.h"
19 : #include "mozilla/Telemetry.h"
20 : #include "nsAutoPtr.h"
21 :
22 : using namespace js;
23 : using namespace JS;
24 : using namespace mozilla;
25 : using namespace mozilla::jsipc;
26 : using namespace mozilla::dom;
27 :
28 : static void
29 1 : TraceParent(JSTracer* trc, void* data)
30 : {
31 1 : static_cast<JavaScriptParent*>(data)->trace(trc);
32 1 : }
33 :
34 0 : JavaScriptParent::~JavaScriptParent()
35 : {
36 0 : JS_RemoveExtraGCRootsTracer(danger::GetJSContext(), TraceParent, this);
37 0 : }
38 :
39 : bool
40 1 : JavaScriptParent::init()
41 : {
42 1 : if (!WrapperOwner::init())
43 0 : return false;
44 :
45 1 : JS_AddExtraGCRootsTracer(danger::GetJSContext(), TraceParent, this);
46 1 : return true;
47 : }
48 :
49 : static bool
50 0 : ForbidUnsafeBrowserCPOWs()
51 : {
52 : static bool result;
53 : static bool cached = false;
54 0 : if (!cached) {
55 0 : cached = true;
56 0 : Preferences::AddBoolVarCache(&result, "dom.ipc.cpows.forbid-unsafe-from-browser", false);
57 : }
58 0 : return result;
59 : }
60 :
61 : // Should we allow CPOWs in aAddonId, even though it's marked as multiprocess
62 : // compatible? This is controlled by two prefs:
63 : // If dom.ipc.cpows.forbid-cpows-in-compat-addons is false, then we allow the CPOW.
64 : // If dom.ipc.cpows.forbid-cpows-in-compat-addons is true:
65 : // We check if aAddonId is listed in dom.ipc.cpows.allow-cpows-in-compat-addons
66 : // (which should be a comma-separated string). If it's present there, we allow
67 : // the CPOW. Otherwise we forbid the CPOW.
68 : static bool
69 0 : ForbidCPOWsInCompatibleAddon(const nsACString& aAddonId)
70 : {
71 0 : bool forbid = Preferences::GetBool("dom.ipc.cpows.forbid-cpows-in-compat-addons", false);
72 0 : if (!forbid) {
73 0 : return false;
74 : }
75 :
76 0 : nsCString allow;
77 0 : allow.Assign(',');
78 0 : allow.Append(Preferences::GetCString("dom.ipc.cpows.allow-cpows-in-compat-addons"));
79 0 : allow.Append(',');
80 :
81 0 : nsCString searchString(",");
82 0 : searchString.Append(aAddonId);
83 0 : searchString.Append(',');
84 0 : return allow.Find(searchString) == kNotFound;
85 : }
86 :
87 : bool
88 0 : JavaScriptParent::allowMessage(JSContext* cx)
89 : {
90 : // If we're running browser code, then we allow all safe CPOWs and forbid
91 : // unsafe CPOWs based on a pref (which defaults to forbidden). We also allow
92 : // CPOWs unconditionally in selected globals (based on
93 : // Cu.permitCPOWsInScope).
94 : //
95 : // If we're running add-on code, then we check if the add-on is multiprocess
96 : // compatible (which eventually translates to a given setting of allowCPOWs
97 : // on the scopw). If it's not compatible, then we allow the CPOW but
98 : // warn. If it is marked as compatible, then we check the
99 : // ForbidCPOWsInCompatibleAddon; see the comment there.
100 :
101 0 : MessageChannel* channel = GetIPCChannel();
102 0 : bool isSafe = channel->IsInTransaction();
103 :
104 0 : bool warn = !isSafe;
105 0 : nsIGlobalObject* global = dom::GetIncumbentGlobal();
106 0 : JSObject* jsGlobal = global ? global->GetGlobalJSObject() : nullptr;
107 0 : if (jsGlobal) {
108 0 : JSAutoCompartment ac(cx, jsGlobal);
109 0 : JSAddonId* addonId = JS::AddonIdOfObject(jsGlobal);
110 :
111 0 : if (!xpc::CompartmentPrivate::Get(jsGlobal)->allowCPOWs) {
112 0 : if (!addonId && ForbidUnsafeBrowserCPOWs() && !isSafe) {
113 0 : Telemetry::Accumulate(Telemetry::BROWSER_SHIM_USAGE_BLOCKED, 1);
114 0 : JS_ReportErrorASCII(cx, "unsafe CPOW usage forbidden");
115 0 : return false;
116 : }
117 :
118 0 : if (addonId) {
119 0 : JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(addonId));
120 0 : nsString addonIdString;
121 0 : AssignJSFlatString(addonIdString, flat);
122 0 : NS_ConvertUTF16toUTF8 addonIdCString(addonIdString);
123 0 : Telemetry::Accumulate(Telemetry::ADDON_FORBIDDEN_CPOW_USAGE, addonIdCString);
124 :
125 0 : if (ForbidCPOWsInCompatibleAddon(addonIdCString)) {
126 0 : JS_ReportErrorASCII(cx, "CPOW usage forbidden in this add-on");
127 0 : return false;
128 : }
129 :
130 0 : warn = true;
131 : }
132 : }
133 : }
134 :
135 0 : if (!warn)
136 0 : return true;
137 :
138 0 : static bool disableUnsafeCPOWWarnings = PR_GetEnv("DISABLE_UNSAFE_CPOW_WARNINGS");
139 0 : if (!disableUnsafeCPOWWarnings) {
140 0 : nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
141 0 : if (console && cx) {
142 0 : nsAutoString filename;
143 0 : uint32_t lineno = 0, column = 0;
144 0 : nsJSUtils::GetCallingLocation(cx, filename, &lineno, &column);
145 0 : nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
146 0 : error->Init(NS_LITERAL_STRING("unsafe/forbidden CPOW usage"), filename,
147 0 : EmptyString(), lineno, column,
148 0 : nsIScriptError::warningFlag, "chrome javascript");
149 0 : console->LogMessage(error);
150 : } else {
151 0 : NS_WARNING("Unsafe synchronous IPC message");
152 : }
153 : }
154 :
155 0 : return true;
156 : }
157 :
158 : void
159 1 : JavaScriptParent::trace(JSTracer* trc)
160 : {
161 1 : objects_.trace(trc);
162 1 : unwaivedObjectIds_.trace(trc);
163 1 : waivedObjectIds_.trace(trc);
164 1 : }
165 :
166 : JSObject*
167 0 : JavaScriptParent::scopeForTargetObjects()
168 : {
169 : // CPOWs from the child need to point into the parent's unprivileged junk
170 : // scope so that a compromised child cannot compromise the parent. In
171 : // practice, this means that a child process can only (a) hold parent
172 : // objects alive and (b) invoke them if they are callable.
173 0 : return xpc::UnprivilegedJunkScope();
174 : }
175 :
176 : void
177 574 : JavaScriptParent::afterProcessTask()
178 : {
179 574 : if (savedNextCPOWNumber_ == nextCPOWNumber_)
180 571 : return;
181 :
182 3 : savedNextCPOWNumber_ = nextCPOWNumber_;
183 :
184 3 : MOZ_ASSERT(nextCPOWNumber_ > 0);
185 3 : if (active())
186 3 : Unused << SendDropTemporaryStrongReferences(nextCPOWNumber_ - 1);
187 : }
188 :
189 : PJavaScriptParent*
190 1 : mozilla::jsipc::NewJavaScriptParent()
191 : {
192 1 : JavaScriptParent* parent = new JavaScriptParent();
193 1 : if (!parent->init()) {
194 0 : delete parent;
195 0 : return nullptr;
196 : }
197 1 : return parent;
198 : }
199 :
200 : void
201 0 : mozilla::jsipc::ReleaseJavaScriptParent(PJavaScriptParent* parent)
202 : {
203 0 : static_cast<JavaScriptParent*>(parent)->decref();
204 0 : }
205 :
206 : void
207 1227 : mozilla::jsipc::AfterProcessTask()
208 : {
209 2080 : for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
210 853 : if (PJavaScriptParent* p = LoneManagedOrNullAsserts(cp->ManagedPJavaScriptParent()))
211 574 : static_cast<JavaScriptParent*>(p)->afterProcessTask();
212 : }
213 1227 : }
|