Sat, 03 Jan 2015 20:18:00 +0100
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 | } |