1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 "nsGlobalWindow.h"
11 
12 #include "XPCWrapper.h"
13 #include "XrayWrapper.h"
14 #include "FilteringWrapper.h"
15 
16 #include "jsfriendapi.h"
17 #include "js/Object.h"  // JS::GetClass, JS::GetCompartment
18 #include "mozilla/BasePrincipal.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 "nsJSUtils.h"
24 #include "xpcprivate.h"
25 
26 using namespace mozilla;
27 using namespace JS;
28 using namespace js;
29 
30 namespace xpc {
31 
GetRealmPrincipal(JS::Realm * realm)32 BasePrincipal* GetRealmPrincipal(JS::Realm* realm) {
33   return BasePrincipal::Cast(
34       nsJSPrincipals::get(JS::GetRealmPrincipals(realm)));
35 }
36 
GetObjectPrincipal(JSObject * obj)37 nsIPrincipal* GetObjectPrincipal(JSObject* obj) {
38   return GetRealmPrincipal(js::GetNonCCWObjectRealm(obj));
39 }
40 
subsumes(JSObject * a,JSObject * b)41 bool AccessCheck::subsumes(JSObject* a, JSObject* b) {
42   return CompartmentOriginInfo::Subsumes(JS::GetCompartment(a),
43                                          JS::GetCompartment(b));
44 }
45 
46 // Same as above, but considering document.domain.
subsumesConsideringDomain(JS::Realm * a,JS::Realm * b)47 bool AccessCheck::subsumesConsideringDomain(JS::Realm* a, JS::Realm* b) {
48   MOZ_ASSERT(OriginAttributes::IsRestrictOpenerAccessForFPI());
49   BasePrincipal* aprin = GetRealmPrincipal(a);
50   BasePrincipal* bprin = GetRealmPrincipal(b);
51   return aprin->FastSubsumesConsideringDomain(bprin);
52 }
53 
subsumesConsideringDomainIgnoringFPD(JS::Realm * a,JS::Realm * b)54 bool AccessCheck::subsumesConsideringDomainIgnoringFPD(JS::Realm* a,
55                                                        JS::Realm* b) {
56   MOZ_ASSERT(!OriginAttributes::IsRestrictOpenerAccessForFPI());
57   BasePrincipal* aprin = GetRealmPrincipal(a);
58   BasePrincipal* bprin = GetRealmPrincipal(b);
59   return aprin->FastSubsumesConsideringDomainIgnoringFPD(bprin);
60 }
61 
62 // Does the compartment of the wrapper subsumes the compartment of the wrappee?
wrapperSubsumes(JSObject * wrapper)63 bool AccessCheck::wrapperSubsumes(JSObject* wrapper) {
64   MOZ_ASSERT(js::IsWrapper(wrapper));
65   JSObject* wrapped = js::UncheckedUnwrap(wrapper);
66   return CompartmentOriginInfo::Subsumes(JS::GetCompartment(wrapper),
67                                          JS::GetCompartment(wrapped));
68 }
69 
isChrome(JS::Compartment * compartment)70 bool AccessCheck::isChrome(JS::Compartment* compartment) {
71   return js::IsSystemCompartment(compartment);
72 }
73 
isChrome(JS::Realm * realm)74 bool AccessCheck::isChrome(JS::Realm* realm) {
75   return isChrome(JS::GetCompartmentForRealm(realm));
76 }
77 
isChrome(JSObject * obj)78 bool AccessCheck::isChrome(JSObject* obj) {
79   return isChrome(JS::GetCompartment(obj));
80 }
81 
IsCrossOriginAccessibleObject(JSObject * obj)82 bool IsCrossOriginAccessibleObject(JSObject* obj) {
83   obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
84   const JSClass* clasp = JS::GetClass(obj);
85 
86   return (clasp->name[0] == 'L' && !strcmp(clasp->name, "Location")) ||
87          (clasp->name[0] == 'W' && !strcmp(clasp->name, "Window"));
88 }
89 
checkPassToPrivilegedCode(JSContext * cx,HandleObject wrapper,HandleValue v)90 bool AccessCheck::checkPassToPrivilegedCode(JSContext* cx, HandleObject wrapper,
91                                             HandleValue v) {
92   // Primitives are fine.
93   if (!v.isObject()) {
94     return true;
95   }
96   RootedObject obj(cx, &v.toObject());
97 
98   // Non-wrappers are fine.
99   if (!js::IsWrapper(obj)) {
100     return true;
101   }
102 
103   // Same-origin wrappers are fine.
104   if (AccessCheck::wrapperSubsumes(obj)) {
105     return true;
106   }
107 
108   // Badness.
109   JS_ReportErrorASCII(cx,
110                       "Permission denied to pass object to privileged code");
111   return false;
112 }
113 
checkPassToPrivilegedCode(JSContext * cx,HandleObject wrapper,const CallArgs & args)114 bool AccessCheck::checkPassToPrivilegedCode(JSContext* cx, HandleObject wrapper,
115                                             const CallArgs& args) {
116   if (!checkPassToPrivilegedCode(cx, wrapper, args.thisv())) {
117     return false;
118   }
119   for (size_t i = 0; i < args.length(); ++i) {
120     if (!checkPassToPrivilegedCode(cx, wrapper, args[i])) {
121       return false;
122     }
123   }
124   return true;
125 }
126 
reportCrossOriginDenial(JSContext * cx,JS::HandleId id,const nsACString & accessType)127 void AccessCheck::reportCrossOriginDenial(JSContext* cx, JS::HandleId id,
128                                           const nsACString& accessType) {
129   // This function exists because we want to report DOM SecurityErrors, not JS
130   // Errors, when denying access on cross-origin DOM objects.  It's
131   // conceptually pretty similar to
132   // AutoEnterPolicy::reportErrorIfExceptionIsNotPending.
133   if (JS_IsExceptionPending(cx)) {
134     return;
135   }
136 
137   nsAutoCString message;
138   if (JSID_IS_VOID(id)) {
139     message = "Permission denied to access object"_ns;
140   } else {
141     // We want to use JS_ValueToSource here, because that most closely
142     // matches what AutoEnterPolicy::reportErrorIfExceptionIsNotPending
143     // does.
144     JS::RootedValue idVal(cx, js::IdToValue(id));
145     nsAutoJSString propName;
146     JS::RootedString idStr(cx, JS_ValueToSource(cx, idVal));
147     if (!idStr || !propName.init(cx, idStr)) {
148       return;
149     }
150     message = "Permission denied to "_ns + accessType + " property "_ns +
151               NS_ConvertUTF16toUTF8(propName) + " on cross-origin object"_ns;
152   }
153   ErrorResult rv;
154   rv.ThrowSecurityError(message);
155   MOZ_ALWAYS_TRUE(rv.MaybeSetPendingException(cx));
156 }
157 
deny(JSContext * cx,js::Wrapper::Action act,HandleId id,bool mayThrow)158 bool OpaqueWithSilentFailing::deny(JSContext* cx, js::Wrapper::Action act,
159                                    HandleId id, bool mayThrow) {
160   // Fail silently for GET, ENUMERATE, and GET_PROPERTY_DESCRIPTOR.
161   if (act == js::Wrapper::GET || act == js::Wrapper::ENUMERATE ||
162       act == js::Wrapper::GET_PROPERTY_DESCRIPTOR) {
163     // Note that ReportWrapperDenial doesn't do any _exception_ reporting,
164     // so we want to do this regardless of the value of mayThrow.
165     return ReportWrapperDenial(cx, id, WrapperDenialForCOW,
166                                "Access to privileged JS object not permitted");
167   }
168 
169   return false;
170 }
171 
172 }  // namespace xpc
173