js/xpconnect/wrappers/AccessCheck.cpp

branch
TOR_BUG_3246
changeset 7
129ffea94266
equal deleted inserted replaced
-1:000000000000 0:285648cd5722
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 "AccessCheck.h"
8
9 #include "nsJSPrincipals.h"
10 #include "nsGlobalWindow.h"
11
12 #include "XPCWrapper.h"
13 #include "XrayWrapper.h"
14
15 #include "jsfriendapi.h"
16 #include "mozilla/dom/BindingUtils.h"
17 #include "mozilla/dom/WindowBinding.h"
18 #include "nsIDOMWindowCollection.h"
19 #include "nsJSUtils.h"
20
21 using namespace mozilla;
22 using namespace JS;
23 using namespace js;
24
25 namespace xpc {
26
27 nsIPrincipal *
28 GetCompartmentPrincipal(JSCompartment *compartment)
29 {
30 return nsJSPrincipals::get(JS_GetCompartmentPrincipals(compartment));
31 }
32
33 nsIPrincipal *
34 GetObjectPrincipal(JSObject *obj)
35 {
36 return GetCompartmentPrincipal(js::GetObjectCompartment(obj));
37 }
38
39 // Does the principal of compartment a subsume the principal of compartment b?
40 bool
41 AccessCheck::subsumes(JSCompartment *a, JSCompartment *b)
42 {
43 nsIPrincipal *aprin = GetCompartmentPrincipal(a);
44 nsIPrincipal *bprin = GetCompartmentPrincipal(b);
45 return aprin->Subsumes(bprin);
46 }
47
48 bool
49 AccessCheck::subsumes(JSObject *a, JSObject *b)
50 {
51 return subsumes(js::GetObjectCompartment(a), js::GetObjectCompartment(b));
52 }
53
54 // Same as above, but considering document.domain.
55 bool
56 AccessCheck::subsumesConsideringDomain(JSCompartment *a, JSCompartment *b)
57 {
58 nsIPrincipal *aprin = GetCompartmentPrincipal(a);
59 nsIPrincipal *bprin = GetCompartmentPrincipal(b);
60 return aprin->SubsumesConsideringDomain(bprin);
61 }
62
63 // Does the compartment of the wrapper subsumes the compartment of the wrappee?
64 bool
65 AccessCheck::wrapperSubsumes(JSObject *wrapper)
66 {
67 MOZ_ASSERT(js::IsWrapper(wrapper));
68 JSObject *wrapped = js::UncheckedUnwrap(wrapper);
69 return AccessCheck::subsumes(js::GetObjectCompartment(wrapper),
70 js::GetObjectCompartment(wrapped));
71 }
72
73 bool
74 AccessCheck::isChrome(JSCompartment *compartment)
75 {
76 nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager();
77 if (!ssm) {
78 return false;
79 }
80
81 bool privileged;
82 nsIPrincipal *principal = GetCompartmentPrincipal(compartment);
83 return NS_SUCCEEDED(ssm->IsSystemPrincipal(principal, &privileged)) && privileged;
84 }
85
86 bool
87 AccessCheck::isChrome(JSObject *obj)
88 {
89 return isChrome(js::GetObjectCompartment(obj));
90 }
91
92 bool
93 AccessCheck::callerIsChrome()
94 {
95 nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager();
96 if (!ssm)
97 return false;
98 bool subjectIsSystem;
99 nsresult rv = ssm->SubjectPrincipalIsSystem(&subjectIsSystem);
100 return NS_SUCCEEDED(rv) && subjectIsSystem;
101 }
102
103 nsIPrincipal *
104 AccessCheck::getPrincipal(JSCompartment *compartment)
105 {
106 return GetCompartmentPrincipal(compartment);
107 }
108
109 #define NAME(ch, str, cases) \
110 case ch: if (!strcmp(name, str)) switch (propChars[0]) { cases }; break;
111 #define PROP(ch, actions) case ch: { actions }; break;
112 #define RW(str) if (JS_FlatStringEqualsAscii(prop, str)) return true;
113 #define R(str) if (!set && JS_FlatStringEqualsAscii(prop, str)) return true;
114 #define W(str) if (set && JS_FlatStringEqualsAscii(prop, str)) return true;
115
116 // Hardcoded policy for cross origin property access. This was culled from the
117 // preferences file (all.js). We don't want users to overwrite highly sensitive
118 // security policies.
119 static bool
120 IsPermitted(const char *name, JSFlatString *prop, bool set)
121 {
122 size_t propLength;
123 const jschar *propChars =
124 JS_GetInternedStringCharsAndLength(JS_FORGET_STRING_FLATNESS(prop), &propLength);
125 if (!propLength)
126 return false;
127 switch (name[0]) {
128 NAME('L', "Location",
129 PROP('h', W("href"))
130 PROP('r', R("replace")))
131 case 'W':
132 if (!strcmp(name, "Window"))
133 return dom::WindowBinding::IsPermitted(prop, propChars[0], set);
134 break;
135 }
136 return false;
137 }
138
139 #undef NAME
140 #undef RW
141 #undef R
142 #undef W
143
144 static bool
145 IsFrameId(JSContext *cx, JSObject *objArg, jsid idArg)
146 {
147 RootedObject obj(cx, objArg);
148 RootedId id(cx, idArg);
149
150 obj = JS_ObjectToInnerObject(cx, obj);
151 MOZ_ASSERT(!js::IsWrapper(obj));
152 nsGlobalWindow* win = WindowOrNull(obj);
153 if (!win) {
154 return false;
155 }
156
157 nsCOMPtr<nsIDOMWindowCollection> col;
158 win->GetFrames(getter_AddRefs(col));
159 if (!col) {
160 return false;
161 }
162
163 nsCOMPtr<nsIDOMWindow> domwin;
164 if (JSID_IS_INT(id)) {
165 col->Item(JSID_TO_INT(id), getter_AddRefs(domwin));
166 } else if (JSID_IS_STRING(id)) {
167 col->NamedItem(nsDependentJSString(id), getter_AddRefs(domwin));
168 }
169
170 return domwin != nullptr;
171 }
172
173 static bool
174 IsWindow(const char *name)
175 {
176 return name[0] == 'W' && !strcmp(name, "Window");
177 }
178
179 bool
180 AccessCheck::isCrossOriginAccessPermitted(JSContext *cx, JSObject *wrapperArg, jsid idArg,
181 Wrapper::Action act)
182 {
183 if (!XPCWrapper::GetSecurityManager())
184 return true;
185
186 if (act == Wrapper::CALL)
187 return false;
188
189 RootedId id(cx, idArg);
190 RootedObject wrapper(cx, wrapperArg);
191 RootedObject obj(cx, Wrapper::wrappedObject(wrapper));
192
193 // For XOWs, we generally want to deny enumerate-like operations, but fail
194 // silently (see CrossOriginAccessiblePropertiesOnly::deny).
195 if (act == Wrapper::ENUMERATE)
196 return false;
197
198 const char *name;
199 const js::Class *clasp = js::GetObjectClass(obj);
200 MOZ_ASSERT(!XrayUtils::IsXPCWNHolderClass(Jsvalify(clasp)), "shouldn't have a holder here");
201 if (clasp->ext.innerObject)
202 name = "Window";
203 else
204 name = clasp->name;
205
206 if (JSID_IS_STRING(id)) {
207 if (IsPermitted(name, JSID_TO_FLAT_STRING(id), act == Wrapper::SET))
208 return true;
209 }
210
211 if (act != Wrapper::GET)
212 return false;
213
214 // Check for frame IDs. If we're resolving named frames, make sure to only
215 // resolve ones that don't shadow native properties. See bug 860494.
216 if (IsWindow(name)) {
217 if (JSID_IS_STRING(id) && !XrayUtils::IsXrayResolving(cx, wrapper, id)) {
218 bool wouldShadow = false;
219 if (!XrayUtils::HasNativeProperty(cx, wrapper, id, &wouldShadow) ||
220 wouldShadow)
221 {
222 return false;
223 }
224 }
225 return IsFrameId(cx, obj, id);
226 }
227 return false;
228 }
229
230 enum Access { READ = (1<<0), WRITE = (1<<1), NO_ACCESS = 0 };
231
232 static void
233 EnterAndThrow(JSContext *cx, JSObject *wrapper, const char *msg)
234 {
235 JSAutoCompartment ac(cx, wrapper);
236 JS_ReportError(cx, msg);
237 }
238
239 bool
240 ExposedPropertiesOnly::check(JSContext *cx, JSObject *wrapperArg, jsid idArg, Wrapper::Action act)
241 {
242 RootedObject wrapper(cx, wrapperArg);
243 RootedId id(cx, idArg);
244 RootedObject wrappedObject(cx, Wrapper::wrappedObject(wrapper));
245
246 if (act == Wrapper::CALL)
247 return true;
248
249 RootedId exposedPropsId(cx, GetRTIdByIndex(cx, XPCJSRuntime::IDX_EXPOSEDPROPS));
250
251 // We need to enter the wrappee's compartment to look at __exposedProps__,
252 // but we want to be in the wrapper's compartment if we call Deny().
253 //
254 // Unfortunately, |cx| can be in either compartment when we call ::check. :-(
255 JSAutoCompartment ac(cx, wrappedObject);
256
257 bool found = false;
258 if (!JS_HasPropertyById(cx, wrappedObject, exposedPropsId, &found))
259 return false;
260
261 // Always permit access to "length" and indexed properties of arrays.
262 if ((JS_IsArrayObject(cx, wrappedObject) ||
263 JS_IsTypedArrayObject(wrappedObject)) &&
264 ((JSID_IS_INT(id) && JSID_TO_INT(id) >= 0) ||
265 (JSID_IS_STRING(id) && JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "length")))) {
266 return true; // Allow
267 }
268
269 // If no __exposedProps__ existed, deny access.
270 if (!found) {
271 return false;
272 }
273
274 if (id == JSID_VOID)
275 return true;
276
277 RootedValue exposedProps(cx);
278 if (!JS_LookupPropertyById(cx, wrappedObject, exposedPropsId, &exposedProps))
279 return false;
280
281 if (exposedProps.isNullOrUndefined())
282 return false;
283
284 if (!exposedProps.isObject()) {
285 EnterAndThrow(cx, wrapper, "__exposedProps__ must be undefined, null, or an Object");
286 return false;
287 }
288
289 RootedObject hallpass(cx, &exposedProps.toObject());
290
291 if (!AccessCheck::subsumes(js::UncheckedUnwrap(hallpass), wrappedObject)) {
292 EnterAndThrow(cx, wrapper, "Invalid __exposedProps__");
293 return false;
294 }
295
296 Access access = NO_ACCESS;
297
298 Rooted<JSPropertyDescriptor> desc(cx);
299 if (!JS_GetPropertyDescriptorById(cx, hallpass, id, &desc)) {
300 return false; // Error
301 }
302 if (!desc.object() || !desc.isEnumerable())
303 return false;
304
305 if (!desc.value().isString()) {
306 EnterAndThrow(cx, wrapper, "property must be a string");
307 return false;
308 }
309
310 JSString *str = desc.value().toString();
311 size_t length;
312 const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
313 if (!chars)
314 return false;
315
316 for (size_t i = 0; i < length; ++i) {
317 switch (chars[i]) {
318 case 'r':
319 if (access & READ) {
320 EnterAndThrow(cx, wrapper, "duplicate 'readable' property flag");
321 return false;
322 }
323 access = Access(access | READ);
324 break;
325
326 case 'w':
327 if (access & WRITE) {
328 EnterAndThrow(cx, wrapper, "duplicate 'writable' property flag");
329 return false;
330 }
331 access = Access(access | WRITE);
332 break;
333
334 default:
335 EnterAndThrow(cx, wrapper, "properties can only be readable or read and writable");
336 return false;
337 }
338 }
339
340 if (access == NO_ACCESS) {
341 EnterAndThrow(cx, wrapper, "specified properties must have a permission bit set");
342 return false;
343 }
344
345 if ((act == Wrapper::SET && !(access & WRITE)) ||
346 (act != Wrapper::SET && !(access & READ))) {
347 return false;
348 }
349
350 return true;
351 }
352
353 bool
354 ExposedPropertiesOnly::allowNativeCall(JSContext *cx, JS::IsAcceptableThis test,
355 JS::NativeImpl impl)
356 {
357 return js::IsTypedArrayThisCheck(test);
358 }
359
360 }

mercurial