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 b4e3c103d2
commit 9b4a386a14
4 changed files with 105 additions and 25 deletions

View File

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

View File

@ -2937,6 +2937,18 @@ AssertReturnTypeMatchesJitinfo(const JSJitInfo* aJitinfo,
bool bool
CheckPermissions(JSContext* aCx, JSObject* aObj, const char* const aPermissions[]); 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 dom
} // namespace mozilla } // namespace mozilla

View File

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