예제 #1
0
bool
AccessCheck::checkPassToPrivilegedCode(JSContext* cx, HandleObject wrapper, HandleValue v)
{
    // Primitives are fine.
    if (!v.isObject())
        return true;
    RootedObject obj(cx, &v.toObject());

    // Non-wrappers are fine.
    if (!js::IsWrapper(obj))
        return true;

    // CPOWs use COWs (in the unprivileged junk scope) for all child->parent
    // references. Without this test, the child process wouldn't be able to
    // pass any objects at all to CPOWs.
    if (mozilla::jsipc::IsWrappedCPOW(obj) &&
        js::GetObjectCompartment(wrapper) == js::GetObjectCompartment(xpc::UnprivilegedJunkScope()) &&
        XRE_IsParentProcess())
    {
        return true;
    }

    // COWs are fine to pass to chrome if and only if they have __exposedProps__,
    // since presumably content should never have a reason to pass an opaque
    // object back to chrome.
    if (AccessCheck::isChrome(js::UncheckedUnwrap(wrapper)) && WrapperFactory::IsCOW(obj)) {
        RootedObject target(cx, js::UncheckedUnwrap(obj));
        JSAutoCompartment ac(cx, target);
        RootedId id(cx, GetRTIdByIndex(cx, XPCJSRuntime::IDX_EXPOSEDPROPS));
        bool found = false;
        if (!JS_HasPropertyById(cx, target, id, &found))
            return false;
        if (found)
            return true;
    }

    // Same-origin wrappers are fine.
    if (AccessCheck::wrapperSubsumes(obj))
        return true;

    // Badness.
    JS_ReportError(cx, "Permission denied to pass object to privileged code");
    return false;
}
예제 #2
0
파일: importer.cpp 프로젝트: brownsr/cjs
static JSObject *
load_module_init(JSContext  *context,
                 JSObject   *in_object,
                 const char *full_path)
{
    JSObject *module_obj;
    JSBool found;
    jsid module_init_name;
    GFile *file;

    /* First we check if js module has already been loaded  */
    module_init_name = gjs_context_get_const_string(context, GJS_STRING_MODULE_INIT);
    if (!is_extension_module (full_path) &&
        JS_HasPropertyById(context, in_object, module_init_name, &found) && found) {
        jsval module_obj_val;

        if (JS_GetPropertyById(context,
                               in_object,
                               module_init_name,
                               &module_obj_val)) {
            return JSVAL_TO_OBJECT(module_obj_val);
        }
    }

    module_obj = create_module_object (context);
    file = g_file_new_for_commandline_arg(full_path);
    if (!import_file (context, "__init__", file, module_obj))
        goto out;

    if (!JS_DefinePropertyById(context, in_object,
                               module_init_name, OBJECT_TO_JSVAL(module_obj),
                               NULL, NULL,
                               GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT))
        goto out;

 out:
    g_object_unref (file);
    return module_obj;
}
예제 #3
0
파일: importer.c 프로젝트: PofigNaNik/gjs
static JSObject *
load_module_init(JSContext  *context,
                 JSObject   *in_object,
                 const char *full_path)
{
    char *script;
    gsize script_len;
    jsval script_retval;
    JSObject *module_obj;
    GError *error;
    JSBool found;
    jsid module_init_name;

    /* First we check if js module has already been loaded  */
    module_init_name = gjs_runtime_get_const_string(JS_GetRuntime(context),
                                                    GJS_STRING_MODULE_INIT);
    if (JS_HasPropertyById(context, in_object, module_init_name, &found) && found) {
        jsval module_obj_val;

        if (JS_GetPropertyById(context,
                               in_object,
                               module_init_name,
                               &module_obj_val)) {
            return JSVAL_TO_OBJECT(module_obj_val);
        }
    }

    module_obj = JS_NewObject(context, NULL, NULL, NULL);
    if (module_obj == NULL) {
        return JS_FALSE;
    }

    /* https://bugzilla.mozilla.org/show_bug.cgi?id=599651 means we
     * can't just pass in the global as the parent */
    JS_SetParent(context, module_obj,
                 gjs_get_import_global (context));

    /* Define module in importer for future use and to avoid module_obj
     * object to be garbage collected during the evaluation of the script */
    JS_DefinePropertyById(context, in_object,
                          module_init_name, OBJECT_TO_JSVAL(module_obj),
                          NULL, NULL,
                          GJS_MODULE_PROP_FLAGS & ~JSPROP_PERMANENT);

    script_len = 0;
    error = NULL;

    if (!g_file_get_contents(full_path, &script, &script_len, &error)) {
        if (!g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_ISDIR) &&
            !g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOTDIR) &&
            !g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
            gjs_throw_g_error(context, error);
        else
            g_error_free(error);

        return NULL;
    }

    g_assert(script != NULL);

    gjs_debug(GJS_DEBUG_IMPORTER, "Importing %s", full_path);

    if (!JS_EvaluateScript(context,
                           module_obj,
                           script,
                           script_len,
                           full_path,
                           1, /* line number */
                           &script_retval)) {
        g_free(script);

        /* If JSOPTION_DONT_REPORT_UNCAUGHT is set then the exception
         * would be left set after the evaluate and not go to the error
         * reporter function.
         */
        if (JS_IsExceptionPending(context)) {
            gjs_debug(GJS_DEBUG_IMPORTER,
                      "Module " MODULE_INIT_FILENAME " left an exception set");
            gjs_log_and_keep_exception(context);
        } else {
            gjs_throw(context,
                      "JS_EvaluateScript() returned FALSE but did not set exception");
        }

        return NULL;
    }

    g_free(script);

    return module_obj;
}
예제 #4
0
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;
}
예제 #5
0
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;
}