Hol*_*ger 35 java lambda serialization java-8
由于已经知道它很容易地添加序列化支持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();
}
}
Run Code Online (Sandbox Code Playgroud)
但是对于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");
}
}
Run Code Online (Sandbox Code Playgroud)
在这里,只要程序员注意,即使TargetInterface是public(接口方法总是public),也不会公开对私有方法的访问,而不是将实例传递f给不受信任的代码.
但是,如果TargetInterface继承,事情会发生变化Serializable.然后,即使CreationSite永远不会发出实例,攻击者也可以通过反序列化手动构造的流来创建等效实例.如果上面示例的界面看起来像
public interface TargetInterface extends Runnable, Serializable {}
Run Code Online (Sandbox Code Playgroud)
它很简单:
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
Run Code Online (Sandbox Code Playgroud)
请注意,攻击代码不包含任何SecurityManager将撤消的操作.
支持序列化的决定是在编译时完成的.它需要添加一个合成工厂方法,CreationSite并将一个标志传递给metafactory方法.如果没有该标志,即使接口恰好继承,生成的lambda也不支持Serialization Serializable.lambda类甚至可以使用上面示例中的writeObject方法NotSupportingSerialization.如果没有合成工厂方法,De-Serialization是不可能的.
这找到了一个解决方案.您可以创建接口的副本并将其修改为不继承Serializable,然后针对该修改版本进行编译.因此,当运行时的实际版本碰巧继承时Serializable,仍然会撤消Serialization.
好吧,另一种解决方案是永远不要在安全相关代码中使用lambda表达式/方法引用,至少在目标接口继承时Serializable,必须始终重新检查,在针对较新版本的接口进行编译时.
但我认为必须有更好的,最好是语言内的解决方案.
Bri*_*etz 29
如何处理可串行化是EG的最大挑战之一; 足以说没有很好的解决方案,只能在各种缺点之间进行权衡.有些人坚持认为所有lambdas都可以自动序列化(!); 其他人坚持认为lambdas永远不可序列化(有时这似乎是一个很有吸引力的想法,但遗憾的是会严重违反用户的期望.)
你注意到:
那么,另一种解决方案是永远不要在安全相关代码中使用lambda表达式/方法引用,
事实上,序列化规范现在正是如此.
但是,有一个相当容易的技巧来做你想要的事情.假设您有一些需要可序列化实例的库:
public interface SomeLibType extends Runnable, Serializable { }
Run Code Online (Sandbox Code Playgroud)
使用期望此类型的方法:
public void gimmeLambda(SomeLibType r)
Run Code Online (Sandbox Code Playgroud)
并且你想将lambdas传递给它,但是没有它们可序列化(并且考虑到它的后果.)所以,写下你自己的帮助器方法:
public static SomeLibType launder(Runnable r) {
return new SomeLibType() {
public void run() { r.run(); }
}
}
Run Code Online (Sandbox Code Playgroud)
现在你可以调用库方法:
gimmeLambda(launder(() -> myPrivateMethod()));
Run Code Online (Sandbox Code Playgroud)
编译器会将您的lambda转换为不可序列化的Runnable,并且清洗包装器将使用满足类型系统的实例来包装它.当您尝试序列化它时,将失败,因为r不可序列化.更重要的是,您无法伪造对私有方法的访问权限,因为捕获类中所需的$ deserializeLambda $支持甚至不会存在.
| 归档时间: |
|
| 查看次数: |
2423 次 |
| 最近记录: |