Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* vim: set ts=8 sts=4 et sw=4 tw=99: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "FilteringWrapper.h"
8 : #include "AccessCheck.h"
9 : #include "ChromeObjectWrapper.h"
10 : #include "XrayWrapper.h"
11 : #include "nsJSUtils.h"
12 : #include "mozilla/ErrorResult.h"
13 :
14 : #include "jsapi.h"
15 :
16 : using namespace JS;
17 : using namespace js;
18 :
19 : namespace xpc {
20 :
21 : static JS::SymbolCode sCrossOriginWhitelistedSymbolCodes[] = {
22 : JS::SymbolCode::toStringTag,
23 : JS::SymbolCode::hasInstance,
24 : JS::SymbolCode::isConcatSpreadable
25 : };
26 :
27 : bool
28 0 : IsCrossOriginWhitelistedSymbol(JSContext* cx, JS::HandleId id)
29 : {
30 0 : if (!JSID_IS_SYMBOL(id)) {
31 0 : return false;
32 : }
33 :
34 0 : JS::Symbol* symbol = JSID_TO_SYMBOL(id);
35 0 : for (auto code : sCrossOriginWhitelistedSymbolCodes) {
36 0 : if (symbol == JS::GetWellKnownSymbol(cx, code)) {
37 0 : return true;
38 : }
39 : }
40 :
41 0 : return false;
42 : }
43 :
44 : template <typename Policy>
45 : static bool
46 0 : Filter(JSContext* cx, HandleObject wrapper, AutoIdVector& props)
47 : {
48 0 : size_t w = 0;
49 0 : RootedId id(cx);
50 0 : for (size_t n = 0; n < props.length(); ++n) {
51 0 : id = props[n];
52 0 : if (Policy::check(cx, wrapper, id, Wrapper::GET) || Policy::check(cx, wrapper, id, Wrapper::SET))
53 0 : props[w++].set(id);
54 0 : else if (JS_IsExceptionPending(cx))
55 0 : return false;
56 : }
57 0 : if (!props.resize(w))
58 0 : return false;
59 :
60 0 : return true;
61 : }
62 :
63 : template <typename Policy>
64 : static bool
65 0 : FilterPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id, MutableHandle<PropertyDescriptor> desc)
66 : {
67 0 : MOZ_ASSERT(!JS_IsExceptionPending(cx));
68 0 : bool getAllowed = Policy::check(cx, wrapper, id, Wrapper::GET);
69 0 : if (JS_IsExceptionPending(cx))
70 0 : return false;
71 0 : bool setAllowed = Policy::check(cx, wrapper, id, Wrapper::SET);
72 0 : if (JS_IsExceptionPending(cx))
73 0 : return false;
74 :
75 0 : MOZ_ASSERT(getAllowed || setAllowed,
76 : "Filtering policy should not allow GET_PROPERTY_DESCRIPTOR in this case");
77 :
78 0 : if (!desc.hasGetterOrSetter()) {
79 : // Handle value properties.
80 0 : if (!getAllowed)
81 0 : desc.value().setUndefined();
82 : } else {
83 : // Handle accessor properties.
84 0 : MOZ_ASSERT(desc.value().isUndefined());
85 0 : if (!getAllowed)
86 0 : desc.setGetter(nullptr);
87 0 : if (!setAllowed)
88 0 : desc.setSetter(nullptr);
89 : }
90 :
91 0 : return true;
92 : }
93 :
94 : template <typename Base, typename Policy>
95 : bool
96 0 : FilteringWrapper<Base, Policy>::getPropertyDescriptor(JSContext* cx, HandleObject wrapper,
97 : HandleId id,
98 : MutableHandle<PropertyDescriptor> desc) const
99 : {
100 0 : assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET |
101 : BaseProxyHandler::GET_PROPERTY_DESCRIPTOR);
102 0 : if (!Base::getPropertyDescriptor(cx, wrapper, id, desc))
103 0 : return false;
104 0 : return FilterPropertyDescriptor<Policy>(cx, wrapper, id, desc);
105 : }
106 :
107 : template <typename Base, typename Policy>
108 : bool
109 0 : FilteringWrapper<Base, Policy>::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper,
110 : HandleId id,
111 : MutableHandle<PropertyDescriptor> desc) const
112 : {
113 0 : assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET |
114 : BaseProxyHandler::GET_PROPERTY_DESCRIPTOR);
115 0 : if (!Base::getOwnPropertyDescriptor(cx, wrapper, id, desc))
116 0 : return false;
117 0 : return FilterPropertyDescriptor<Policy>(cx, wrapper, id, desc);
118 : }
119 :
120 : template <typename Base, typename Policy>
121 : bool
122 0 : FilteringWrapper<Base, Policy>::ownPropertyKeys(JSContext* cx, HandleObject wrapper,
123 : AutoIdVector& props) const
124 : {
125 0 : assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE);
126 0 : return Base::ownPropertyKeys(cx, wrapper, props) &&
127 0 : Filter<Policy>(cx, wrapper, props);
128 : }
129 :
130 : template <typename Base, typename Policy>
131 : bool
132 0 : FilteringWrapper<Base, Policy>::getOwnEnumerablePropertyKeys(JSContext* cx,
133 : HandleObject wrapper,
134 : AutoIdVector& props) const
135 : {
136 0 : assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE);
137 0 : return Base::getOwnEnumerablePropertyKeys(cx, wrapper, props) &&
138 0 : Filter<Policy>(cx, wrapper, props);
139 : }
140 :
141 : template <typename Base, typename Policy>
142 : JSObject*
143 0 : FilteringWrapper<Base, Policy>::enumerate(JSContext* cx, HandleObject wrapper) const
144 : {
145 0 : assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE);
146 : // We refuse to trigger the enumerate hook across chrome wrappers because
147 : // we don't know how to censor custom iterator objects. Instead we trigger
148 : // the default proxy enumerate trap, which will use js::GetPropertyKeys
149 : // for the list of (censored) ids.
150 0 : return js::BaseProxyHandler::enumerate(cx, wrapper);
151 : }
152 :
153 : template <typename Base, typename Policy>
154 : bool
155 0 : FilteringWrapper<Base, Policy>::call(JSContext* cx, JS::Handle<JSObject*> wrapper,
156 : const JS::CallArgs& args) const
157 : {
158 0 : if (!Policy::checkCall(cx, wrapper, args))
159 0 : return false;
160 0 : return Base::call(cx, wrapper, args);
161 : }
162 :
163 : template <typename Base, typename Policy>
164 : bool
165 0 : FilteringWrapper<Base, Policy>::construct(JSContext* cx, JS::Handle<JSObject*> wrapper,
166 : const JS::CallArgs& args) const
167 : {
168 0 : if (!Policy::checkCall(cx, wrapper, args))
169 0 : return false;
170 0 : return Base::construct(cx, wrapper, args);
171 : }
172 :
173 : template <typename Base, typename Policy>
174 : bool
175 0 : FilteringWrapper<Base, Policy>::nativeCall(JSContext* cx, JS::IsAcceptableThis test,
176 : JS::NativeImpl impl, const JS::CallArgs& args) const
177 : {
178 0 : if (Policy::allowNativeCall(cx, test, impl))
179 0 : return Base::Permissive::nativeCall(cx, test, impl, args);
180 0 : return Base::Restrictive::nativeCall(cx, test, impl, args);
181 : }
182 :
183 : template <typename Base, typename Policy>
184 : bool
185 0 : FilteringWrapper<Base, Policy>::getPrototype(JSContext* cx, JS::HandleObject wrapper,
186 : JS::MutableHandleObject protop) const
187 : {
188 : // Filtering wrappers do not allow access to the prototype.
189 0 : protop.set(nullptr);
190 0 : return true;
191 : }
192 :
193 : template <typename Base, typename Policy>
194 : bool
195 0 : FilteringWrapper<Base, Policy>::enter(JSContext* cx, HandleObject wrapper,
196 : HandleId id, Wrapper::Action act,
197 : bool mayThrow, bool* bp) const
198 : {
199 0 : if (!Policy::check(cx, wrapper, id, act)) {
200 0 : *bp = JS_IsExceptionPending(cx) ?
201 0 : false : Policy::deny(cx, act, id, mayThrow);
202 0 : return false;
203 : }
204 0 : *bp = true;
205 0 : return true;
206 : }
207 :
208 : bool
209 0 : CrossOriginXrayWrapper::getPropertyDescriptor(JSContext* cx,
210 : JS::Handle<JSObject*> wrapper,
211 : JS::Handle<jsid> id,
212 : JS::MutableHandle<PropertyDescriptor> desc) const
213 : {
214 0 : if (!SecurityXrayDOM::getPropertyDescriptor(cx, wrapper, id, desc))
215 0 : return false;
216 0 : if (desc.object()) {
217 : // Cross-origin DOM objects do not have symbol-named properties apart
218 : // from the ones we add ourselves here.
219 0 : MOZ_ASSERT(!JSID_IS_SYMBOL(id),
220 : "What's this symbol-named property that appeared on a "
221 : "Window or Location instance?");
222 :
223 : // All properties on cross-origin DOM objects are |own|.
224 0 : desc.object().set(wrapper);
225 :
226 : // All properties on cross-origin DOM objects are non-enumerable and
227 : // "configurable". Any value attributes are read-only.
228 0 : desc.attributesRef() &= ~JSPROP_ENUMERATE;
229 0 : desc.attributesRef() &= ~JSPROP_PERMANENT;
230 0 : if (!desc.getter() && !desc.setter())
231 0 : desc.attributesRef() |= JSPROP_READONLY;
232 0 : } else if (IsCrossOriginWhitelistedSymbol(cx, id)) {
233 : // Spec says to return PropertyDescriptor {
234 : // [[Value]]: undefined, [[Writable]]: false, [[Enumerable]]: false,
235 : // [[Configurable]]: true
236 : // }.
237 : //
238 0 : desc.setDataDescriptor(JS::UndefinedHandleValue, JSPROP_READONLY);
239 0 : desc.object().set(wrapper);
240 : }
241 :
242 0 : return true;
243 : }
244 :
245 : bool
246 0 : CrossOriginXrayWrapper::getOwnPropertyDescriptor(JSContext* cx,
247 : JS::Handle<JSObject*> wrapper,
248 : JS::Handle<jsid> id,
249 : JS::MutableHandle<PropertyDescriptor> desc) const
250 : {
251 : // All properties on cross-origin DOM objects are |own|.
252 0 : return getPropertyDescriptor(cx, wrapper, id, desc);
253 : }
254 :
255 : bool
256 0 : CrossOriginXrayWrapper::ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
257 : JS::AutoIdVector& props) const
258 : {
259 : // All properties on cross-origin objects are supposed |own|, despite what
260 : // the underlying native object may report. Override the inherited trap to
261 : // avoid passing JSITER_OWNONLY as a flag.
262 0 : if (!SecurityXrayDOM::getPropertyKeys(cx, wrapper, JSITER_HIDDEN, props)) {
263 0 : return false;
264 : }
265 :
266 : // Now add the three symbol-named props cross-origin objects have.
267 : #ifdef DEBUG
268 0 : for (size_t n = 0; n < props.length(); ++n) {
269 0 : MOZ_ASSERT(!JSID_IS_SYMBOL(props[n]),
270 : "Unexpected existing symbol-name prop");
271 : }
272 : #endif
273 0 : if (!props.reserve(props.length() +
274 0 : ArrayLength(sCrossOriginWhitelistedSymbolCodes))) {
275 0 : return false;
276 : }
277 :
278 0 : for (auto code : sCrossOriginWhitelistedSymbolCodes) {
279 0 : props.infallibleAppend(SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, code)));
280 : }
281 :
282 0 : return true;
283 : }
284 :
285 : bool
286 0 : CrossOriginXrayWrapper::defineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
287 : JS::Handle<jsid> id,
288 : JS::Handle<PropertyDescriptor> desc,
289 : JS::ObjectOpResult& result) const
290 : {
291 0 : AccessCheck::reportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("define"));
292 0 : return false;
293 : }
294 :
295 : bool
296 0 : CrossOriginXrayWrapper::delete_(JSContext* cx, JS::Handle<JSObject*> wrapper,
297 : JS::Handle<jsid> id, JS::ObjectOpResult& result) const
298 : {
299 0 : AccessCheck::reportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("delete"));
300 0 : return false;
301 : }
302 :
303 : bool
304 0 : CrossOriginXrayWrapper::setPrototype(JSContext* cx, JS::HandleObject wrapper,
305 : JS::HandleObject proto,
306 : JS::ObjectOpResult& result) const
307 : {
308 : // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-setprototypeof
309 : // and
310 : // https://html.spec.whatwg.org/multipage/browsers.html#location-setprototypeof
311 : // both say to call SetImmutablePrototype, which does nothing and just
312 : // returns whether the passed-in value equals the current prototype. Our
313 : // current prototype is always null, so this just comes down to returning
314 : // whether null was passed in.
315 : //
316 : // In terms of ObjectOpResult that means calling one of the fail*() things
317 : // on it if non-null was passed, and it's got one that does just what we
318 : // want.
319 0 : if (!proto) {
320 0 : return result.succeed();
321 : }
322 0 : return result.failCantSetProto();
323 : }
324 :
325 : #define XOW FilteringWrapper<CrossOriginXrayWrapper, CrossOriginAccessiblePropertiesOnly>
326 : #define NNXOW FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>
327 : #define NNXOWC FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>
328 :
329 : template<> const XOW XOW::singleton(0);
330 : template<> const NNXOW NNXOW::singleton(0);
331 : template<> const NNXOWC NNXOWC::singleton(0);
332 :
333 : template class XOW;
334 : template class NNXOW;
335 : template class NNXOWC;
336 : template class ChromeObjectWrapperBase;
337 : } // namespace xpc
|