Mypal/js/xpconnect/wrappers/FilteringWrapper.cpp

312 lines
11 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "FilteringWrapper.h"
#include "AccessCheck.h"
#include "ChromeObjectWrapper.h"
#include "XrayWrapper.h"
#include "jsapi.h"
using namespace JS;
using namespace js;
namespace xpc {
static JS::SymbolCode sCrossOriginWhitelistedSymbolCodes[] = {
JS::SymbolCode::toStringTag,
JS::SymbolCode::hasInstance,
JS::SymbolCode::isConcatSpreadable
};
bool
IsCrossOriginWhitelistedSymbol(JSContext* cx, JS::HandleId id)
{
if (!JSID_IS_SYMBOL(id)) {
return false;
}
JS::Symbol* symbol = JSID_TO_SYMBOL(id);
for (auto code : sCrossOriginWhitelistedSymbolCodes) {
if (symbol == JS::GetWellKnownSymbol(cx, code)) {
return true;
}
}
return false;
}
template <typename Policy>
static bool
Filter(JSContext* cx, HandleObject wrapper, AutoIdVector& props)
{
size_t w = 0;
RootedId id(cx);
for (size_t n = 0; n < props.length(); ++n) {
id = props[n];
if (Policy::check(cx, wrapper, id, Wrapper::GET) || Policy::check(cx, wrapper, id, Wrapper::SET))
props[w++].set(id);
else if (JS_IsExceptionPending(cx))
return false;
}
if (!props.resize(w))
return false;
return true;
}
template <typename Policy>
static bool
FilterPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id, MutableHandle<PropertyDescriptor> desc)
{
MOZ_ASSERT(!JS_IsExceptionPending(cx));
bool getAllowed = Policy::check(cx, wrapper, id, Wrapper::GET);
if (JS_IsExceptionPending(cx))
return false;
bool setAllowed = Policy::check(cx, wrapper, id, Wrapper::SET);
if (JS_IsExceptionPending(cx))
return false;
MOZ_ASSERT(getAllowed || setAllowed,
"Filtering policy should not allow GET_PROPERTY_DESCRIPTOR in this case");
if (!desc.hasGetterOrSetter()) {
// Handle value properties.
if (!getAllowed)
desc.value().setUndefined();
} else {
// Handle accessor properties.
MOZ_ASSERT(desc.value().isUndefined());
if (!getAllowed)
desc.setGetter(nullptr);
if (!setAllowed)
desc.setSetter(nullptr);
}
return true;
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::getPropertyDescriptor(JSContext* cx, HandleObject wrapper,
HandleId id,
MutableHandle<PropertyDescriptor> desc) const
{
assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET |
BaseProxyHandler::GET_PROPERTY_DESCRIPTOR);
if (!Base::getPropertyDescriptor(cx, wrapper, id, desc))
return false;
return FilterPropertyDescriptor<Policy>(cx, wrapper, id, desc);
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper,
HandleId id,
MutableHandle<PropertyDescriptor> desc) const
{
assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET |
BaseProxyHandler::GET_PROPERTY_DESCRIPTOR);
if (!Base::getOwnPropertyDescriptor(cx, wrapper, id, desc))
return false;
return FilterPropertyDescriptor<Policy>(cx, wrapper, id, desc);
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::ownPropertyKeys(JSContext* cx, HandleObject wrapper,
AutoIdVector& props) const
{
assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE);
return Base::ownPropertyKeys(cx, wrapper, props) &&
Filter<Policy>(cx, wrapper, props);
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::getOwnEnumerablePropertyKeys(JSContext* cx,
HandleObject wrapper,
AutoIdVector& props) const
{
assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE);
return Base::getOwnEnumerablePropertyKeys(cx, wrapper, props) &&
Filter<Policy>(cx, wrapper, props);
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::enumerate(JSContext* cx, HandleObject wrapper,
MutableHandleObject objp) const
{
assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE);
// We refuse to trigger the enumerate hook across chrome wrappers because
// we don't know how to censor custom iterator objects. Instead we trigger
// the default proxy enumerate trap, which will use js::GetPropertyKeys
// for the list of (censored) ids.
return js::BaseProxyHandler::enumerate(cx, wrapper, objp);
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::call(JSContext* cx, JS::Handle<JSObject*> wrapper,
const JS::CallArgs& args) const
{
if (!Policy::checkCall(cx, wrapper, args))
return false;
return Base::call(cx, wrapper, args);
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::construct(JSContext* cx, JS::Handle<JSObject*> wrapper,
const JS::CallArgs& args) const
{
if (!Policy::checkCall(cx, wrapper, args))
return false;
return Base::construct(cx, wrapper, args);
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::nativeCall(JSContext* cx, JS::IsAcceptableThis test,
JS::NativeImpl impl, const JS::CallArgs& args) const
{
if (Policy::allowNativeCall(cx, test, impl))
return Base::Permissive::nativeCall(cx, test, impl, args);
return Base::Restrictive::nativeCall(cx, test, impl, args);
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::getPrototype(JSContext* cx, JS::HandleObject wrapper,
JS::MutableHandleObject protop) const
{
// Filtering wrappers do not allow access to the prototype.
protop.set(nullptr);
return true;
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::enter(JSContext* cx, HandleObject wrapper,
HandleId id, Wrapper::Action act, bool* bp) const
{
if (!Policy::check(cx, wrapper, id, act)) {
*bp = JS_IsExceptionPending(cx) ? false : Policy::deny(act, id);
return false;
}
*bp = true;
return true;
}
bool
CrossOriginXrayWrapper::getPropertyDescriptor(JSContext* cx,
JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,
JS::MutableHandle<PropertyDescriptor> desc) const
{
if (!SecurityXrayDOM::getPropertyDescriptor(cx, wrapper, id, desc))
return false;
if (desc.object()) {
// Cross-origin DOM objects do not have symbol-named properties apart
// from the ones we add ourselves here.
MOZ_ASSERT(!JSID_IS_SYMBOL(id),
"What's this symbol-named property that appeared on a "
"Window or Location instance?");
// All properties on cross-origin DOM objects are |own|.
desc.object().set(wrapper);
// All properties on cross-origin DOM objects are non-enumerable and
// "configurable". Any value attributes are read-only.
desc.attributesRef() &= ~JSPROP_ENUMERATE;
desc.attributesRef() &= ~JSPROP_PERMANENT;
if (!desc.getter() && !desc.setter())
desc.attributesRef() |= JSPROP_READONLY;
} else if (IsCrossOriginWhitelistedSymbol(cx, id)) {
// Spec says to return PropertyDescriptor {
// [[Value]]: undefined, [[Writable]]: false, [[Enumerable]]: false,
// [[Configurable]]: true
// }.
//
desc.setDataDescriptor(JS::UndefinedHandleValue, JSPROP_READONLY);
desc.object().set(wrapper);
}
return true;
}
bool
CrossOriginXrayWrapper::getOwnPropertyDescriptor(JSContext* cx,
JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,
JS::MutableHandle<PropertyDescriptor> desc) const
{
// All properties on cross-origin DOM objects are |own|.
return getPropertyDescriptor(cx, wrapper, id, desc);
}
bool
CrossOriginXrayWrapper::ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::AutoIdVector& props) const
{
// All properties on cross-origin objects are supposed |own|, despite what
// the underlying native object may report. Override the inherited trap to
// avoid passing JSITER_OWNONLY as a flag.
if (!SecurityXrayDOM::getPropertyKeys(cx, wrapper, JSITER_HIDDEN, props)) {
return false;
}
// Now add the three symbol-named props cross-origin objects have.
#ifdef DEBUG
for (size_t n = 0; n < props.length(); ++n) {
MOZ_ASSERT(!JSID_IS_SYMBOL(props[n]),
"Unexpected existing symbol-name prop");
}
#endif
if (!props.reserve(props.length() +
ArrayLength(sCrossOriginWhitelistedSymbolCodes))) {
return false;
}
for (auto code : sCrossOriginWhitelistedSymbolCodes) {
props.infallibleAppend(SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, code)));
}
return true;
}
bool
CrossOriginXrayWrapper::defineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,
JS::Handle<PropertyDescriptor> desc,
JS::ObjectOpResult& result) const
{
JS_ReportErrorASCII(cx, "Permission denied to define property on cross-origin object");
return false;
}
bool
CrossOriginXrayWrapper::delete_(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id, JS::ObjectOpResult& result) const
{
JS_ReportErrorASCII(cx, "Permission denied to delete property on cross-origin object");
return false;
}
#define XOW FilteringWrapper<CrossOriginXrayWrapper, CrossOriginAccessiblePropertiesOnly>
#define NNXOW FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>
#define NNXOWC FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>
template<> const XOW XOW::singleton(0);
template<> const NNXOW NNXOW::singleton(0);
template<> const NNXOWC NNXOWC::singleton(0);
template class XOW;
template class NNXOW;
template class NNXOWC;
template class ChromeObjectWrapperBase;
} // namespace xpc