Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* vim: set ts=8 sts=4 et sw=4 tw=99: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "AccessCheck.h"
8 :
9 : #include "nsJSPrincipals.h"
10 : #include "BasePrincipal.h"
11 : #include "nsDOMWindowList.h"
12 : #include "nsGlobalWindow.h"
13 :
14 : #include "XPCWrapper.h"
15 : #include "XrayWrapper.h"
16 : #include "FilteringWrapper.h"
17 :
18 : #include "jsfriendapi.h"
19 : #include "mozilla/ErrorResult.h"
20 : #include "mozilla/dom/BindingUtils.h"
21 : #include "mozilla/dom/LocationBinding.h"
22 : #include "mozilla/dom/WindowBinding.h"
23 : #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
24 : #include "nsJSUtils.h"
25 : #include "xpcprivate.h"
26 :
27 : using namespace mozilla;
28 : using namespace JS;
29 : using namespace js;
30 :
31 : namespace xpc {
32 :
33 : nsIPrincipal*
34 0 : GetCompartmentPrincipal(JS::Compartment* compartment)
35 : {
36 0 : return nsJSPrincipals::get(JS_GetCompartmentPrincipals(compartment));
37 : }
38 :
39 : nsIPrincipal*
40 0 : GetRealmPrincipal(JS::Realm* realm)
41 : {
42 0 : return nsJSPrincipals::get(JS::GetRealmPrincipals(realm));
43 : }
44 :
45 : nsIPrincipal*
46 0 : GetObjectPrincipal(JSObject* obj)
47 : {
48 0 : return GetCompartmentPrincipal(js::GetObjectCompartment(obj));
49 : }
50 :
51 : // Does the principal of compartment a subsume the principal of compartment b?
52 : bool
53 0 : AccessCheck::subsumes(JS::Compartment* a, JS::Compartment* b)
54 : {
55 0 : nsIPrincipal* aprin = GetCompartmentPrincipal(a);
56 0 : nsIPrincipal* bprin = GetCompartmentPrincipal(b);
57 0 : return BasePrincipal::Cast(aprin)->FastSubsumes(bprin);
58 : }
59 :
60 : bool
61 0 : AccessCheck::subsumes(JSObject* a, JSObject* b)
62 : {
63 0 : return subsumes(js::GetObjectCompartment(a), js::GetObjectCompartment(b));
64 : }
65 :
66 : // Same as above, but considering document.domain.
67 : bool
68 0 : AccessCheck::subsumesConsideringDomain(JS::Compartment* a, JS::Compartment* b)
69 : {
70 0 : MOZ_ASSERT(OriginAttributes::IsRestrictOpenerAccessForFPI());
71 0 : nsIPrincipal* aprin = GetCompartmentPrincipal(a);
72 0 : nsIPrincipal* bprin = GetCompartmentPrincipal(b);
73 0 : return BasePrincipal::Cast(aprin)->FastSubsumesConsideringDomain(bprin);
74 : }
75 :
76 : bool
77 0 : AccessCheck::subsumesConsideringDomainIgnoringFPD(JS::Compartment* a,
78 : JS::Compartment* b)
79 : {
80 0 : MOZ_ASSERT(!OriginAttributes::IsRestrictOpenerAccessForFPI());
81 0 : nsIPrincipal* aprin = GetCompartmentPrincipal(a);
82 0 : nsIPrincipal* bprin = GetCompartmentPrincipal(b);
83 0 : return BasePrincipal::Cast(aprin)->FastSubsumesConsideringDomainIgnoringFPD(bprin);
84 : }
85 :
86 : // Does the compartment of the wrapper subsumes the compartment of the wrappee?
87 : bool
88 0 : AccessCheck::wrapperSubsumes(JSObject* wrapper)
89 : {
90 0 : MOZ_ASSERT(js::IsWrapper(wrapper));
91 0 : JSObject* wrapped = js::UncheckedUnwrap(wrapper);
92 0 : return AccessCheck::subsumes(js::GetObjectCompartment(wrapper),
93 0 : js::GetObjectCompartment(wrapped));
94 : }
95 :
96 : bool
97 0 : AccessCheck::isChrome(JS::Compartment* compartment)
98 : {
99 0 : nsIPrincipal* principal = GetCompartmentPrincipal(compartment);
100 0 : return nsXPConnect::SystemPrincipal() == principal;
101 : }
102 :
103 : bool
104 0 : AccessCheck::isChrome(JSObject* obj)
105 : {
106 0 : return isChrome(js::GetObjectCompartment(obj));
107 : }
108 :
109 : nsIPrincipal*
110 0 : AccessCheck::getPrincipal(JS::Compartment* compartment)
111 : {
112 0 : return GetCompartmentPrincipal(compartment);
113 : }
114 :
115 : // Hardcoded policy for cross origin property access. See the HTML5 Spec.
116 : static bool
117 0 : IsPermitted(CrossOriginObjectType type, JSFlatString* prop, bool set)
118 : {
119 0 : size_t propLength = JS_GetStringLength(JS_FORGET_STRING_FLATNESS(prop));
120 0 : if (!propLength)
121 : return false;
122 :
123 0 : char16_t propChar0 = JS_GetFlatStringCharAt(prop, 0);
124 0 : if (type == CrossOriginLocation)
125 0 : return dom::LocationBinding::IsPermitted(prop, propChar0, set);
126 0 : if (type == CrossOriginWindow)
127 0 : return dom::WindowBinding::IsPermitted(prop, propChar0, set);
128 :
129 : return false;
130 : }
131 :
132 : static bool
133 0 : IsFrameId(JSContext* cx, JSObject* obj, jsid idArg)
134 : {
135 0 : MOZ_ASSERT(!js::IsWrapper(obj));
136 0 : RootedId id(cx, idArg);
137 :
138 0 : nsGlobalWindowInner* win = WindowOrNull(obj);
139 0 : if (!win) {
140 : return false;
141 : }
142 :
143 0 : nsDOMWindowList* col = win->GetFrames();
144 0 : if (!col) {
145 : return false;
146 : }
147 :
148 0 : nsCOMPtr<mozIDOMWindowProxy> domwin;
149 0 : if (JSID_IS_INT(id)) {
150 0 : domwin = col->IndexedGetter(JSID_TO_INT(id));
151 0 : } else if (JSID_IS_STRING(id)) {
152 0 : nsAutoJSString idAsString;
153 0 : if (!idAsString.init(cx, JSID_TO_STRING(id))) {
154 0 : return false;
155 : }
156 0 : domwin = col->NamedItem(idAsString);
157 : }
158 :
159 0 : return domwin != nullptr;
160 : }
161 :
162 : CrossOriginObjectType
163 0 : IdentifyCrossOriginObject(JSObject* obj)
164 : {
165 0 : obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
166 0 : const js::Class* clasp = js::GetObjectClass(obj);
167 :
168 2 : if (clasp->name[0] == 'L' && !strcmp(clasp->name, "Location"))
169 : return CrossOriginLocation;
170 2 : if (clasp->name[0] == 'W' && !strcmp(clasp->name, "Window"))
171 : return CrossOriginWindow;
172 :
173 0 : return CrossOriginOpaque;
174 : }
175 :
176 : bool
177 0 : AccessCheck::isCrossOriginAccessPermitted(JSContext* cx, HandleObject wrapper, HandleId id,
178 : Wrapper::Action act)
179 : {
180 0 : if (act == Wrapper::CALL)
181 : return false;
182 :
183 0 : if (act == Wrapper::ENUMERATE)
184 : return true;
185 :
186 : // For the case of getting a property descriptor, we allow if either GET or SET
187 : // is allowed, and rely on FilteringWrapper to filter out any disallowed accessors.
188 0 : if (act == Wrapper::GET_PROPERTY_DESCRIPTOR) {
189 0 : return isCrossOriginAccessPermitted(cx, wrapper, id, Wrapper::GET) ||
190 0 : isCrossOriginAccessPermitted(cx, wrapper, id, Wrapper::SET);
191 : }
192 :
193 0 : RootedObject obj(cx, js::UncheckedUnwrap(wrapper, /* stopAtWindowProxy = */ false));
194 0 : CrossOriginObjectType type = IdentifyCrossOriginObject(obj);
195 0 : if (JSID_IS_STRING(id)) {
196 0 : if (IsPermitted(type, JSID_TO_FLAT_STRING(id), act == Wrapper::SET))
197 : return true;
198 : }
199 :
200 0 : if (type != CrossOriginOpaque &&
201 0 : IsCrossOriginWhitelistedProp(cx, id)) {
202 : // We always allow access to "then", @@toStringTag, @@hasInstance, and
203 : // @@isConcatSpreadable. But then we nerf them to be a value descriptor
204 : // with value undefined in CrossOriginXrayWrapper.
205 : return true;
206 : }
207 :
208 0 : if (act != Wrapper::GET)
209 : return false;
210 :
211 : // Check for frame IDs. If we're resolving named frames, make sure to only
212 : // resolve ones that don't shadow native properties. See bug 860494.
213 0 : if (type == CrossOriginWindow) {
214 0 : if (JSID_IS_STRING(id)) {
215 0 : bool wouldShadow = false;
216 0 : if (!XrayUtils::HasNativeProperty(cx, wrapper, id, &wouldShadow) ||
217 : wouldShadow)
218 : {
219 : // If the named subframe matches the name of a DOM constructor,
220 : // the global resolve triggered by the HasNativeProperty call
221 : // above will try to perform a CheckedUnwrap on |wrapper|, and
222 : // throw a security error if it fails. That exception isn't
223 : // really useful for our callers, so we silence it and just
224 : // deny access to the property (since it matched a builtin).
225 : //
226 : // Note that this would be a problem if the resolve code ever
227 : // tried to CheckedUnwrap the wrapper _before_ concluding that
228 : // the name corresponds to a builtin global property, since it
229 : // would mean that we'd never permit cross-origin named subframe
230 : // access (something we regrettably need to support).
231 0 : JS_ClearPendingException(cx);
232 0 : return false;
233 : }
234 : }
235 0 : return IsFrameId(cx, obj, id);
236 : }
237 : return false;
238 : }
239 :
240 : bool
241 0 : AccessCheck::checkPassToPrivilegedCode(JSContext* cx, HandleObject wrapper, HandleValue v)
242 : {
243 : // Primitives are fine.
244 0 : if (!v.isObject())
245 : return true;
246 0 : RootedObject obj(cx, &v.toObject());
247 :
248 : // Non-wrappers are fine.
249 0 : if (!js::IsWrapper(obj))
250 : return true;
251 :
252 : // CPOWs use COWs (in the unprivileged junk scope) for all child->parent
253 : // references. Without this test, the child process wouldn't be able to
254 : // pass any objects at all to CPOWs.
255 0 : if (mozilla::jsipc::IsWrappedCPOW(obj) &&
256 0 : js::GetObjectCompartment(wrapper) == js::GetObjectCompartment(xpc::UnprivilegedJunkScope()) &&
257 0 : XRE_IsParentProcess())
258 : {
259 : return true;
260 : }
261 :
262 : // Same-origin wrappers are fine.
263 0 : if (AccessCheck::wrapperSubsumes(obj))
264 : return true;
265 :
266 : // Badness.
267 0 : JS_ReportErrorASCII(cx, "Permission denied to pass object to privileged code");
268 0 : return false;
269 : }
270 :
271 : bool
272 0 : AccessCheck::checkPassToPrivilegedCode(JSContext* cx, HandleObject wrapper, const CallArgs& args)
273 : {
274 0 : if (!checkPassToPrivilegedCode(cx, wrapper, args.thisv()))
275 : return false;
276 0 : for (size_t i = 0; i < args.length(); ++i) {
277 0 : if (!checkPassToPrivilegedCode(cx, wrapper, args[i]))
278 : return false;
279 : }
280 : return true;
281 : }
282 :
283 : void
284 0 : AccessCheck::reportCrossOriginDenial(JSContext* cx, JS::HandleId id,
285 : const nsACString& accessType)
286 : {
287 : // This function exists because we want to report DOM SecurityErrors, not JS
288 : // Errors, when denying access on cross-origin DOM objects. It's
289 : // conceptually pretty similar to
290 : // AutoEnterPolicy::reportErrorIfExceptionIsNotPending.
291 0 : if (JS_IsExceptionPending(cx)) {
292 0 : return;
293 : }
294 :
295 0 : nsAutoCString message;
296 0 : if (JSID_IS_VOID(id)) {
297 0 : message = NS_LITERAL_CSTRING("Permission denied to access object");
298 : } else {
299 : // We want to use JS_ValueToSource here, because that most closely
300 : // matches what AutoEnterPolicy::reportErrorIfExceptionIsNotPending
301 : // does.
302 0 : JS::RootedValue idVal(cx, js::IdToValue(id));
303 0 : nsAutoJSString propName;
304 0 : JS::RootedString idStr(cx, JS_ValueToSource(cx, idVal));
305 0 : if (!idStr || !propName.init(cx, idStr)) {
306 0 : return;
307 : }
308 0 : message = NS_LITERAL_CSTRING("Permission denied to ") +
309 0 : accessType +
310 0 : NS_LITERAL_CSTRING(" property ") +
311 0 : NS_ConvertUTF16toUTF8(propName) +
312 0 : NS_LITERAL_CSTRING(" on cross-origin object");
313 : }
314 0 : ErrorResult rv;
315 0 : rv.ThrowDOMException(NS_ERROR_DOM_SECURITY_ERR, message);
316 0 : MOZ_ALWAYS_TRUE(rv.MaybeSetPendingException(cx));
317 : }
318 :
319 : bool
320 0 : OpaqueWithSilentFailing::deny(JSContext* cx, js::Wrapper::Action act, HandleId id,
321 : bool mayThrow)
322 : {
323 : // Fail silently for GET, ENUMERATE, and GET_PROPERTY_DESCRIPTOR.
324 0 : if (act == js::Wrapper::GET || act == js::Wrapper::ENUMERATE ||
325 : act == js::Wrapper::GET_PROPERTY_DESCRIPTOR)
326 : {
327 : // Note that ReportWrapperDenial doesn't do any _exception_ reporting,
328 : // so we want to do this regardless of the value of mayThrow.
329 : return ReportWrapperDenial(cx, id, WrapperDenialForCOW,
330 0 : "Access to privileged JS object not permitted");
331 : }
332 :
333 : return false;
334 : }
335 :
336 : } // namespace xpc
|