可以显式删除lambda的序列化支持 [英] Possibility to explicit remove Serialization support for a 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 $ c也拒绝序列化$ c>过去是一个合法的行为,并且在程序员控制的类下,模式看起来像:
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屋!