1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/xpconnect/wrappers/AccessCheck.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,360 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "AccessCheck.h" 1.11 + 1.12 +#include "nsJSPrincipals.h" 1.13 +#include "nsGlobalWindow.h" 1.14 + 1.15 +#include "XPCWrapper.h" 1.16 +#include "XrayWrapper.h" 1.17 + 1.18 +#include "jsfriendapi.h" 1.19 +#include "mozilla/dom/BindingUtils.h" 1.20 +#include "mozilla/dom/WindowBinding.h" 1.21 +#include "nsIDOMWindowCollection.h" 1.22 +#include "nsJSUtils.h" 1.23 + 1.24 +using namespace mozilla; 1.25 +using namespace JS; 1.26 +using namespace js; 1.27 + 1.28 +namespace xpc { 1.29 + 1.30 +nsIPrincipal * 1.31 +GetCompartmentPrincipal(JSCompartment *compartment) 1.32 +{ 1.33 + return nsJSPrincipals::get(JS_GetCompartmentPrincipals(compartment)); 1.34 +} 1.35 + 1.36 +nsIPrincipal * 1.37 +GetObjectPrincipal(JSObject *obj) 1.38 +{ 1.39 + return GetCompartmentPrincipal(js::GetObjectCompartment(obj)); 1.40 +} 1.41 + 1.42 +// Does the principal of compartment a subsume the principal of compartment b? 1.43 +bool 1.44 +AccessCheck::subsumes(JSCompartment *a, JSCompartment *b) 1.45 +{ 1.46 + nsIPrincipal *aprin = GetCompartmentPrincipal(a); 1.47 + nsIPrincipal *bprin = GetCompartmentPrincipal(b); 1.48 + return aprin->Subsumes(bprin); 1.49 +} 1.50 + 1.51 +bool 1.52 +AccessCheck::subsumes(JSObject *a, JSObject *b) 1.53 +{ 1.54 + return subsumes(js::GetObjectCompartment(a), js::GetObjectCompartment(b)); 1.55 +} 1.56 + 1.57 +// Same as above, but considering document.domain. 1.58 +bool 1.59 +AccessCheck::subsumesConsideringDomain(JSCompartment *a, JSCompartment *b) 1.60 +{ 1.61 + nsIPrincipal *aprin = GetCompartmentPrincipal(a); 1.62 + nsIPrincipal *bprin = GetCompartmentPrincipal(b); 1.63 + return aprin->SubsumesConsideringDomain(bprin); 1.64 +} 1.65 + 1.66 +// Does the compartment of the wrapper subsumes the compartment of the wrappee? 1.67 +bool 1.68 +AccessCheck::wrapperSubsumes(JSObject *wrapper) 1.69 +{ 1.70 + MOZ_ASSERT(js::IsWrapper(wrapper)); 1.71 + JSObject *wrapped = js::UncheckedUnwrap(wrapper); 1.72 + return AccessCheck::subsumes(js::GetObjectCompartment(wrapper), 1.73 + js::GetObjectCompartment(wrapped)); 1.74 +} 1.75 + 1.76 +bool 1.77 +AccessCheck::isChrome(JSCompartment *compartment) 1.78 +{ 1.79 + nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager(); 1.80 + if (!ssm) { 1.81 + return false; 1.82 + } 1.83 + 1.84 + bool privileged; 1.85 + nsIPrincipal *principal = GetCompartmentPrincipal(compartment); 1.86 + return NS_SUCCEEDED(ssm->IsSystemPrincipal(principal, &privileged)) && privileged; 1.87 +} 1.88 + 1.89 +bool 1.90 +AccessCheck::isChrome(JSObject *obj) 1.91 +{ 1.92 + return isChrome(js::GetObjectCompartment(obj)); 1.93 +} 1.94 + 1.95 +bool 1.96 +AccessCheck::callerIsChrome() 1.97 +{ 1.98 + nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager(); 1.99 + if (!ssm) 1.100 + return false; 1.101 + bool subjectIsSystem; 1.102 + nsresult rv = ssm->SubjectPrincipalIsSystem(&subjectIsSystem); 1.103 + return NS_SUCCEEDED(rv) && subjectIsSystem; 1.104 +} 1.105 + 1.106 +nsIPrincipal * 1.107 +AccessCheck::getPrincipal(JSCompartment *compartment) 1.108 +{ 1.109 + return GetCompartmentPrincipal(compartment); 1.110 +} 1.111 + 1.112 +#define NAME(ch, str, cases) \ 1.113 + case ch: if (!strcmp(name, str)) switch (propChars[0]) { cases }; break; 1.114 +#define PROP(ch, actions) case ch: { actions }; break; 1.115 +#define RW(str) if (JS_FlatStringEqualsAscii(prop, str)) return true; 1.116 +#define R(str) if (!set && JS_FlatStringEqualsAscii(prop, str)) return true; 1.117 +#define W(str) if (set && JS_FlatStringEqualsAscii(prop, str)) return true; 1.118 + 1.119 +// Hardcoded policy for cross origin property access. This was culled from the 1.120 +// preferences file (all.js). We don't want users to overwrite highly sensitive 1.121 +// security policies. 1.122 +static bool 1.123 +IsPermitted(const char *name, JSFlatString *prop, bool set) 1.124 +{ 1.125 + size_t propLength; 1.126 + const jschar *propChars = 1.127 + JS_GetInternedStringCharsAndLength(JS_FORGET_STRING_FLATNESS(prop), &propLength); 1.128 + if (!propLength) 1.129 + return false; 1.130 + switch (name[0]) { 1.131 + NAME('L', "Location", 1.132 + PROP('h', W("href")) 1.133 + PROP('r', R("replace"))) 1.134 + case 'W': 1.135 + if (!strcmp(name, "Window")) 1.136 + return dom::WindowBinding::IsPermitted(prop, propChars[0], set); 1.137 + break; 1.138 + } 1.139 + return false; 1.140 +} 1.141 + 1.142 +#undef NAME 1.143 +#undef RW 1.144 +#undef R 1.145 +#undef W 1.146 + 1.147 +static bool 1.148 +IsFrameId(JSContext *cx, JSObject *objArg, jsid idArg) 1.149 +{ 1.150 + RootedObject obj(cx, objArg); 1.151 + RootedId id(cx, idArg); 1.152 + 1.153 + obj = JS_ObjectToInnerObject(cx, obj); 1.154 + MOZ_ASSERT(!js::IsWrapper(obj)); 1.155 + nsGlobalWindow* win = WindowOrNull(obj); 1.156 + if (!win) { 1.157 + return false; 1.158 + } 1.159 + 1.160 + nsCOMPtr<nsIDOMWindowCollection> col; 1.161 + win->GetFrames(getter_AddRefs(col)); 1.162 + if (!col) { 1.163 + return false; 1.164 + } 1.165 + 1.166 + nsCOMPtr<nsIDOMWindow> domwin; 1.167 + if (JSID_IS_INT(id)) { 1.168 + col->Item(JSID_TO_INT(id), getter_AddRefs(domwin)); 1.169 + } else if (JSID_IS_STRING(id)) { 1.170 + col->NamedItem(nsDependentJSString(id), getter_AddRefs(domwin)); 1.171 + } 1.172 + 1.173 + return domwin != nullptr; 1.174 +} 1.175 + 1.176 +static bool 1.177 +IsWindow(const char *name) 1.178 +{ 1.179 + return name[0] == 'W' && !strcmp(name, "Window"); 1.180 +} 1.181 + 1.182 +bool 1.183 +AccessCheck::isCrossOriginAccessPermitted(JSContext *cx, JSObject *wrapperArg, jsid idArg, 1.184 + Wrapper::Action act) 1.185 +{ 1.186 + if (!XPCWrapper::GetSecurityManager()) 1.187 + return true; 1.188 + 1.189 + if (act == Wrapper::CALL) 1.190 + return false; 1.191 + 1.192 + RootedId id(cx, idArg); 1.193 + RootedObject wrapper(cx, wrapperArg); 1.194 + RootedObject obj(cx, Wrapper::wrappedObject(wrapper)); 1.195 + 1.196 + // For XOWs, we generally want to deny enumerate-like operations, but fail 1.197 + // silently (see CrossOriginAccessiblePropertiesOnly::deny). 1.198 + if (act == Wrapper::ENUMERATE) 1.199 + return false; 1.200 + 1.201 + const char *name; 1.202 + const js::Class *clasp = js::GetObjectClass(obj); 1.203 + MOZ_ASSERT(!XrayUtils::IsXPCWNHolderClass(Jsvalify(clasp)), "shouldn't have a holder here"); 1.204 + if (clasp->ext.innerObject) 1.205 + name = "Window"; 1.206 + else 1.207 + name = clasp->name; 1.208 + 1.209 + if (JSID_IS_STRING(id)) { 1.210 + if (IsPermitted(name, JSID_TO_FLAT_STRING(id), act == Wrapper::SET)) 1.211 + return true; 1.212 + } 1.213 + 1.214 + if (act != Wrapper::GET) 1.215 + return false; 1.216 + 1.217 + // Check for frame IDs. If we're resolving named frames, make sure to only 1.218 + // resolve ones that don't shadow native properties. See bug 860494. 1.219 + if (IsWindow(name)) { 1.220 + if (JSID_IS_STRING(id) && !XrayUtils::IsXrayResolving(cx, wrapper, id)) { 1.221 + bool wouldShadow = false; 1.222 + if (!XrayUtils::HasNativeProperty(cx, wrapper, id, &wouldShadow) || 1.223 + wouldShadow) 1.224 + { 1.225 + return false; 1.226 + } 1.227 + } 1.228 + return IsFrameId(cx, obj, id); 1.229 + } 1.230 + return false; 1.231 +} 1.232 + 1.233 +enum Access { READ = (1<<0), WRITE = (1<<1), NO_ACCESS = 0 }; 1.234 + 1.235 +static void 1.236 +EnterAndThrow(JSContext *cx, JSObject *wrapper, const char *msg) 1.237 +{ 1.238 + JSAutoCompartment ac(cx, wrapper); 1.239 + JS_ReportError(cx, msg); 1.240 +} 1.241 + 1.242 +bool 1.243 +ExposedPropertiesOnly::check(JSContext *cx, JSObject *wrapperArg, jsid idArg, Wrapper::Action act) 1.244 +{ 1.245 + RootedObject wrapper(cx, wrapperArg); 1.246 + RootedId id(cx, idArg); 1.247 + RootedObject wrappedObject(cx, Wrapper::wrappedObject(wrapper)); 1.248 + 1.249 + if (act == Wrapper::CALL) 1.250 + return true; 1.251 + 1.252 + RootedId exposedPropsId(cx, GetRTIdByIndex(cx, XPCJSRuntime::IDX_EXPOSEDPROPS)); 1.253 + 1.254 + // We need to enter the wrappee's compartment to look at __exposedProps__, 1.255 + // but we want to be in the wrapper's compartment if we call Deny(). 1.256 + // 1.257 + // Unfortunately, |cx| can be in either compartment when we call ::check. :-( 1.258 + JSAutoCompartment ac(cx, wrappedObject); 1.259 + 1.260 + bool found = false; 1.261 + if (!JS_HasPropertyById(cx, wrappedObject, exposedPropsId, &found)) 1.262 + return false; 1.263 + 1.264 + // Always permit access to "length" and indexed properties of arrays. 1.265 + if ((JS_IsArrayObject(cx, wrappedObject) || 1.266 + JS_IsTypedArrayObject(wrappedObject)) && 1.267 + ((JSID_IS_INT(id) && JSID_TO_INT(id) >= 0) || 1.268 + (JSID_IS_STRING(id) && JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "length")))) { 1.269 + return true; // Allow 1.270 + } 1.271 + 1.272 + // If no __exposedProps__ existed, deny access. 1.273 + if (!found) { 1.274 + return false; 1.275 + } 1.276 + 1.277 + if (id == JSID_VOID) 1.278 + return true; 1.279 + 1.280 + RootedValue exposedProps(cx); 1.281 + if (!JS_LookupPropertyById(cx, wrappedObject, exposedPropsId, &exposedProps)) 1.282 + return false; 1.283 + 1.284 + if (exposedProps.isNullOrUndefined()) 1.285 + return false; 1.286 + 1.287 + if (!exposedProps.isObject()) { 1.288 + EnterAndThrow(cx, wrapper, "__exposedProps__ must be undefined, null, or an Object"); 1.289 + return false; 1.290 + } 1.291 + 1.292 + RootedObject hallpass(cx, &exposedProps.toObject()); 1.293 + 1.294 + if (!AccessCheck::subsumes(js::UncheckedUnwrap(hallpass), wrappedObject)) { 1.295 + EnterAndThrow(cx, wrapper, "Invalid __exposedProps__"); 1.296 + return false; 1.297 + } 1.298 + 1.299 + Access access = NO_ACCESS; 1.300 + 1.301 + Rooted<JSPropertyDescriptor> desc(cx); 1.302 + if (!JS_GetPropertyDescriptorById(cx, hallpass, id, &desc)) { 1.303 + return false; // Error 1.304 + } 1.305 + if (!desc.object() || !desc.isEnumerable()) 1.306 + return false; 1.307 + 1.308 + if (!desc.value().isString()) { 1.309 + EnterAndThrow(cx, wrapper, "property must be a string"); 1.310 + return false; 1.311 + } 1.312 + 1.313 + JSString *str = desc.value().toString(); 1.314 + size_t length; 1.315 + const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length); 1.316 + if (!chars) 1.317 + return false; 1.318 + 1.319 + for (size_t i = 0; i < length; ++i) { 1.320 + switch (chars[i]) { 1.321 + case 'r': 1.322 + if (access & READ) { 1.323 + EnterAndThrow(cx, wrapper, "duplicate 'readable' property flag"); 1.324 + return false; 1.325 + } 1.326 + access = Access(access | READ); 1.327 + break; 1.328 + 1.329 + case 'w': 1.330 + if (access & WRITE) { 1.331 + EnterAndThrow(cx, wrapper, "duplicate 'writable' property flag"); 1.332 + return false; 1.333 + } 1.334 + access = Access(access | WRITE); 1.335 + break; 1.336 + 1.337 + default: 1.338 + EnterAndThrow(cx, wrapper, "properties can only be readable or read and writable"); 1.339 + return false; 1.340 + } 1.341 + } 1.342 + 1.343 + if (access == NO_ACCESS) { 1.344 + EnterAndThrow(cx, wrapper, "specified properties must have a permission bit set"); 1.345 + return false; 1.346 + } 1.347 + 1.348 + if ((act == Wrapper::SET && !(access & WRITE)) || 1.349 + (act != Wrapper::SET && !(access & READ))) { 1.350 + return false; 1.351 + } 1.352 + 1.353 + return true; 1.354 +} 1.355 + 1.356 +bool 1.357 +ExposedPropertiesOnly::allowNativeCall(JSContext *cx, JS::IsAcceptableThis test, 1.358 + JS::NativeImpl impl) 1.359 +{ 1.360 + return js::IsTypedArrayThisCheck(test); 1.361 +} 1.362 + 1.363 +}