可以显式删除 lambda 的序列化支持

2024-05-11

As 已经知道 https://stackoverflow.com/a/22808112/2711488很容易添加序列化当目标接口尚未继承时支持 lambda 表达式Serializable, 就像(TargetInterface&Serializable)()->{/*code*/}.

我要求的是一种相反的方法,即在目标接口时显式删除序列化支持does继承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 is 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会撤销。


支持序列化的决定是在编译时做出的。它需要添加一个合成工厂方法CreationSite and a flag http://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html#FLAG_SERIALIZABLE传递给元工厂 http://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html#altMetafactory-java.lang.invoke.MethodHandles.Lookup-java.lang.String-java.lang.invoke.MethodType-java.lang.Object...-方法。如果没有该标志,即使接口碰巧继承,生成的 lambda 将不支持序列化Serializable。 lambda 类甚至会有一个writeObject方法就像在NotSupportingSerialization上面的例子。如果没有合成工厂方法,反序列化是不可能的。

我发现这导致了一个解决方案。您可以创建该接口的副本并将其修改为不继承Serializable,然后针对该修改后的版本进行编译。所以当运行时的真实版本恰好继承时Serializable,连载仍会被撤销。

那么,另一个解决方案是永远不要在安全相关代码中使用 lambda 表达式/方法引用,至少在目标接口继承的情况下是如此Serializable在针对较新版本的接口进行编译时,必须始终重新检查。

但我认为必须有更好的、最好是语言解决方案。


如何处理可串行化是 EG 面临的最大挑战之一;可以说,没有什么好的解决方案,只有各种缺点之间的权衡。有些团体坚持认为所有 lambda 都可以自动序列化(!);其他人坚持认为 lambda 永远不会被序列化(这有时似乎是一个很有吸引力的想法,但遗憾的是会严重违反用户的期望。)

您注意到:

那么,另一个解决方案是永远不要在安全相关代码中使用 lambda 表达式/方法引用,

事实上,序列化规范现在正是这么说的。

但是,这里有一个相当简单的技巧可以做你想做的事。假设您有一些需要可序列化实例的库:

public interface SomeLibType extends Runnable, Serializable { }

使用期望这种类型的方法:

public void gimmeLambda(SomeLibType r)

并且您想要将 lambda 传递给其中,但不让它们可序列化(并承担由此产生的后果。)因此,请自己编写这个辅助方法:

public static SomeLibType launder(Runnable r) {
    return new SomeLibType() {
        public void run() { r.run(); }
    }
}

现在您可以调用库方法:

gimmeLambda(launder(() -> myPrivateMethod()));

编译器会将您的 lambda 转换为不可序列化的 Runnable,并且清洗包装器将用满足类型系统的实例包装它。当您尝试序列化它时,将会失败,因为r不可序列化。更重要的是,您无法伪造对私有方法的访问,因为捕获类中所需的 $deserializeLambda$ 支持甚至不存在。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

可以显式删除 lambda 的序列化支持 的相关文章

  • 我们可以在java中放弃已经签名的jar吗?

    I ve a jar文件具有旧签名 并希望使用新签名重新签名 是否可以 如果可能的话 怎么做 如果签名不是您拥有的 您需要先解压 jar 像这样 假设是unix 否则翻译成dos jar xvf JarName jar rm rf META
  • 如何将 ArrayList 中的所有值相加或转换为 ArrayList

    我试图将 ArrayList 中的所有值相加 但没有任何方法可以让我得到总和 我必须找到从文本文件中提取的数字的平均值 public static void main String args throws IOException File
  • docker 中带有参数的 jar 文件

    Helo 我有一个 java jar 文件 当我从终端运行它时 它会接受一堆参数作为输入 我想制作一个 docker 映像并运行它 其中包含 jar 文件 我仍然可以在其中传递 jar 文件的参数 将 jar 文件设置为您的入口点 http
  • 外部化 Spring Security 配置?

    我有一个 Web 应用程序 可以使用 Spring Security 的几种不同配置 但是 这些差异配置都是在我的 applicationContext 配置文件中设置的 因此 为了在客户站点调整这些内容 必须在 WAR 文件内修改这些内容
  • 声纳要求将这一领域定为最终目标

    我的程序中有以下代码 在与 Maven 集成后 我正在运行 SonarQube 5 对其进行代码质量检查 我面临这个错误 将此 public static processStatus 字段设为最终字段 将此 public static pr
  • Poi:从 xlsm 打开 Excel 文件后将其保存为 xlsx

    我正在编写一个java程序 它打开一个用户定义的excel文件 用数据填充它 然后将其保存在用户指定的路径 文件名和扩展名下 即使输入文件是 xlsm 也应该可以声明输出保存为 xlsx 但实际上是不可能的 如果我尝试使用下面的代码 打开文
  • 最终类中的静态函数是否隐式最终?

    我的问题基本上与this https stackoverflow com q 8766476 3882565一 但这是否也适用于static功能 我想了解 编译器是否处理所有static函数在一个final类为final 是否添加final
  • 使用嵌入式 Jetty 7 发布 JAX-WS 端点

    有人可以帮忙吗 我想使用嵌入式 Jetty 7 作为端点 这是我尝试过的 public class MiniTestJetty WebService targetNamespace http public static class Calc
  • SQLite 64位整数在jooq中被识别为int

    我有一个与 jOOQ 一起使用的 SQLite 数据库 当我使用 jOOQ 的代码生成工具时 它会按预期构建所有表和记录类 然而 所有的 SQLiteINTEGER列变成java lang Integer生成的代码中的字段 问题是 SQLi
  • 为什么这段代码可以在 Java 7 中运行,而不能在 Java 8 中运行?

    我目前使用 IDE Eclipse 版本 Neon 2 Release 4 6 2 和版本 java Version 8 Update 131 在此代码中 IDE 给出错误 类型不匹配 无法从字节转换为整数 Integer i byte 1
  • 抛出 UnsupportedOperationException

    因此其中一种方法的描述如下 public BasicLinkedList addToFront T data 该操作无效 对于排序列表 将生成 UnsupportedOperationException 使用消息 排序列表的操作无效 我的代
  • Spark java:如何处理多部分/表单数据输入?

    我在用spark http sparkjava com 开发网络应用程序 当我想上传文件时出现问题 public final class SparkTesting public static void main final String a
  • 在Java中,为什么某些变量首先需要初始化,而其他变量只需要声明?

    我试图更深入地理解我是否遗漏了一些关于 Java 何时需要变量初始化与简单声明的理解 在以下代码中 不需要为变量 row 赋值即可编译和运行 但变量 column 则需要赋值 注意 该程序没有任何用处 它已被修剪为仅显示此问题所需的内容 以
  • 如何强制 Spark 执行代码?

    我如何强制 Spark 执行对 map 的调用 即使它认为由于其惰性求值而不需要执行它 我试过把cache 与地图调用 但这仍然没有解决问题 我的地图方法实际上将结果上传到 HDFS 所以 它并非无用 但 Spark 认为它是无用的 简短回
  • FocusEvent 没有获取 JFormattedTextField 的最后一个值,我如何获取它?

    我有两个JFormattedTextField我的物体JFrame目的 我想要通过这些值进行基本数学 加法 JFormattedTextField对象 我希望当焦点丢失第一个或第二个文本字段时发生这种情况 但当 focusLost 事件没有
  • 如何在 logback 中启动时滚动日志文件

    我想配置 logback 来执行以下操作 记录到文件 当文件达到 50MB 时滚动文件 仅保留 7 天的日志 启动时始终生成一个新文件 滚动 除了最后一项 启动卷 外 我一切都正常 有谁知道如何实现这一目标 这是配置
  • 将Json字符串映射到java中的map或hashmap字段

    假设我从服务器返回了以下 JSON 字符串 response imageInstances one id 1 url ONE two id 2 url TWO 杰克逊代码大厦 JsonProperty 我怎样才能得到HashMap对象出来了
  • 无法取消 GWT 中的重复计时器

    我正在尝试在 GWT 中安排一个重复计时器 它将每一毫秒运行一次 轮询某个事件 如果发现满意 则执行某些操作并取消计时器 我尝试这样做 final Timer t new Timer public void run if condition
  • 警告从 lambda 返回捕获的引用

    我尝试使用 lambda 有条件地将引用绑定到两个变量之一 int foo bar int choice gt int if true some condition return foo else return bar 这会在 clang
  • Encog:BasicNetwork:无需预先构建数据集的在线学习

    我正在尝试使用 encog 库作为强化学习问题的函数逼近器 更准确地说 我正在尝试启动并运行多层感知器 BasicNetwork 由于我的代理将根据我选择的任何 RL 算法以某种方式探索世界 因此我无法预先构建任何 BasicNeuralD

随机推荐