LCOV - code coverage report
Current view: top level - js/xpconnect/wrappers - AccessCheck.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 2 120 1.7 %
Date: 2018-08-07 16:35:00 Functions: 0 0 -
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13-14-ga5dd952