可以显式删除 lambda 的序列化支持 [英] Possibility to explicit remove Serialization support for a 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");
}
}
这里,即使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 方法.如果没有该标志,即使接口碰巧继承了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屋!