当前位置: 首页 > 面试题库 >

明确删除对lambda的序列化支持的可能性

宣原
2023-03-14
问题内容

由于已经知道它很容易地添加 _序列化_支持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();
    }
}

但是对于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");
    }
}

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

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

public interface TargetInterface extends Runnable, Serializable {}

就像这样简单:

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

请注意,攻击代码不包含a SecurityManager将会撤销的任何操作。

支持序列化的决定是在编译时做出的。它需要添加一个合成工厂方法,CreationSite并将标记传递给元工厂方法。没有该标志,即使接口碰巧继承了,生成的lambda也将不支持序列化Serializable。lambda类甚至具有上面示例中的writeObject方法NotSupportingSerialization。如果没有合成工厂方法,则无法进行反序列化。

我找到了一种解决方案。您可以创建接口的副本,并将其修改为不继承Serializable,然后针对该修改后的版本进行编译。因此,当在运行时继承真实版本时Serializable,序列化仍将被撤销。

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

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


问题答案:

EG面临的最大挑战之一是如何处理可序列化性。可以说,没有伟大的解决方案,只有各种弊端之间的权衡。一些团体坚持认为所有lambda都可以自动序列化(!);其他人则坚持认为lambda永远不可序列化(这有时看起来很吸引人,但可悲的是,这将严重违反用户的期望。)

您注意:

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

实际上,序列化规范现在已明确说明了这一点。

但是,有一个相当简单的技巧可以在这里执行您想要的操作。假设您有一些需要可序列化实例的库:

public interface SomeLibType extends Runnable, Serializable { }

使用期望这种类型的方法:

public void gimmeLambda(SomeLibType r)

并且您想将lambda传递给它,但又不想让它们可序列化(并承担其后果。)因此,请编写此辅助方法:

public static SomeLibType launder(Runnable r) {
    return new SomeLibType() {
        public void run() { r.run(); }
    }
}

现在您可以调用库方法:

gimmeLambda(launder(() -> myPrivateMethod()));

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



 类似资料:
  • 众所周知,当目标接口尚未继承可序列化的时,很容易向lambda表达式添加序列化支持,就像 我要求的是一种相反的方法,即在目标接口继承时显式删除序列化支持。 由于无法从类型中删除接口,基于语言的解决方案可能看起来像是(@NotSerializable TargetInterface)()- 即使在类实现可序列化时拒绝序列化在过去是一种合法的行为,并且类在程序员的控制下,模式如下: 但是对于lambd

  • 问题内容: 我收到以下消息: [#| 2010-07-30T11:28:32.723 + 0000 |警告| glassfish3.0.1 | javax.faces | _ThreadID = 37; _ThreadName = Thread-1; |将不可序列化的属性值设置为ViewMap:((键:MyBackingBean,值类:foo.bar.org.jsf.MyBackingBean)|

  • 我想缓存来自第三方API的响应,该API提供自己的客户端和数据类。但问题是,这两个数据类都没有实现接口,因此使用Spring Boot Cache Redis进行缓存时,在我尝试时抛出。 是否可以通过某种方式将Spring Boot Redis缓存配置为允许缓存未实现可序列化的对象?

  • 我在搞lambdas的Java序列化。 我有两个完全独立的项目,其中只有一个类。 项目1: 项目二: 但是,在尝试反序列化可运行文件时,它会抛出一个异常,说它找不到 ...我有什么办法可以避免这种情况吗?

  • 我想将前面文章中提到的要求扩展到支持删除。我们有两个数据模型对象组织和部门共享一对多的关系。通过下面的映射,我可以从organization对象读取部门列表。我没有添加cascade ALL属性来限制在创建组织时添加部门。

  • 我正在试验Stanford CoreNLP库,我想序列化主要的StanfordCoreNLP管道对象,尽管它抛出了一个java.io.NotSerializableException。 完整故事:每当我运行我的实现时,将管道注释器和分类器加载到内存中大约需要15秒。最终进程的内存约为600MB(很容易小到可以存储在我的机箱中)。我想在第一次创建管道后保存它,这样我就可以在以后将其读入内存。 然而,