js/xpconnect/wrappers/AccessCheck.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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/. */
     7 #include "AccessCheck.h"
     9 #include "nsJSPrincipals.h"
    10 #include "nsGlobalWindow.h"
    12 #include "XPCWrapper.h"
    13 #include "XrayWrapper.h"
    15 #include "jsfriendapi.h"
    16 #include "mozilla/dom/BindingUtils.h"
    17 #include "mozilla/dom/WindowBinding.h"
    18 #include "nsIDOMWindowCollection.h"
    19 #include "nsJSUtils.h"
    21 using namespace mozilla;
    22 using namespace JS;
    23 using namespace js;
    25 namespace xpc {
    27 nsIPrincipal *
    28 GetCompartmentPrincipal(JSCompartment *compartment)
    29 {
    30     return nsJSPrincipals::get(JS_GetCompartmentPrincipals(compartment));
    31 }
    33 nsIPrincipal *
    34 GetObjectPrincipal(JSObject *obj)
    35 {
    36     return GetCompartmentPrincipal(js::GetObjectCompartment(obj));
    37 }
    39 // Does the principal of compartment a subsume the principal of compartment b?
    40 bool
    41 AccessCheck::subsumes(JSCompartment *a, JSCompartment *b)
    42 {
    43     nsIPrincipal *aprin = GetCompartmentPrincipal(a);
    44     nsIPrincipal *bprin = GetCompartmentPrincipal(b);
    45     return aprin->Subsumes(bprin);
    46 }
    48 bool
    49 AccessCheck::subsumes(JSObject *a, JSObject *b)
    50 {
    51     return subsumes(js::GetObjectCompartment(a), js::GetObjectCompartment(b));
    52 }
    54 // Same as above, but considering document.domain.
    55 bool
    56 AccessCheck::subsumesConsideringDomain(JSCompartment *a, JSCompartment *b)
    57 {
    58     nsIPrincipal *aprin = GetCompartmentPrincipal(a);
    59     nsIPrincipal *bprin = GetCompartmentPrincipal(b);
    60     return aprin->SubsumesConsideringDomain(bprin);
    61 }
    63 // Does the compartment of the wrapper subsumes the compartment of the wrappee?
    64 bool
    65 AccessCheck::wrapperSubsumes(JSObject *wrapper)
    66 {
    67     MOZ_ASSERT(js::IsWrapper(wrapper));
    68     JSObject *wrapped = js::UncheckedUnwrap(wrapper);
    69     return AccessCheck::subsumes(js::GetObjectCompartment(wrapper),
    70                                  js::GetObjectCompartment(wrapped));
    71 }
    73 bool
    74 AccessCheck::isChrome(JSCompartment *compartment)
    75 {
    76     nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager();
    77     if (!ssm) {
    78         return false;
    79     }
    81     bool privileged;
    82     nsIPrincipal *principal = GetCompartmentPrincipal(compartment);
    83     return NS_SUCCEEDED(ssm->IsSystemPrincipal(principal, &privileged)) && privileged;
    84 }
    86 bool
    87 AccessCheck::isChrome(JSObject *obj)
    88 {
    89     return isChrome(js::GetObjectCompartment(obj));
    90 }
    92 bool
    93 AccessCheck::callerIsChrome()
    94 {
    95     nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager();
    96     if (!ssm)
    97         return false;
    98     bool subjectIsSystem;
    99     nsresult rv = ssm->SubjectPrincipalIsSystem(&subjectIsSystem);
   100     return NS_SUCCEEDED(rv) && subjectIsSystem;
   101 }
   103 nsIPrincipal *
   104 AccessCheck::getPrincipal(JSCompartment *compartment)
   105 {
   106     return GetCompartmentPrincipal(compartment);
   107 }
   109 #define NAME(ch, str, cases)                                                  \
   110     case ch: if (!strcmp(name, str)) switch (propChars[0]) { cases }; break;
   111 #define PROP(ch, actions) case ch: { actions }; break;
   112 #define RW(str) if (JS_FlatStringEqualsAscii(prop, str)) return true;
   113 #define R(str) if (!set && JS_FlatStringEqualsAscii(prop, str)) return true;
   114 #define W(str) if (set && JS_FlatStringEqualsAscii(prop, str)) return true;
   116 // Hardcoded policy for cross origin property access. This was culled from the
   117 // preferences file (all.js). We don't want users to overwrite highly sensitive
   118 // security policies.
   119 static bool
   120 IsPermitted(const char *name, JSFlatString *prop, bool set)
   121 {
   122     size_t propLength;
   123     const jschar *propChars =
   124         JS_GetInternedStringCharsAndLength(JS_FORGET_STRING_FLATNESS(prop), &propLength);
   125     if (!propLength)
   126         return false;
   127     switch (name[0]) {
   128         NAME('L', "Location",
   129              PROP('h', W("href"))
   130              PROP('r', R("replace")))
   131         case 'W':
   132             if (!strcmp(name, "Window"))
   133                 return dom::WindowBinding::IsPermitted(prop, propChars[0], set);
   134             break;
   135     }
   136     return false;
   137 }
   139 #undef NAME
   140 #undef RW
   141 #undef R
   142 #undef W
   144 static bool
   145 IsFrameId(JSContext *cx, JSObject *objArg, jsid idArg)
   146 {
   147     RootedObject obj(cx, objArg);
   148     RootedId id(cx, idArg);
   150     obj = JS_ObjectToInnerObject(cx, obj);
   151     MOZ_ASSERT(!js::IsWrapper(obj));
   152     nsGlobalWindow* win = WindowOrNull(obj);
   153     if (!win) {
   154         return false;
   155     }
   157     nsCOMPtr<nsIDOMWindowCollection> col;
   158     win->GetFrames(getter_AddRefs(col));
   159     if (!col) {
   160         return false;
   161     }
   163     nsCOMPtr<nsIDOMWindow> domwin;
   164     if (JSID_IS_INT(id)) {
   165         col->Item(JSID_TO_INT(id), getter_AddRefs(domwin));
   166     } else if (JSID_IS_STRING(id)) {
   167         col->NamedItem(nsDependentJSString(id), getter_AddRefs(domwin));
   168     }
   170     return domwin != nullptr;
   171 }
   173 static bool
   174 IsWindow(const char *name)
   175 {
   176     return name[0] == 'W' && !strcmp(name, "Window");
   177 }
   179 bool
   180 AccessCheck::isCrossOriginAccessPermitted(JSContext *cx, JSObject *wrapperArg, jsid idArg,
   181                                           Wrapper::Action act)
   182 {
   183     if (!XPCWrapper::GetSecurityManager())
   184         return true;
   186     if (act == Wrapper::CALL)
   187         return false;
   189     RootedId id(cx, idArg);
   190     RootedObject wrapper(cx, wrapperArg);
   191     RootedObject obj(cx, Wrapper::wrappedObject(wrapper));
   193     // For XOWs, we generally want to deny enumerate-like operations, but fail
   194     // silently (see CrossOriginAccessiblePropertiesOnly::deny).
   195     if (act == Wrapper::ENUMERATE)
   196         return false;
   198     const char *name;
   199     const js::Class *clasp = js::GetObjectClass(obj);
   200     MOZ_ASSERT(!XrayUtils::IsXPCWNHolderClass(Jsvalify(clasp)), "shouldn't have a holder here");
   201     if (clasp->ext.innerObject)
   202         name = "Window";
   203     else
   204         name = clasp->name;
   206     if (JSID_IS_STRING(id)) {
   207         if (IsPermitted(name, JSID_TO_FLAT_STRING(id), act == Wrapper::SET))
   208             return true;
   209     }
   211     if (act != Wrapper::GET)
   212         return false;
   214     // Check for frame IDs. If we're resolving named frames, make sure to only
   215     // resolve ones that don't shadow native properties. See bug 860494.
   216     if (IsWindow(name)) {
   217         if (JSID_IS_STRING(id) && !XrayUtils::IsXrayResolving(cx, wrapper, id)) {
   218             bool wouldShadow = false;
   219             if (!XrayUtils::HasNativeProperty(cx, wrapper, id, &wouldShadow) ||
   220                 wouldShadow)
   221             {
   222                 return false;
   223             }
   224         }
   225         return IsFrameId(cx, obj, id);
   226     }
   227     return false;
   228 }
   230 enum Access { READ = (1<<0), WRITE = (1<<1), NO_ACCESS = 0 };
   232 static void
   233 EnterAndThrow(JSContext *cx, JSObject *wrapper, const char *msg)
   234 {
   235     JSAutoCompartment ac(cx, wrapper);
   236     JS_ReportError(cx, msg);
   237 }
   239 bool
   240 ExposedPropertiesOnly::check(JSContext *cx, JSObject *wrapperArg, jsid idArg, Wrapper::Action act)
   241 {
   242     RootedObject wrapper(cx, wrapperArg);
   243     RootedId id(cx, idArg);
   244     RootedObject wrappedObject(cx, Wrapper::wrappedObject(wrapper));
   246     if (act == Wrapper::CALL)
   247         return true;
   249     RootedId exposedPropsId(cx, GetRTIdByIndex(cx, XPCJSRuntime::IDX_EXPOSEDPROPS));
   251     // We need to enter the wrappee's compartment to look at __exposedProps__,
   252     // but we want to be in the wrapper's compartment if we call Deny().
   253     //
   254     // Unfortunately, |cx| can be in either compartment when we call ::check. :-(
   255     JSAutoCompartment ac(cx, wrappedObject);
   257     bool found = false;
   258     if (!JS_HasPropertyById(cx, wrappedObject, exposedPropsId, &found))
   259         return false;
   261     // Always permit access to "length" and indexed properties of arrays.
   262     if ((JS_IsArrayObject(cx, wrappedObject) ||
   263          JS_IsTypedArrayObject(wrappedObject)) &&
   264         ((JSID_IS_INT(id) && JSID_TO_INT(id) >= 0) ||
   265          (JSID_IS_STRING(id) && JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "length")))) {
   266         return true; // Allow
   267     }
   269     // If no __exposedProps__ existed, deny access.
   270     if (!found) {
   271         return false;
   272     }
   274     if (id == JSID_VOID)
   275         return true;
   277     RootedValue exposedProps(cx);
   278     if (!JS_LookupPropertyById(cx, wrappedObject, exposedPropsId, &exposedProps))
   279         return false;
   281     if (exposedProps.isNullOrUndefined())
   282         return false;
   284     if (!exposedProps.isObject()) {
   285         EnterAndThrow(cx, wrapper, "__exposedProps__ must be undefined, null, or an Object");
   286         return false;
   287     }
   289     RootedObject hallpass(cx, &exposedProps.toObject());
   291     if (!AccessCheck::subsumes(js::UncheckedUnwrap(hallpass), wrappedObject)) {
   292         EnterAndThrow(cx, wrapper, "Invalid __exposedProps__");
   293         return false;
   294     }
   296     Access access = NO_ACCESS;
   298     Rooted<JSPropertyDescriptor> desc(cx);
   299     if (!JS_GetPropertyDescriptorById(cx, hallpass, id, &desc)) {
   300         return false; // Error
   301     }
   302     if (!desc.object() || !desc.isEnumerable())
   303         return false;
   305     if (!desc.value().isString()) {
   306         EnterAndThrow(cx, wrapper, "property must be a string");
   307         return false;
   308     }
   310     JSString *str = desc.value().toString();
   311     size_t length;
   312     const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
   313     if (!chars)
   314         return false;
   316     for (size_t i = 0; i < length; ++i) {
   317         switch (chars[i]) {
   318         case 'r':
   319             if (access & READ) {
   320                 EnterAndThrow(cx, wrapper, "duplicate 'readable' property flag");
   321                 return false;
   322             }
   323             access = Access(access | READ);
   324             break;
   326         case 'w':
   327             if (access & WRITE) {
   328                 EnterAndThrow(cx, wrapper, "duplicate 'writable' property flag");
   329                 return false;
   330             }
   331             access = Access(access | WRITE);
   332             break;
   334         default:
   335             EnterAndThrow(cx, wrapper, "properties can only be readable or read and writable");
   336             return false;
   337         }
   338     }
   340     if (access == NO_ACCESS) {
   341         EnterAndThrow(cx, wrapper, "specified properties must have a permission bit set");
   342         return false;
   343     }
   345     if ((act == Wrapper::SET && !(access & WRITE)) ||
   346         (act != Wrapper::SET && !(access & READ))) {
   347         return false;
   348     }
   350     return true;
   351 }
   353 bool
   354 ExposedPropertiesOnly::allowNativeCall(JSContext *cx, JS::IsAcceptableThis test,
   355                                        JS::NativeImpl impl)
   356 {
   357     return js::IsTypedArrayThisCheck(test);
   358 }
   360 }

mercurial