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