Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/js/xpconnect/wrappers/AccessCheck.cpp
Line
Count
Source (jump to first uncovered line)
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 "BasePrincipal.h"
11
#include "nsDOMWindowList.h"
12
#include "nsGlobalWindow.h"
13
14
#include "XPCWrapper.h"
15
#include "XrayWrapper.h"
16
#include "FilteringWrapper.h"
17
18
#include "jsfriendapi.h"
19
#include "mozilla/ErrorResult.h"
20
#include "mozilla/dom/BindingUtils.h"
21
#include "mozilla/dom/LocationBinding.h"
22
#include "mozilla/dom/WindowBinding.h"
23
#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
24
#include "nsJSUtils.h"
25
#include "xpcprivate.h"
26
27
using namespace mozilla;
28
using namespace JS;
29
using namespace js;
30
31
namespace xpc {
32
33
nsIPrincipal*
34
GetCompartmentPrincipal(JS::Compartment* compartment)
35
0
{
36
0
    return nsJSPrincipals::get(JS_GetCompartmentPrincipals(compartment));
37
0
}
38
39
nsIPrincipal*
40
GetRealmPrincipal(JS::Realm* realm)
41
0
{
42
0
    return nsJSPrincipals::get(JS::GetRealmPrincipals(realm));
43
0
}
44
45
nsIPrincipal*
46
GetObjectPrincipal(JSObject* obj)
47
0
{
48
0
    return GetRealmPrincipal(js::GetNonCCWObjectRealm(obj));
49
0
}
50
51
bool
52
AccessCheck::subsumes(JSObject* a, JSObject* b)
53
0
{
54
0
    return CompartmentOriginInfo::Subsumes(js::GetObjectCompartment(a),
55
0
                                           js::GetObjectCompartment(b));
56
0
}
57
58
// Same as above, but considering document.domain.
59
bool
60
AccessCheck::subsumesConsideringDomain(JS::Compartment* a, JS::Compartment* b)
61
0
{
62
0
    MOZ_ASSERT(OriginAttributes::IsRestrictOpenerAccessForFPI());
63
0
    nsIPrincipal* aprin = GetCompartmentPrincipal(a);
64
0
    nsIPrincipal* bprin = GetCompartmentPrincipal(b);
65
0
    return BasePrincipal::Cast(aprin)->FastSubsumesConsideringDomain(bprin);
66
0
}
67
68
bool
69
AccessCheck::subsumesConsideringDomainIgnoringFPD(JS::Compartment* a,
70
                                                  JS::Compartment* b)
71
0
{
72
0
    MOZ_ASSERT(!OriginAttributes::IsRestrictOpenerAccessForFPI());
73
0
    nsIPrincipal* aprin = GetCompartmentPrincipal(a);
74
0
    nsIPrincipal* bprin = GetCompartmentPrincipal(b);
75
0
    return BasePrincipal::Cast(aprin)->FastSubsumesConsideringDomainIgnoringFPD(bprin);
76
0
}
77
78
// Does the compartment of the wrapper subsumes the compartment of the wrappee?
79
bool
80
AccessCheck::wrapperSubsumes(JSObject* wrapper)
81
0
{
82
0
    MOZ_ASSERT(js::IsWrapper(wrapper));
83
0
    JSObject* wrapped = js::UncheckedUnwrap(wrapper);
84
0
    return CompartmentOriginInfo::Subsumes(js::GetObjectCompartment(wrapper),
85
0
                                           js::GetObjectCompartment(wrapped));
86
0
}
87
88
bool
89
AccessCheck::isChrome(JS::Compartment* compartment)
90
9
{
91
9
    return js::IsSystemCompartment(compartment);
92
9
}
93
94
bool
95
AccessCheck::isChrome(JSObject* obj)
96
9
{
97
9
    return isChrome(js::GetObjectCompartment(obj));
98
9
}
99
100
// Hardcoded policy for cross origin property access. See the HTML5 Spec.
101
static bool
102
IsPermitted(CrossOriginObjectType type, JSFlatString* prop, bool set)
103
0
{
104
0
    size_t propLength = JS_GetStringLength(JS_FORGET_STRING_FLATNESS(prop));
105
0
    if (!propLength) {
106
0
        return false;
107
0
    }
108
0
109
0
    char16_t propChar0 = JS_GetFlatStringCharAt(prop, 0);
110
0
    if (type == CrossOriginLocation) {
111
0
        return dom::Location_Binding::IsPermitted(prop, propChar0, set);
112
0
    }
113
0
    if (type == CrossOriginWindow) {
114
0
        return dom::Window_Binding::IsPermitted(prop, propChar0, set);
115
0
    }
116
0
117
0
    return false;
118
0
}
119
120
static bool
121
IsFrameId(JSContext* cx, JSObject* obj, jsid idArg)
122
0
{
123
0
    MOZ_ASSERT(!js::IsWrapper(obj));
124
0
    RootedId id(cx, idArg);
125
0
126
0
    nsGlobalWindowInner* win = WindowOrNull(obj);
127
0
    if (!win) {
128
0
        return false;
129
0
    }
130
0
131
0
    nsDOMWindowList* col = win->GetFrames();
132
0
    if (!col) {
133
0
        return false;
134
0
    }
135
0
136
0
    nsCOMPtr<mozIDOMWindowProxy> domwin;
137
0
    if (JSID_IS_INT(id)) {
138
0
        domwin = col->IndexedGetter(JSID_TO_INT(id));
139
0
    } else if (JSID_IS_STRING(id)) {
140
0
        nsAutoJSString idAsString;
141
0
        if (!idAsString.init(cx, JSID_TO_STRING(id))) {
142
0
            return false;
143
0
        }
144
0
        domwin = col->NamedItem(idAsString);
145
0
    }
146
0
147
0
    return domwin != nullptr;
148
0
}
149
150
CrossOriginObjectType
151
IdentifyCrossOriginObject(JSObject* obj)
152
0
{
153
0
    obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
154
0
    const js::Class* clasp = js::GetObjectClass(obj);
155
0
156
0
    if (clasp->name[0] == 'L' && !strcmp(clasp->name, "Location")) {
157
0
        return CrossOriginLocation;
158
0
    }
159
0
    if (clasp->name[0] == 'W' && !strcmp(clasp->name, "Window")) {
160
0
        return CrossOriginWindow;
161
0
    }
162
0
163
0
    return CrossOriginOpaque;
164
0
}
165
166
bool
167
AccessCheck::isCrossOriginAccessPermitted(JSContext* cx, HandleObject wrapper, HandleId id,
168
                                          Wrapper::Action act)
169
0
{
170
0
    if (act == Wrapper::CALL) {
171
0
        return false;
172
0
    }
173
0
174
0
    if (act == Wrapper::ENUMERATE) {
175
0
        return true;
176
0
    }
177
0
178
0
    // For the case of getting a property descriptor, we allow if either GET or SET
179
0
    // is allowed, and rely on FilteringWrapper to filter out any disallowed accessors.
180
0
    if (act == Wrapper::GET_PROPERTY_DESCRIPTOR) {
181
0
        return isCrossOriginAccessPermitted(cx, wrapper, id, Wrapper::GET) ||
182
0
               isCrossOriginAccessPermitted(cx, wrapper, id, Wrapper::SET);
183
0
    }
184
0
185
0
    RootedObject obj(cx, js::UncheckedUnwrap(wrapper, /* stopAtWindowProxy = */ false));
186
0
    CrossOriginObjectType type = IdentifyCrossOriginObject(obj);
187
0
    if (JSID_IS_STRING(id)) {
188
0
        if (IsPermitted(type, JSID_TO_FLAT_STRING(id), act == Wrapper::SET)) {
189
0
            return true;
190
0
        }
191
0
    }
192
0
193
0
    if (type != CrossOriginOpaque &&
194
0
        IsCrossOriginWhitelistedProp(cx, id)) {
195
0
        // We always allow access to "then", @@toStringTag, @@hasInstance, and
196
0
        // @@isConcatSpreadable.  But then we nerf them to be a value descriptor
197
0
        // with value undefined in CrossOriginXrayWrapper.
198
0
        return true;
199
0
    }
200
0
201
0
    if (act != Wrapper::GET) {
202
0
        return false;
203
0
    }
204
0
205
0
    // Check for frame IDs. If we're resolving named frames, make sure to only
206
0
    // resolve ones that don't shadow native properties. See bug 860494.
207
0
    if (type == CrossOriginWindow) {
208
0
        if (JSID_IS_STRING(id)) {
209
0
            bool wouldShadow = false;
210
0
            if (!XrayUtils::HasNativeProperty(cx, wrapper, id, &wouldShadow) ||
211
0
                wouldShadow)
212
0
            {
213
0
                // If the named subframe matches the name of a DOM constructor,
214
0
                // the global resolve triggered by the HasNativeProperty call
215
0
                // above will try to perform a CheckedUnwrap on |wrapper|, and
216
0
                // throw a security error if it fails. That exception isn't
217
0
                // really useful for our callers, so we silence it and just
218
0
                // deny access to the property (since it matched a builtin).
219
0
                //
220
0
                // Note that this would be a problem if the resolve code ever
221
0
                // tried to CheckedUnwrap the wrapper _before_ concluding that
222
0
                // the name corresponds to a builtin global property, since it
223
0
                // would mean that we'd never permit cross-origin named subframe
224
0
                // access (something we regrettably need to support).
225
0
                JS_ClearPendingException(cx);
226
0
                return false;
227
0
            }
228
0
        }
229
0
        return IsFrameId(cx, obj, id);
230
0
    }
231
0
    return false;
232
0
}
233
234
bool
235
AccessCheck::checkPassToPrivilegedCode(JSContext* cx, HandleObject wrapper, HandleValue v)
236
0
{
237
0
    // Primitives are fine.
238
0
    if (!v.isObject()) {
239
0
        return true;
240
0
    }
241
0
    RootedObject obj(cx, &v.toObject());
242
0
243
0
    // Non-wrappers are fine.
244
0
    if (!js::IsWrapper(obj)) {
245
0
        return true;
246
0
    }
247
0
248
0
    // CPOWs use COWs (in the unprivileged junk scope) for all child->parent
249
0
    // references. Without this test, the child process wouldn't be able to
250
0
    // pass any objects at all to CPOWs.
251
0
    if (mozilla::jsipc::IsWrappedCPOW(obj) &&
252
0
        js::GetObjectCompartment(wrapper) == js::GetObjectCompartment(xpc::UnprivilegedJunkScope()) &&
253
0
        XRE_IsParentProcess())
254
0
    {
255
0
        return true;
256
0
    }
257
0
258
0
    // Same-origin wrappers are fine.
259
0
    if (AccessCheck::wrapperSubsumes(obj)) {
260
0
        return true;
261
0
    }
262
0
263
0
    // Badness.
264
0
    JS_ReportErrorASCII(cx, "Permission denied to pass object to privileged code");
265
0
    return false;
266
0
}
267
268
bool
269
AccessCheck::checkPassToPrivilegedCode(JSContext* cx, HandleObject wrapper, const CallArgs& args)
270
0
{
271
0
    if (!checkPassToPrivilegedCode(cx, wrapper, args.thisv())) {
272
0
        return false;
273
0
    }
274
0
    for (size_t i = 0; i < args.length(); ++i) {
275
0
        if (!checkPassToPrivilegedCode(cx, wrapper, args[i])) {
276
0
            return false;
277
0
        }
278
0
    }
279
0
    return true;
280
0
}
281
282
void
283
AccessCheck::reportCrossOriginDenial(JSContext* cx, JS::HandleId id,
284
                                     const nsACString& accessType)
285
0
{
286
0
    // This function exists because we want to report DOM SecurityErrors, not JS
287
0
    // Errors, when denying access on cross-origin DOM objects.  It's
288
0
    // conceptually pretty similar to
289
0
    // AutoEnterPolicy::reportErrorIfExceptionIsNotPending.
290
0
    if (JS_IsExceptionPending(cx)) {
291
0
        return;
292
0
    }
293
0
294
0
    nsAutoCString message;
295
0
    if (JSID_IS_VOID(id)) {
296
0
        message = NS_LITERAL_CSTRING("Permission denied to access object");
297
0
    } else {
298
0
        // We want to use JS_ValueToSource here, because that most closely
299
0
        // matches what AutoEnterPolicy::reportErrorIfExceptionIsNotPending
300
0
        // does.
301
0
        JS::RootedValue idVal(cx, js::IdToValue(id));
302
0
        nsAutoJSString propName;
303
0
        JS::RootedString idStr(cx, JS_ValueToSource(cx, idVal));
304
0
        if (!idStr || !propName.init(cx, idStr)) {
305
0
            return;
306
0
        }
307
0
        message = NS_LITERAL_CSTRING("Permission denied to ") +
308
0
                  accessType +
309
0
                  NS_LITERAL_CSTRING(" property ") +
310
0
                  NS_ConvertUTF16toUTF8(propName) +
311
0
                  NS_LITERAL_CSTRING(" on cross-origin object");
312
0
    }
313
0
    ErrorResult rv;
314
0
    rv.ThrowDOMException(NS_ERROR_DOM_SECURITY_ERR, message);
315
0
    MOZ_ALWAYS_TRUE(rv.MaybeSetPendingException(cx));
316
0
}
317
318
bool
319
OpaqueWithSilentFailing::deny(JSContext* cx, js::Wrapper::Action act, HandleId id,
320
                              bool mayThrow)
321
0
{
322
0
    // Fail silently for GET, ENUMERATE, and GET_PROPERTY_DESCRIPTOR.
323
0
    if (act == js::Wrapper::GET || act == js::Wrapper::ENUMERATE ||
324
0
        act == js::Wrapper::GET_PROPERTY_DESCRIPTOR)
325
0
    {
326
0
        // Note that ReportWrapperDenial doesn't do any _exception_ reporting,
327
0
        // so we want to do this regardless of the value of mayThrow.
328
0
        return ReportWrapperDenial(cx, id, WrapperDenialForCOW,
329
0
                                   "Access to privileged JS object not permitted");
330
0
    }
331
0
332
0
    return false;
333
0
}
334
335
} // namespace xpc