js/xpconnect/wrappers/AccessCheck.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial