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

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

问题描述

已知时,很容易在目标时为lambda表达式添加序列化支持接口尚未继承 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*/}.

我要求的是一种相反的方法,当目标接口 继承<$>时,显式删除序列化支持c $ c> 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.

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

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");
    }
}

此处,未公开对私有方法的访问权限即使 TargetInterface public (接口方法总是 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 方法。如果没有标志,生成的lambda将不支持序列化,即使接口恰好继承 Serializable 。 lambda类甚至会有一个 writeObject 方法,就像上面的 NotSupportingSerialization 示例一样。如果没有合成工厂方法,De-Serialization是不可能的。

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 时,序列化仍将被撤销。

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面临的最大挑战之一;足以说没有很好的解决方案,只能在各种缺点之间进行权衡。有些人坚持要求所有lambdas都可以自动序列化(!);其他人坚持认为lambdas永远不可序列化(这有时候看起来很有吸引力,但遗憾的是会严重违反用户期望。)

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天全站免登陆