显式删除对 lambda 的序列化支持的可能性
众所周知,当目标接口尚未继承时,很容易将序列化支持添加到 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();
}
}
但对于 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");
}
}
在这里,只要程序员小心,对私有方法的访问也不会公开,即使是(接口方法总是),不要将实例传递给不受信任的代码。TargetInterface
public
public
f
但是,如果继承 ,则情况会发生变化。然后,即使从不分发实例,攻击者也可以通过反序列化手动构造的流来创建等效实例。如果上述示例的接口如下所示TargetInterface
Serializable
CreationSite
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
支持序列化的决定是在编译时做出的。它需要将合成工厂方法添加到元工厂方法,并将标志传递给元工厂方法。如果没有该标志,生成的 lambda 将不支持序列化,即使接口碰巧继承 。lambda 类甚至会有一个方法,如上面的示例所示。如果没有合成工厂方法,反序列化是不可能的。CreationSite
Serializable
writeObject
NotSupportingSerialization
我发现,这导致了一个解决方案。您可以创建接口的副本并将其修改为不继承,然后针对该修改后的版本进行编译。所以当运行时的真实版本碰巧继承时,序列化仍然会被撤销。Serializable
Serializable
好吧,另一种解决方案是永远不要在安全相关代码中使用lambda表达式/方法引用,至少在针对较新版本的接口进行编译时,如果目标接口继承了必须始终重新检查。Serializable
但我认为必须有更好的,最好是语言解决方案。