std::string gjs_debug_id(jsid id) { if (JSID_IS_STRING(id)) return gjs_debug_flat_string(JSID_TO_FLAT_STRING(id)); return gjs_debug_value(js::IdToValue(id)); }
bool AccessCheck::isCrossOriginAccessPermitted(JSContext* cx, HandleObject wrapper, HandleId id, Wrapper::Action act) { if (act == Wrapper::CALL) return false; if (act == Wrapper::ENUMERATE) return true; // For the case of getting a property descriptor, we allow if either GET or SET // is allowed, and rely on FilteringWrapper to filter out any disallowed accessors. if (act == Wrapper::GET_PROPERTY_DESCRIPTOR) { return isCrossOriginAccessPermitted(cx, wrapper, id, Wrapper::GET) || isCrossOriginAccessPermitted(cx, wrapper, id, Wrapper::SET); } RootedObject obj(cx, js::UncheckedUnwrap(wrapper, /* stopAtOuter = */ false)); CrossOriginObjectType type = IdentifyCrossOriginObject(obj); if (JSID_IS_STRING(id)) { if (IsPermitted(type, JSID_TO_FLAT_STRING(id), act == Wrapper::SET)) return true; } if (act != Wrapper::GET) return false; // Check for frame IDs. If we're resolving named frames, make sure to only // resolve ones that don't shadow native properties. See bug 860494. if (type == CrossOriginWindow) { if (JSID_IS_STRING(id)) { bool wouldShadow = false; if (!XrayUtils::HasNativeProperty(cx, wrapper, id, &wouldShadow) || wouldShadow) { // If the named subframe matches the name of a DOM constructor, // the global resolve triggered by the HasNativeProperty call // above will try to perform a CheckedUnwrap on |wrapper|, and // throw a security error if it fails. That exception isn't // really useful for our callers, so we silence it and just // deny access to the property (since it matched a builtin). // // Note that this would be a problem if the resolve code ever // tried to CheckedUnwrap the wrapper _before_ concluding that // the name corresponds to a builtin global property, since it // would mean that we'd never permit cross-origin named subframe // access (something we regrettably need to support). JS_ClearPendingException(cx); return false; } } return IsFrameId(cx, obj, id); } return false; }
// Generate 'usage' and 'help' properties for the given object. // JS_DefineFunctionsWithHelp will define individual function objects with both // of those properties (eg getpid.usage = "getpid()" and getpid.help = "return // the process id"). This function will generate strings for an "interface // object", eg os.file, which contains some number of functions. // // .usage will be set to "<name> - interface object". // // .help will be set to a newline-separated list of functions that have either // 'help' or 'usage' properties. Functions are described with their usage // strings, if they have them, else with just their names. // bool GenerateInterfaceHelp(JSContext* cx, HandleObject obj, const char* name) { AutoIdVector idv(cx); if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &idv)) return false; StringBuffer buf(cx); int numEntries = 0; for (size_t i = 0; i < idv.length(); i++) { RootedId id(cx, idv[i]); RootedValue v(cx); if (!JS_GetPropertyById(cx, obj, id, &v)) return false; if (!v.isObject()) continue; RootedObject prop(cx, &v.toObject()); RootedValue usage(cx); RootedValue help(cx); if (!JS_GetProperty(cx, prop, "usage", &usage)) return false; if (!JS_GetProperty(cx, prop, "help", &help)) return false; if (!usage.isString() && !help.isString()) continue; if (numEntries && !buf.append("\n")) return false; numEntries++; if (!buf.append(" ", 2)) return false; if (!buf.append(usage.isString() ? usage.toString() : JSID_TO_FLAT_STRING(id))) return false; } RootedString s(cx, buf.finishString()); if (!s || !JS_DefineProperty(cx, obj, "help", s, 0)) return false; buf.clear(); if (!buf.append(name, strlen(name)) || !buf.append(" - interface object with ", 25)) return false; char cbuf[100]; SprintfLiteral(cbuf, "%d %s", numEntries, numEntries == 1 ? "entry" : "entries"); if (!buf.append(cbuf, strlen(cbuf))) return false; s = buf.finishString(); if (!s || !JS_DefineProperty(cx, obj, "usage", s, 0)) return false; return true; }
NS_INTERFACE_MAP_END //////////////////////////////////////////////////////////////////////////////// //// nsIXPCScriptable #define XPC_MAP_CLASSNAME StatementJSHelper #define XPC_MAP_QUOTED_CLASSNAME "StatementJSHelper" #define XPC_MAP_WANT_GETPROPERTY #define XPC_MAP_WANT_RESOLVE #define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE #include "xpc_map_end.h" NS_IMETHODIMP StatementJSHelper::GetProperty(nsIXPConnectWrappedNative *aWrapper, JSContext *aCtx, JSObject *aScopeObj, jsid aId, JS::Value *_result, bool *_retval) { if (!JSID_IS_STRING(aId)) return NS_OK; JS::Rooted<JSObject*> scope(aCtx, aScopeObj); JS::Rooted<jsid> id(aCtx, aId); #ifdef DEBUG { nsCOMPtr<mozIStorageStatement> isStatement( do_QueryInterface(aWrapper->Native())); NS_ASSERTION(isStatement, "How is this not a statement?!"); } #endif Statement *stmt = static_cast<Statement *>( static_cast<mozIStorageStatement *>(aWrapper->Native()) ); JSFlatString *str = JSID_TO_FLAT_STRING(id); if (::JS_FlatStringEqualsAscii(str, "row")) return getRow(stmt, aCtx, scope, _result); if (::JS_FlatStringEqualsAscii(str, "params")) return getParams(stmt, aCtx, scope, _result); return NS_OK; }
NS_IMETHODIMP StatementJSHelper::Resolve(nsIXPConnectWrappedNative *aWrapper, JSContext *aCtx, JSObject *aScopeObj, jsid aId, bool *aResolvedp, bool *_retval) { if (!JSID_IS_STRING(aId)) return NS_OK; JS::RootedObject scope(aCtx, aScopeObj); if (::JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(aId), "step")) { *_retval = ::JS_DefineFunction(aCtx, scope, "step", stepFunc, 0, JSPROP_RESOLVING) != nullptr; *aResolvedp = true; return NS_OK; } return NS_OK; }
bool CallbackInterface::GetCallableProperty(JSContext* cx, JS::Handle<jsid> aPropId, JS::MutableHandle<JS::Value> aCallable) { if (!JS_GetPropertyById(cx, CallbackKnownNotGray(), aPropId, aCallable)) { return false; } if (!aCallable.isObject() || !JS::IsCallable(&aCallable.toObject())) { char* propName = JS_EncodeString(cx, JS_FORGET_STRING_FLATNESS(JSID_TO_FLAT_STRING(aPropId))); nsPrintfCString description("Property '%s'", propName); JS_free(cx, propName); ThrowErrorMessage(cx, MSG_NOT_CALLABLE, description.get()); return false; } return true; }
NS_IMETHODIMP StatementJSHelper::NewResolve(nsIXPConnectWrappedNative *aWrapper, JSContext *aCtx, JSObject *aScopeObj, jsid aId, PRUint32 aFlags, JSObject **_objp, bool *_retval) { if (!JSID_IS_STRING(aId)) return NS_OK; if (::JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(aId), "step")) { *_retval = ::JS_DefineFunction(aCtx, aScopeObj, "step", stepFunc, 0, 0) != nullptr; *_objp = aScopeObj; return NS_OK; } return NS_OK; }
/* static */ bool WebIDLGlobalNameHash::ResolveForSystemGlobal(JSContext* aCx, JS::Handle<JSObject*> aObj, JS::Handle<jsid> aId, bool* aResolvedp) { MOZ_ASSERT(JS_IsGlobalObject(aObj)); // First we try to resolve standard classes. if (!JS_ResolveStandardClass(aCx, aObj, aId, aResolvedp)) { return false; } if (*aResolvedp) { return true; } // We don't resolve any non-string entries. if (!JSID_IS_STRING(aId)) { return true; } // XXX(nika): In the Window case, we unwrap our global object here to handle // XRays. I don't think we ever create xrays to system globals, so I believe // we can skip this step. MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(aObj), "Xrays not supported!"); // Look up the corresponding entry in the name table, and resolve if enabled. const WebIDLNameTableEntry* entry = GetEntry(JSID_TO_FLAT_STRING(aId)); if (entry && (!entry->mEnabled || entry->mEnabled(aCx, aObj))) { if (NS_WARN_IF(!GetPerInterfaceObjectHandle( aCx, entry->mConstructorId, entry->mCreate, /* aDefineOnGlobal = */ true))) { return Throw(aCx, NS_ERROR_FAILURE); } *aResolvedp = true; } return true; }
bool ExposedPropertiesOnly::check(JSContext* cx, HandleObject wrapper, HandleId id, Wrapper::Action act) { RootedObject wrappedObject(cx, Wrapper::wrappedObject(wrapper)); if (act == Wrapper::CALL) return false; // For the case of getting a property descriptor, we allow if either GET or SET // is allowed, and rely on FilteringWrapper to filter out any disallowed accessors. if (act == Wrapper::GET_PROPERTY_DESCRIPTOR) { return check(cx, wrapper, id, Wrapper::GET) || check(cx, wrapper, id, Wrapper::SET); } RootedId exposedPropsId(cx, GetRTIdByIndex(cx, XPCJSRuntime::IDX_EXPOSEDPROPS)); // We need to enter the wrappee's compartment to look at __exposedProps__, // but we want to be in the wrapper's compartment if we call Deny(). // // Unfortunately, |cx| can be in either compartment when we call ::check. :-( JSAutoCompartment ac(cx, wrappedObject); bool found = false; if (!JS_HasPropertyById(cx, wrappedObject, exposedPropsId, &found)) return false; // If no __exposedProps__ existed, deny access. if (!found) { // Previously we automatically granted access to indexed properties and // .length for Array COWs. We're not doing that anymore, so make sure to // let people know what's going on. bool isArray; if (!JS_IsArrayObject(cx, wrappedObject, &isArray)) return false; if (!isArray) isArray = JS_IsTypedArrayObject(wrappedObject); bool isIndexedAccessOnArray = isArray && JSID_IS_INT(id) && JSID_TO_INT(id) >= 0; bool isLengthAccessOnArray = isArray && JSID_IS_STRING(id) && JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "length"); if (isIndexedAccessOnArray || isLengthAccessOnArray) { JSAutoCompartment ac2(cx, wrapper); ReportWrapperDenial(cx, id, WrapperDenialForCOW, "Access to elements and length of privileged Array not permitted"); } return false; } if (id == JSID_VOID) return true; Rooted<JSPropertyDescriptor> desc(cx); if (!JS_GetPropertyDescriptorById(cx, wrappedObject, exposedPropsId, &desc)) return false; if (!desc.object()) return false; if (desc.hasGetterOrSetter()) { EnterAndThrow(cx, wrapper, "__exposedProps__ must be a value property"); return false; } RootedValue exposedProps(cx, desc.value()); if (exposedProps.isNullOrUndefined()) return false; if (!exposedProps.isObject()) { EnterAndThrow(cx, wrapper, "__exposedProps__ must be undefined, null, or an Object"); return false; } RootedObject hallpass(cx, &exposedProps.toObject()); if (!AccessCheck::subsumes(js::UncheckedUnwrap(hallpass), wrappedObject)) { EnterAndThrow(cx, wrapper, "Invalid __exposedProps__"); return false; } Access access = NO_ACCESS; if (!JS_GetPropertyDescriptorById(cx, hallpass, id, &desc)) { return false; // Error } if (!desc.object() || !desc.enumerable()) return false; if (!desc.value().isString()) { EnterAndThrow(cx, wrapper, "property must be a string"); return false; } JSFlatString* flat = JS_FlattenString(cx, desc.value().toString()); if (!flat) return false; size_t length = JS_GetStringLength(JS_FORGET_STRING_FLATNESS(flat)); for (size_t i = 0; i < length; ++i) { char16_t ch = JS_GetFlatStringCharAt(flat, i); switch (ch) { case 'r': if (access & READ) { EnterAndThrow(cx, wrapper, "duplicate 'readable' property flag"); return false; } access = Access(access | READ); break; case 'w': if (access & WRITE) { EnterAndThrow(cx, wrapper, "duplicate 'writable' property flag"); return false; } access = Access(access | WRITE); break; default: EnterAndThrow(cx, wrapper, "properties can only be readable or read and writable"); return false; } } if (access == NO_ACCESS) { EnterAndThrow(cx, wrapper, "specified properties must have a permission bit set"); return false; } if ((act == Wrapper::SET && !(access & WRITE)) || (act != Wrapper::SET && !(access & READ))) { return false; } // Inspect the property on the underlying object to check for red flags. if (!JS_GetPropertyDescriptorById(cx, wrappedObject, id, &desc)) return false; // Reject accessor properties. if (desc.hasGetterOrSetter()) { EnterAndThrow(cx, wrapper, "Exposing privileged accessor properties is prohibited"); return false; } // Reject privileged or cross-origin callables. if (desc.value().isObject()) { RootedObject maybeCallable(cx, js::UncheckedUnwrap(&desc.value().toObject())); if (JS::IsCallable(maybeCallable) && !AccessCheck::subsumes(wrapper, maybeCallable)) { EnterAndThrow(cx, wrapper, "Exposing privileged or cross-origin callable is prohibited"); return false; } } return true; }
/* static */ bool WebIDLGlobalNameHash::DefineIfEnabled( JSContext* aCx, JS::Handle<JSObject*> aObj, JS::Handle<jsid> aId, JS::MutableHandle<JS::PropertyDescriptor> aDesc, bool* aFound) { MOZ_ASSERT(JSID_IS_STRING(aId), "Check for string id before calling this!"); const WebIDLNameTableEntry* entry; { entry = GetEntry(JSID_TO_FLAT_STRING(aId)); } if (!entry) { *aFound = false; return true; } *aFound = true; ConstructorEnabled checkEnabledForScope = entry->mEnabled; // We do the enabled check on the current Realm of aCx, but for the // actual object we pass in the underlying object in the Xray case. That // way the callee can decide whether to allow access based on the caller // or the window being touched. // // Using aCx to represent the current Realm for CheckedUnwrapDynamic // purposes is OK here, because that's the Realm where we plan to do // our property-defining. JS::Rooted<JSObject*> global( aCx, js::CheckedUnwrapDynamic(aObj, aCx, /* stopAtWindowProxy = */ false)); if (!global) { return Throw(aCx, NS_ERROR_DOM_SECURITY_ERR); } { // It's safe to pass "&global" here, because we've already unwrapped it, but // for general sanity better to not have debug code even having the // appearance of mutating things that opt code uses. #ifdef DEBUG JS::Rooted<JSObject*> temp(aCx, global); DebugOnly<nsGlobalWindowInner*> win; MOZ_ASSERT(NS_SUCCEEDED( UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT(Window, &temp, win, aCx))); #endif } if (checkEnabledForScope && !checkEnabledForScope(aCx, global)) { return true; } // The DOM constructor resolve machinery interacts with Xrays in tricky // ways, and there are some asymmetries that are important to understand. // // In the regular (non-Xray) case, we only want to resolve constructors // once (so that if they're deleted, they don't reappear). We do this by // stashing the constructor in a slot on the global, such that we can see // during resolve whether we've created it already. This is rather // memory-intensive, so we don't try to maintain these semantics when // manipulating a global over Xray (so the properties just re-resolve if // they've been deleted). // // Unfortunately, there's a bit of an impedance-mismatch between the Xray // and non-Xray machinery. The Xray machinery wants an API that returns a // JS::PropertyDescriptor, so that the resolve hook doesn't have to get // snared up with trying to define a property on the Xray holder. At the // same time, the DefineInterface callbacks are set up to define things // directly on the global. And re-jiggering them to return property // descriptors is tricky, because some DefineInterface callbacks define // multiple things (like the Image() alias for HTMLImageElement). // // So the setup is as-follows: // // * The resolve function takes a JS::PropertyDescriptor, but in the // non-Xray case, callees may define things directly on the global, and // set the value on the property descriptor to |undefined| to indicate // that there's nothing more for the caller to do. We assert against // this behavior in the Xray case. // // * We make sure that we do a non-Xray resolve first, so that all the // slots are set up. In the Xray case, this means unwrapping and doing // a non-Xray resolve before doing the Xray resolve. // // This all could use some grand refactoring, but for now we just limp // along. if (xpc::WrapperFactory::IsXrayWrapper(aObj)) { JS::Rooted<JSObject*> constructor(aCx); { JSAutoRealm ar(aCx, global); constructor = FindNamedConstructorForXray(aCx, aId, entry); } if (NS_WARN_IF(!constructor)) { return Throw(aCx, NS_ERROR_FAILURE); } if (!JS_WrapObject(aCx, &constructor)) { return Throw(aCx, NS_ERROR_FAILURE); } FillPropertyDescriptor(aDesc, aObj, 0, JS::ObjectValue(*constructor)); return true; } JS::Rooted<JSObject*> interfaceObject( aCx, GetPerInterfaceObjectHandle(aCx, entry->mConstructorId, entry->mCreate, /* aDefineOnGlobal = */ true)); if (NS_WARN_IF(!interfaceObject)) { return Throw(aCx, NS_ERROR_FAILURE); } // We've already defined the property. We indicate this to the caller // by filling a property descriptor with JS::UndefinedValue() as the // value. We still have to fill in a property descriptor, though, so // that the caller knows the property is in fact on this object. It // doesn't matter what we pass for the "readonly" argument here. FillPropertyDescriptor(aDesc, aObj, JS::UndefinedValue(), false); return true; }
/* static */ bool WebIDLGlobalNameHash::MayResolve(jsid aId) { return GetEntry(JSID_TO_FLAT_STRING(aId)) != nullptr; }
bool ExposedPropertiesOnly::check(JSContext *cx, HandleObject wrapper, HandleId id, Wrapper::Action act) { RootedObject wrappedObject(cx, Wrapper::wrappedObject(wrapper)); if (act == Wrapper::CALL) return true; // For the case of getting a property descriptor, we allow if either GET or SET // is allowed, and rely on FilteringWrapper to filter out any disallowed accessors. if (act == Wrapper::GET_PROPERTY_DESCRIPTOR) { return check(cx, wrapper, id, Wrapper::GET) || check(cx, wrapper, id, Wrapper::SET); } RootedId exposedPropsId(cx, GetRTIdByIndex(cx, XPCJSRuntime::IDX_EXPOSEDPROPS)); // We need to enter the wrappee's compartment to look at __exposedProps__, // but we want to be in the wrapper's compartment if we call Deny(). // // Unfortunately, |cx| can be in either compartment when we call ::check. :-( JSAutoCompartment ac(cx, wrappedObject); bool found = false; if (!JS_HasPropertyById(cx, wrappedObject, exposedPropsId, &found)) return false; // Always permit access to "length" and indexed properties of arrays. if ((JS_IsArrayObject(cx, wrappedObject) || JS_IsTypedArrayObject(wrappedObject)) && ((JSID_IS_INT(id) && JSID_TO_INT(id) >= 0) || (JSID_IS_STRING(id) && JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "length")))) { return true; // Allow } // If no __exposedProps__ existed, deny access. if (!found) { return false; } if (id == JSID_VOID) return true; RootedValue exposedProps(cx); if (!JS_LookupPropertyById(cx, wrappedObject, exposedPropsId, &exposedProps)) return false; if (exposedProps.isNullOrUndefined()) return false; if (!exposedProps.isObject()) { EnterAndThrow(cx, wrapper, "__exposedProps__ must be undefined, null, or an Object"); return false; } RootedObject hallpass(cx, &exposedProps.toObject()); if (!AccessCheck::subsumes(js::UncheckedUnwrap(hallpass), wrappedObject)) { EnterAndThrow(cx, wrapper, "Invalid __exposedProps__"); return false; } Access access = NO_ACCESS; Rooted<JSPropertyDescriptor> desc(cx); if (!JS_GetPropertyDescriptorById(cx, hallpass, id, &desc)) { return false; // Error } if (!desc.object() || !desc.isEnumerable()) return false; if (!desc.value().isString()) { EnterAndThrow(cx, wrapper, "property must be a string"); return false; } JSFlatString *flat = JS_FlattenString(cx, desc.value().toString()); if (!flat) return false; size_t length = JS_GetStringLength(JS_FORGET_STRING_FLATNESS(flat)); for (size_t i = 0; i < length; ++i) { char16_t ch = JS_GetFlatStringCharAt(flat, i); switch (ch) { case 'r': if (access & READ) { EnterAndThrow(cx, wrapper, "duplicate 'readable' property flag"); return false; } access = Access(access | READ); break; case 'w': if (access & WRITE) { EnterAndThrow(cx, wrapper, "duplicate 'writable' property flag"); return false; } access = Access(access | WRITE); break; default: EnterAndThrow(cx, wrapper, "properties can only be readable or read and writable"); return false; } } if (access == NO_ACCESS) { EnterAndThrow(cx, wrapper, "specified properties must have a permission bit set"); return false; } if ((act == Wrapper::SET && !(access & WRITE)) || (act != Wrapper::SET && !(access & READ))) { return false; } return true; }