diff options
author | Jason Zaugg <jzaugg@gmail.com> | 2016-08-04 01:58:33 -0700 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2016-08-08 13:42:36 +1000 |
commit | 498a2ce7397b909c0bebf36affeb1ee5a1c03d6a (patch) | |
tree | abcfcf39af3231ade6402d30acca548704711311 /src/compiler/scala/tools/nsc/backend/jvm | |
parent | 2b172be8c83c3146d3fd5ab01546c171ab18fa46 (diff) | |
download | scala-498a2ce7397b909c0bebf36affeb1ee5a1c03d6a.tar.gz scala-498a2ce7397b909c0bebf36affeb1ee5a1c03d6a.tar.bz2 scala-498a2ce7397b909c0bebf36affeb1ee5a1c03d6a.zip |
SD-193 Lock down lambda deserialization
The old design allowed a forged `SerializedLambda` to be
deserialized into a lambda that could call any private method
in the host class.
This commit passes through the list of all lambda impl methods
to the bootstrap method and verifies that you are deserializing
one of these.
The new test case shows that a forged lambda can no longer call
the private method, and that the new encoding is okay with a large
number of lambdas in a file.
We already have method handle constants in the constant pool
to support the invokedynamic through LambdaMetafactory, so
the only additional cost will be referring to these in
the boostrap args for `LambdaDeserialize`, 2 bytes per lambda.
I checked this with an example:
https://gist.github.com/retronym/e343d211f7536d06f1fef4b499a0a177
Fixes SD-193
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm')
7 files changed, 41 insertions, 24 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index d5c4b5e201..6f9682f434 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -14,6 +14,7 @@ import scala.reflect.internal.Flags import scala.tools.asm import GenBCode._ import BackendReporting._ +import scala.collection.mutable import scala.tools.asm.Opcodes import scala.tools.asm.tree.{MethodInsnNode, MethodNode} import scala.tools.nsc.backend.jvm.BCodeHelpers.{InvokeStyle, TestOp} @@ -1349,7 +1350,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val markers = if (addScalaSerializableMarker) classBTypeFromSymbol(definitions.SerializableClass).toASMType :: Nil else Nil visitInvokeDynamicInsnLMF(bc.jmethod, sam.name.toString, invokedType, samMethodType, implMethodHandle, constrainedType, isSerializable, markers) if (isSerializable) - indyLambdaHosts += cnode.name + addIndyLambdaImplMethod(cnode.name, implMethodHandle :: Nil) } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index 1bff8519ec..d4d532f4df 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -112,14 +112,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { gen(cd.impl) - val shouldAddLambdaDeserialize = ( - settings.target.value == "jvm-1.8" - && settings.Ydelambdafy.value == "method" - && indyLambdaHosts.contains(cnode.name)) - - if (shouldAddLambdaDeserialize) - backendUtils.addLambdaDeserialize(cnode) - cnode.visitAttribute(thisBType.inlineInfoAttribute.get) if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern)) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 7b2686e7a9..0845e440d7 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -122,7 +122,16 @@ abstract class BTypes { * inlining: when inlining an indyLambda instruction into a class, we need to make sure the class * has the method. */ - val indyLambdaHosts: mutable.Set[InternalName] = recordPerRunCache(mutable.Set.empty) + val indyLambdaImplMethods: mutable.AnyRefMap[InternalName, mutable.LinkedHashSet[asm.Handle]] = recordPerRunCache(mutable.AnyRefMap()) + def addIndyLambdaImplMethod(hostClass: InternalName, handle: Seq[asm.Handle]): Unit = { + indyLambdaImplMethods.getOrElseUpdate(hostClass, mutable.LinkedHashSet()) ++= handle + } + def getIndyLambdaImplMethods(hostClass: InternalName): List[asm.Handle] = { + indyLambdaImplMethods.getOrNull(hostClass) match { + case null => Nil + case xs => xs.toList.distinct + } + } /** * Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala index c2010d2828..1dbb18722f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala @@ -283,7 +283,21 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { List( coreBTypes.jliMethodHandlesLookupRef, coreBTypes.StringRef, - coreBTypes.jliMethodTypeRef + coreBTypes.jliMethodTypeRef, + ArrayBType(jliMethodHandleRef) + ), + coreBTypes.jliCallSiteRef + ).descriptor, + /* itf = */ coreBTypes.srLambdaDeserialize.isInterface.get) + lazy val lambdaDeserializeAddTargets = + new scala.tools.asm.Handle(scala.tools.asm.Opcodes.H_INVOKESTATIC, + coreBTypes.srLambdaDeserialize.internalName, "bootstrapAddTargets", + MethodBType( + List( + coreBTypes.jliMethodHandlesLookupRef, + coreBTypes.StringRef, + coreBTypes.jliMethodTypeRef, + ArrayBType(coreBTypes.jliMethodHandleRef) ), coreBTypes.jliCallSiteRef ).descriptor, diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index 584b11d4ed..0a54767f76 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -266,6 +266,9 @@ abstract class GenBCode extends BCodeSyncAndTry { try { localOptimizations(item.plain) setInnerClasses(item.plain) + val lambdaImplMethods = getIndyLambdaImplMethods(item.plain.name) + if (lambdaImplMethods.nonEmpty) + backendUtils.addLambdaDeserialize(item.plain, lambdaImplMethods) setInnerClasses(item.mirror) setInnerClasses(item.bean) addToQ3(item) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala index 83615abc31..d85d85003d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala @@ -76,7 +76,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { * host a static field in the enclosing class. This allows us to add this method to interfaces * that define lambdas in default methods. */ - def addLambdaDeserialize(classNode: ClassNode): Unit = { + def addLambdaDeserialize(classNode: ClassNode, implMethods: List[Handle]): Unit = { val cw = classNode // Make sure to reference the ClassBTypes of all types that are used in the code generated @@ -87,12 +87,13 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { val nilLookupDesc = MethodBType(Nil, jliMethodHandlesLookupRef).descriptor val serlamObjDesc = MethodBType(jliSerializedLambdaRef :: Nil, ObjectRef).descriptor + val addTargetMethodsObjDesc = MethodBType(ObjectRef :: Nil, UNIT).descriptor { val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", serlamObjDesc, null, null) mv.visitCode() mv.visitVarInsn(ALOAD, 0) - mv.visitInvokeDynamicInsn("lambdaDeserialize", serlamObjDesc, lambdaDeserializeBootstrapHandle) + mv.visitInvokeDynamicInsn("lambdaDeserialize", serlamObjDesc, lambdaDeserializeBootstrapHandle, implMethods: _*) mv.visitInsn(ARETURN) mv.visitEnd() } @@ -104,16 +105,16 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { * a boolean indicating if the instruction list contains an instantiation of a serializable SAM * type. */ - def cloneInstructions(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode], keepLineNumbers: Boolean): (InsnList, Map[AbstractInsnNode, AbstractInsnNode], Boolean) = { + def cloneInstructions(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode], keepLineNumbers: Boolean): (InsnList, Map[AbstractInsnNode, AbstractInsnNode], List[Handle]) = { val javaLabelMap = labelMap.asJava val result = new InsnList var map = Map.empty[AbstractInsnNode, AbstractInsnNode] - var hasSerializableClosureInstantiation = false + var inlinedTargetHandles = mutable.ListBuffer[Handle]() for (ins <- methodNode.instructions.iterator.asScala) { - if (!hasSerializableClosureInstantiation) ins match { + ins match { case callGraph.LambdaMetaFactoryCall(indy, _, _, _) => indy.bsmArgs match { - case Array(_, _, _, flags: Integer, xs@_*) if (flags.intValue & LambdaMetafactory.FLAG_SERIALIZABLE) != 0 => - hasSerializableClosureInstantiation = true + case Array(_, targetHandle: Handle, _, flags: Integer, xs@_*) if (flags.intValue & LambdaMetafactory.FLAG_SERIALIZABLE) != 0 => + inlinedTargetHandles += targetHandle case _ => } case _ => @@ -124,7 +125,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { map += ((ins, cloned)) } } - (result, map, hasSerializableClosureInstantiation) + (result, map, inlinedTargetHandles.toList) } def getBoxedUnit: FieldInsnNode = new FieldInsnNode(GETSTATIC, srBoxedUnitRef.internalName, "UNIT", srBoxedUnitRef.descriptor) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala index 9c5a1a9f98..a7916f9c24 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -277,7 +277,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { } case _ => false } - val (clonedInstructions, instructionMap, hasSerializableClosureInstantiation) = cloneInstructions(callee, labelsMap, keepLineNumbers = sameSourceFile) + val (clonedInstructions, instructionMap, targetHandles) = cloneInstructions(callee, labelsMap, keepLineNumbers = sameSourceFile) // local vars in the callee are shifted by the number of locals at the callsite val localVarShift = callsiteMethod.maxLocals @@ -405,10 +405,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { callsiteMethod.maxStack = math.max(callsiteMethod.maxStack, math.max(stackHeightAtNullCheck, maxStackOfInlinedCode)) - if (hasSerializableClosureInstantiation && !indyLambdaHosts(callsiteClass.internalName)) { - indyLambdaHosts += callsiteClass.internalName - addLambdaDeserialize(byteCodeRepository.classNode(callsiteClass.internalName).get) - } + addIndyLambdaImplMethod(callsiteClass.internalName, targetHandles) callGraph.addIfMissing(callee, calleeDeclarationClass) |