可以显式删除lambda的序列化支持

Hol*_*ger 35 java lambda serialization java-8

由于已经知道它很容易地添加序列化支持lambda表达式时,目标接口已经不继承Serializable,只是喜欢(TargetInterface&Serializable)()->{/*code*/}.

我问,是一种反其道而行之,明确删除支持串行当目标接口继承Serializable.

由于您无法从类型中删除接口,因此基于语言的解决方案可能看起来像(@NotSerializable TargetInterface)()->{/* code */}.但据我所知,没有这样的解决方案.(纠正我,如果我错了,这将是一个完美的答案)

即使在类实现时拒绝序列化是Serializable过去的合法行为,并且程序员控制下的类,模式看起来如下:

public class NotSupportingSerialization extends SerializableBaseClass {
    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
      throw new NotSerializableException();
    }
    private void readObject(java.io.ObjectInputStream in)
      throws IOException, ClassNotFoundException {
      throw new NotSerializableException();
    }
    private void readObjectNoData() throws ObjectStreamException {
      throw new NotSerializableException();
    }
}
Run Code Online (Sandbox Code Playgroud)

但是对于lambda表达式,程序员没有对lambda类的控制.


为什么有人会费心去除支持?好吧,除了生成包含Serialization支持的更大代码之外,它还会产生安全风险.请考虑以下代码:

public class CreationSite {
    public static void main(String... arg) {
        TargetInterface f=CreationSite::privateMethod;
    }
    private static void privateMethod() {
        System.out.println("should be private");
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里,只要程序员注意,即使TargetInterfacepublic(接口方法总是public),也不会公开对私有方法的访问,而不是将实例传递f给不受信任的代码.

但是,如果TargetInterface继承,事情会发生变化Serializable.然后,即使CreationSite永远不会发出实例,攻击者也可以通过反序列化手动构造的流来创建等效实例.如果上面示例的界面看起来像

public interface TargetInterface extends Runnable, Serializable {}
Run Code Online (Sandbox Code Playgroud)

它很简单:

SerializedLambda l=new SerializedLambda(CreationSite.class,
    TargetInterface.class.getName().replace('.', '/'), "run", "()V",
    MethodHandleInfo.REF_invokeStatic,
    CreationSite.class.getName().replace('.', '/'), "privateMethod",
    "()V", "()V", new Object[0]);
ByteArrayOutputStream os=new ByteArrayOutputStream();
try(ObjectOutputStream oos=new ObjectOutputStream(os)) { oos.writeObject(l);}
TargetInterface f;
try(ByteArrayInputStream is=new ByteArrayInputStream(os.toByteArray());
    ObjectInputStream ois=new ObjectInputStream(is)) {
    f=(TargetInterface) ois.readObject();
}
f.run();// invokes privateMethod
Run Code Online (Sandbox Code Playgroud)

请注意,攻击代码不包含任何SecurityManager将撤消的操作.


支持序列化的决定是在编译时完成的.它需要添加一个合成工厂方法,CreationSite并将一个标志传递给metafactory方法.如果没有该标志,即使接口恰好继承,生成的lambda也不支持Serialization Serializable.lambda类甚至可以使用上面示例中的writeObject方法NotSupportingSerialization.如果没有合成工厂方法,De-Serialization是不可能的.

这找到了一个解决方案.您可以创建接口的副本并将其修改为不继承Serializable,然后针对该修改版本进行编译.因此,当运行时的实际版本碰巧继承时Serializable,仍然会撤消Serialization.

好吧,另一种解决方案是永远不要在安全相关代码中使用lambda表达式/方法引用,至少在目标接口继承时Serializable,必须始终重新检查,在针对较新版本的接口进行编译时.

但我认为必须有更好的,最好是语言内的解决方案.

Bri*_*etz 29

如何处理可串行化是EG的最大挑战之一; 足以说没有很好的解决方案,只能在各种缺点之间进行权衡.有些人坚持认为所有lambdas都可以自动序列化(!); 其他人坚持认为lambdas永远不可序列化(有时这似乎是一个很有吸引力的想法,但遗憾的是会严重违反用户的期望.)

你注意到:

那么,另一种解决方案是永远不要在安全相关代码中使用lambda表达式/方法引用,

事实上,序列化规范现在正是如此.

但是,有一个相当容易的技巧来做你想要的事情.假设您有一些需要可序列化实例的库:

public interface SomeLibType extends Runnable, Serializable { }
Run Code Online (Sandbox Code Playgroud)

使用期望此类型的方法:

public void gimmeLambda(SomeLibType r)
Run Code Online (Sandbox Code Playgroud)

并且你想将lambdas传递给它,但是没有它们可序列化(并且考虑到它的后果.)所以,写下你自己的帮助器方法:

public static SomeLibType launder(Runnable r) {
    return new SomeLibType() {
        public void run() { r.run(); }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你可以调用库方法:

gimmeLambda(launder(() -> myPrivateMethod()));
Run Code Online (Sandbox Code Playgroud)

编译器会将您的lambda转换为不可序列化的Runnable,并且清洗包装器将使用满足类型系统的实例来包装它.当您尝试序列化它时,将失败,因为r不可序列化.更重要的是,您无法伪造对私有方法的访问权限,因为捕获类中所需的$ deserializeLambda $支持甚至不会存在.

  • 从什么时候开始序列化"语言语义"?这是一个*库*功能,并且将类标记为可序列化应该首先是注释而不是标记接口.同样适用于`transient`关键字.好吧,引入序列化时不存在注释,但这不应该推动未来语言的设计.当然,注释也可以用于继承,现在我们有了类型注释.因此,如果它应该是整个图片,请将它们全部添加:`@ Serializable`,`@ NotSerializable`,`@ Transient`并使`Serializable`弃用... (17认同)
  • @Holger我们不会通过注释添加语言语义.期.因此,如果我们有这个,它肯定不会是基于注释的.但是,如果我们这样做,那将不仅仅是lambdas - 同样的事情必须适用于任何可序列化基于继承(命名类,匿名类等)的东西.始终对想法持开放态度...... (7认同)
  • @BrianGoetz四年后,@ Serializable和/或@Transient的想法是什么?我还没有从建筑师那里听到任何消息,但似乎仍然有意义。还确定不吗?你们现在在围栏上,看到其他语言如何证明它可行吗? (6认同)
  • 我仍然非常有兴趣听到您对 @Holger 最后评论的回应。 (2认同)