可以显式删除 lambda 的序列化支持 [英] Possibility to explicit remove Serialization support for a lambda

查看:27
本文介绍了可以显式删除 lambda 的序列化支持的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

正如众所周知,很容易为 lambda 表达式添加序列化支持,当目标interface 还没有继承 Serializable,就像 (TargetInterface&Serializable)()->{/*code*/}.

As already known it’s easy to add Serialization support to a lambda expression when the target interface does not already inherit Serializable, just like (TargetInterface&Serializable)()->{/*code*/}.

我要求的是一种相反的方法,当目标接口确实继承Serializable时,明确删除序列化支持.

What I ask for, is a way to do the opposite, explicitly remove Serialization support when the target interface does inherit Serializable.

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

Since you can’t remove an interface from a type a language-based solution would possibly look like (@NotSerializable TargetInterface)()->{/* code */}. But as far as I know, there is no such solution. (Correct me if I’m wrong, that would be a perfect answer)

即使类实现了Serializable,但拒绝序列化在过去是一种合法行为,并且在程序员控制下的类中,该模式如下所示:

Denying the serialization even when the class implements Serializable was a legitimate behavior in the past and with classes under the programmers control, the pattern would look like:

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 类.

But for lambda expression, the programmer doesn’t have that control over the lambda class.

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

Why would someone ever bother about removing the support? Well, beside the bigger code generated to include the Serialization support, it creates a security risk. Consider the following code:

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

这里,即使TargetInterfacepublic(接口方法总是public),对私有方法的访问也不会暴露正如程序员所注意的那样,不要将实例 f 传递给不受信任的代码.

Here, the access to the private method is not exposed even if the TargetInterface is public (interface methods are always public) as long as the programmer takes care, not to pass the instance f to untrusted code.

然而,如果 TargetInterface 继承 Serializable,事情就会改变.然后,即使 CreationSite 从未分发实例,攻击者也可以通过反序列化手动构建的流来创建等效的实例.如果上面例子的界面看起来像

However, things change if TargetInterface inherits Serializable. Then, even if the CreationSite never hands out an instance, an attacker could create an equivalent instance by de-serializing a manually constructed stream. If the interface for the above example looks like

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

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

Note that the attacking code does not contain any action that a SecurityManager would revoke.

支持序列化的决定是在编译时做出的.它需要一个合成工厂方法添加到 CreationSite 和一个 标志 传递给 metafactory 方法.如果没有该标志,即使接口碰巧继承了Serializable,生成的 lambda 也不会支持序列化.lambda 类甚至会有一个 writeObject 方法,就像上面的 NotSupportingSerialization 示例一样.没有合成工厂方法,反序列化是不可能的.

The decision to support Serialization is made at compile-time. It requires a synthetic factory method added to CreationSite and a flag passed to the metafactory method. Without the flag, the generated lambda will not support Serialization even if the interface happens to inherit Serializable. The lambda class will even have a writeObject method like in the NotSupportingSerialization example above. And without the synthetic factory method, De-Serialization is impossible.

这导致了一个解决方案,我发现.您可以创建接口的副本并将其修改为不继承 Serializable,然后针对修改后的版本进行编译.所以当运行时的真实版本碰巧继承了Serializable时,Serialization还是会被撤销.

This leads to the one solution, I found. You can create a copy of the interface and modify it to not inherit Serializable, then compile against that modified version. So when the real version at runtime happens to inherit Serializable, Serialization will still be revoked.

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

Well, another solution is to never use lambda expressions/method references in security relevant code, at least if the target interface inherits Serializable which must always be re-checked, when compiling against a newer version of the interface.

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

But I think there must be better, preferably in-language solutions.

推荐答案

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

How to handle serializability was one of the biggest challenges for the EG; suffice it to say that there were no great solutions, only tradeoffs between various downsides. Some parties insisted that all lambdas be automatically serializable (!); others insisted that lambdas never be serializable (which seemed an attractive idea at times, but sadly would badly violate user expectations.)

请注意:

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

Well, another solution is to never use lambda expressions/method references in security relevant code,

事实上,序列化规范现在正是这么说的.

In fact, the serialization spec now says exactly that.

但是,这里有一个相当简单的技巧可以做你想做的事.假设您有一些需要可序列化实例的库:

But, there is a fairly easy trick to do what you want here. Suppose you have some library that wants serializable instances:

public interface SomeLibType extends Runnable, Serializable { }

使用需要这种类型的方法:

with methods that expect this type:

public void gimmeLambda(SomeLibType r)

并且您想将 lambdas 传递给它,但不让它们可序列化(并承担其后果.)所以,自己编写这个辅助方法:

and you want to pass lambdas into it, but not have them be serializable (and take the consequences of that.) So, write yourself this helper method:

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

现在可以调用库方法了:

Now you can call the library method:

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

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

The compiler will convert your lambda into a non-serializable Runnable, and the laundering wrapper will wrap it with an instance that satisifies the type system. When you try to serialize it, that will fail since r is not serializable. More importantly, you can't forge access to the private method, because the $deserializeLambda$ support that's needed in the capturing class won't even be there.

这篇关于可以显式删除 lambda 的序列化支持的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆