Bug 1036214 - Do a subsumes check on object and any parameters (and things containing them) to JS-implemented WebIDL. r=bz

This commit is contained in:
Bobby Holley 2014-08-19 18:12:15 -07:00
parent fd4bd58644
commit 12abd6ffbc
4 changed files with 105 additions and 25 deletions

View File

@ -2645,5 +2645,12 @@ AssertReturnTypeMatchesJitinfo(const JSJitInfo* aJitInfo,
}
#endif
bool
CallerSubsumes(JSObject *aObject)
{
nsIPrincipal* objPrin = nsContentUtils::ObjectPrincipal(js::UncheckedUnwrap(aObject));
return nsContentUtils::SubjectPrincipal()->Subsumes(objPrin);
}
} // namespace dom
} // namespace mozilla

View File

@ -2937,6 +2937,18 @@ AssertReturnTypeMatchesJitinfo(const JSJitInfo* aJitinfo,
bool
CheckPermissions(JSContext* aCx, JSObject* aObj, const char* const aPermissions[]);
bool
CallerSubsumes(JSObject* aObject);
MOZ_ALWAYS_INLINE bool
CallerSubsumes(JS::Handle<JS::Value> aValue)
{
if (!aValue.isObject()) {
return true;
}
return CallerSubsumes(&aValue.toObject());
}
} // namespace dom
} // namespace mozilla

View File

@ -3572,6 +3572,9 @@ class JSToNativeConversionInfo():
for whether we have a JS::Value. Only used when
defaultValue is not None or when True is passed for
checkForValue to instantiateJSToNativeConversion.
${passedToJSImpl} replaced by an expression that evaluates to a boolean
for whether this value is being passed to a JS-
implemented interface.
declType: A CGThing representing the native C++ type we're converting
to. This is allowed to be None if the conversion code is
@ -3827,7 +3830,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
return templateBody
# A helper function for converting things that look like a JSObject*.
def handleJSObjectType(type, isMember, failureCode):
def handleJSObjectType(type, isMember, failureCode, exceptionCode, sourceDescription):
if not isMember:
if isOptional:
# We have a specialization of Optional that will use a
@ -3843,6 +3846,19 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
declType = CGGeneric("JSObject*")
declArgs = None
templateBody = "${declName} = &${val}.toObject();\n"
# For JS-implemented APIs, we refuse to allow passing objects that the
# API consumer does not subsume.
if not isinstance(descriptorProvider, Descriptor) or descriptorProvider.interface.isJSImplemented():
templateBody = fill("""
if ($${passedToJSImpl} && !CallerSubsumes($${val})) {
ThrowErrorMessage(cx, MSG_PERMISSION_DENIED_TO_PASS_ARG, "${sourceDescription}");
$*{exceptionCode}
}
""",
sourceDescription=sourceDescription,
exceptionCode=exceptionCode) + templateBody
setToNullCode = "${declName} = nullptr;\n"
template = wrapObjectTemplate(templateBody, type, setToNullCode,
failureCode)
@ -3917,7 +3933,8 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
# We only need holderName here to handle isExternal()
# interfaces, which use an internal holder for the
# conversion even when forceOwningType ends up true.
"holderName": "tempHolder"
"holderName": "tempHolder",
"passedToJSImpl": "${passedToJSImpl}"
})
# NOTE: Keep this in sync with variadic conversions as needed
@ -4021,7 +4038,8 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
# We only need holderName here to handle isExternal()
# interfaces, which use an internal holder for the
# conversion even when forceOwningType ends up true.
"holderName": "tempHolder"
"holderName": "tempHolder",
"passedToJSImpl": "${passedToJSImpl}"
})
templateBody = fill(
@ -4114,7 +4132,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
for memberType in interfaceMemberTypes:
name = getUnionMemberName(memberType)
interfaceObject.append(
CGGeneric("(failed = !%s.TrySetTo%s(cx, ${val}, tryNext)) || !tryNext" %
CGGeneric("(failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext" %
(unionArgumentObj, name)))
names.append(name)
interfaceObject = CGWrapper(CGList(interfaceObject, " ||\n"),
@ -4127,7 +4145,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
assert len(arrayObjectMemberTypes) == 1
name = getUnionMemberName(arrayObjectMemberTypes[0])
arrayObject = CGGeneric(
"done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext)) || !tryNext;\n" %
"done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n" %
(unionArgumentObj, name))
names.append(name)
else:
@ -4151,7 +4169,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
memberType = callbackMemberTypes[0]
name = getUnionMemberName(memberType)
callbackObject = CGGeneric(
"done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext)) || !tryNext;\n" %
"done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n" %
(unionArgumentObj, name))
names.append(name)
else:
@ -4162,7 +4180,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
assert len(dictionaryMemberTypes) == 1
name = getUnionMemberName(dictionaryMemberTypes[0])
setDictionary = CGGeneric(
"done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext)) || !tryNext;\n" %
"done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n" %
(unionArgumentObj, name))
names.append(name)
else:
@ -4173,7 +4191,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
assert len(mozMapMemberTypes) == 1
name = getUnionMemberName(mozMapMemberTypes[0])
mozMapObject = CGGeneric(
"done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext)) || !tryNext;\n" %
"done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n" %
(unionArgumentObj, name))
names.append(name)
else:
@ -4185,8 +4203,10 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
# Very important to NOT construct a temporary Rooted here, since the
# SetToObject call can call a Rooted constructor and we need to keep
# stack discipline for Rooted.
object = CGGeneric("%s.SetToObject(cx, &${val}.toObject());\n"
"done = true;\n" % unionArgumentObj)
object = CGGeneric("if (!%s.SetToObject(cx, &${val}.toObject(), ${passedToJSImpl})) {\n"
"%s"
"}\n"
"done = true;\n" % (unionArgumentObj, indent(exceptionCode)))
names.append(objectMemberTypes[0].name)
else:
object = None
@ -4425,7 +4445,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
if descriptor.nativeType == 'JSObject':
# XXXbz Workers code does this sometimes
assert descriptor.workers
return handleJSObjectType(type, isMember, failureCode)
return handleJSObjectType(type, isMember, failureCode, exceptionCode, sourceDescription)
if descriptor.interface.isCallback():
name = descriptor.interface.identifier.name
@ -4507,7 +4527,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
isCallbackReturnValue,
firstCap(sourceDescription)))
elif descriptor.workers:
return handleJSObjectType(type, isMember, failureCode)
return handleJSObjectType(type, isMember, failureCode, exceptionCode, sourceDescription)
else:
# Either external, or new-binding non-castable. We always have a
# holder for these, because we don't actually know whether we have
@ -4826,6 +4846,19 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
assert not isOptional
templateBody = "${declName} = ${val};\n"
# For JS-implemented APIs, we refuse to allow passing objects that the
# API consumer does not subsume.
if not isinstance(descriptorProvider, Descriptor) or descriptorProvider.interface.isJSImplemented():
templateBody = fill("""
if ($${passedToJSImpl} && !CallerSubsumes($${val})) {
ThrowErrorMessage(cx, MSG_PERMISSION_DENIED_TO_PASS_ARG, "${sourceDescription}");
$*{exceptionCode}
}
""",
sourceDescription=sourceDescription,
exceptionCode=exceptionCode) + templateBody
# We may not have a default value if we're being converted for
# a setter, say.
if defaultValue:
@ -4841,7 +4874,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
if type.isObject():
assert not isEnforceRange and not isClamp
return handleJSObjectType(type, isMember, failureCode)
return handleJSObjectType(type, isMember, failureCode, exceptionCode, sourceDescription)
if type.isDictionary():
# There are no nullable dictionaries
@ -4891,7 +4924,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
if type.nullable():
dictLoc += ".SetValue()"
template += ('if (!%s.Init(cx, %s, "%s")) {\n'
template += ('if (!%s.Init(cx, %s, "%s", ${passedToJSImpl})) {\n'
"%s"
"}\n" % (dictLoc, val, firstCap(sourceDescription),
exceptionCodeIndented.define()))
@ -5167,7 +5200,8 @@ class CGArgumentConverter(CGThing):
self.replacementVariables = {
"declName": "arg%d" % index,
"holderName": ("arg%d" % index) + "_holder",
"obj": "obj"
"obj": "obj",
"passedToJSImpl": toStringBool(isJSImplementedDescriptor(descriptorProvider))
}
self.replacementVariables["val"] = string.Template(
"args[${index}]").substitute(replacer)
@ -5245,7 +5279,8 @@ class CGArgumentConverter(CGThing):
# conversion even when forceOwningType ends up true.
"holderName": "tempHolder",
# Use the same ${obj} as for the variadic arg itself
"obj": replacer["obj"]
"obj": replacer["obj"],
"passedToJSImpl": toStringBool(isJSImplementedDescriptor(self.descriptorProvider))
}), 4)
variadicConversion += (" }\n"
@ -6741,7 +6776,8 @@ class CGMethodCall(CGThing):
"holderName": ("arg%d" % distinguishingIndex) + "_holder",
"val": distinguishingArg,
"obj": "obj",
"haveValue": "args.hasDefined(%d)" % distinguishingIndex
"haveValue": "args.hasDefined(%d)" % distinguishingIndex,
"passedToJSImpl": toStringBool(isJSImplementedDescriptor(descriptor))
},
checkForValue=argIsOptional)
caseBody.append(CGIndenter(testCode, indent))
@ -8316,9 +8352,22 @@ def getUnionTypeTemplateVars(unionType, type, descriptorProvider,
mUnion.mValue.mObject.SetValue(cx, obj);
mUnion.mType = mUnion.eObject;
""")
setter = ClassMethod("SetToObject", "void",
# It's a bit sketchy to do the security check after setting the value,
# but it keeps the code cleaner and lets us avoid rooting |obj| over the
# call to CallerSubsumes().
body = body + dedent("""
if (passedToJSImpl && !CallerSubsumes(obj)) {
ThrowErrorMessage(cx, MSG_PERMISSION_DENIED_TO_PASS_ARG, "%s");
return false;
}
return true;
""")
setter = ClassMethod("SetToObject", "bool",
[Argument("JSContext*", "cx"),
Argument("JSObject*", "obj")],
Argument("JSObject*", "obj"),
Argument("bool", "passedToJSImpl", default="false")],
inline=True, bodyInHeader=True,
body=body)
@ -8338,7 +8387,8 @@ def getUnionTypeTemplateVars(unionType, type, descriptorProvider,
val="value",
declName="memberSlot",
holderName=(holderName if ownsMembers else "%s.ref()" % holderName),
destroyHolder=destroyHolder)
destroyHolder=destroyHolder,
passedToJSImpl="passedToJSImpl")
jsConversion = fill(
"""
@ -8357,7 +8407,8 @@ def getUnionTypeTemplateVars(unionType, type, descriptorProvider,
setter = ClassMethod("TrySetTo" + name, "bool",
[Argument("JSContext*", "cx"),
Argument("JS::Handle<JS::Value>", "value"),
Argument("bool&", "tryNext")],
Argument("bool&", "tryNext"),
Argument("bool", "passedToJSImpl", default="false")],
inline=not ownsMembers,
bodyInHeader=not ownsMembers,
body=jsConversion)
@ -9463,7 +9514,8 @@ class CGProxySpecialOperation(CGPerSignatureCall):
"declName": argument.identifier.name,
"holderName": argument.identifier.name + "_holder",
"val": argumentMutableValue,
"obj": "obj"
"obj": "obj",
"passedToJSImpl": "false"
}
self.cgRoot.prepend(instantiateJSToNativeConversion(info, templateValues))
elif operation.isGetter() or operation.isDeleter():
@ -11087,7 +11139,8 @@ class CGDictionary(CGThing):
return ClassMethod("Init", "bool", [
Argument('JSContext*', 'cx'),
Argument('JS::Handle<JS::Value>', 'val'),
Argument('const char*', 'sourceDescription', default='"Value"')
Argument('const char*', 'sourceDescription', default='"Value"'),
Argument('bool', 'passedToJSImpl', default='false')
], body=body)
def initFromJSONMethod(self):
@ -11322,7 +11375,8 @@ class CGDictionary(CGThing):
# We need a holder name for external interfaces, but
# it's scoped down to the conversion so we can just use
# anything we want.
"holderName": "holder"
"holderName": "holder",
"passedToJSImpl": "passedToJSImpl"
}
# We can't handle having a holderType here
assert conversionInfo.holderType is None
@ -11458,6 +11512,11 @@ class CGDictionary(CGThing):
trace = CGGeneric('%s.TraceSelf(trc);\n' % memberData)
if type.nullable():
trace = CGIfWrapper(trace, "!%s.IsNull()" % memberNullable)
elif type.isMozMap():
# If you implement this, add a MozMap<object> to
# TestInterfaceJSDictionary and test it in test_bug1036214.html
# to make sure we end up with the correct security properties.
assert False
else:
assert False # unknown type
@ -13389,7 +13448,8 @@ class CallbackMember(CGNativeMember):
# We actually want to pass in a null scope object here, because
# wrapping things into our current compartment (that of mCallback)
# is what we want.
"obj": "nullptr"
"obj": "nullptr",
"passedToJSImpl": "false"
}
if isJSImplementedDescriptor(self.descriptorProvider):

View File

@ -58,3 +58,4 @@ MSG_DEF(MSG_HEADERS_IMMUTABLE, 0, "Headers are immutable and cannot be modified.
MSG_DEF(MSG_INVALID_HEADER_NAME, 1, "{0} is an invalid header name.")
MSG_DEF(MSG_INVALID_HEADER_VALUE, 1, "{0} is an invalid header value.")
MSG_DEF(MSG_INVALID_HEADER_SEQUENCE, 0, "Headers require name/value tuples when being initialized by a sequence.")
MSG_DEF(MSG_PERMISSION_DENIED_TO_PASS_ARG, 1, "Permission denied to pass cross-origin object as {0}.")