美国国家安全局(NSA)和网络安全与基础设施安全局(CISA)联合发布的报告《内存安全语言:减少现代软件开发中的漏洞》

报告PFD下载

这篇文章是由美国国家安全局(NSA)和网络安全与基础设施安全局(CISA)联合发布的报告,题为《内存安全语言:减少现代软件开发中的漏洞》,主要探讨了内存安全语言(Memory Safe Languages, MSLs)在提升软件安全性方面的重要性、优势、挑战及实施策略。以下是核心内容的总结:

报告PFD下载


1. 背景与重要性

  • 内存安全漏洞的严重性:如缓冲区溢出、释放后使用(use-after-free)等漏洞长期威胁软件安全,导致数据泄露、系统崩溃甚至国家安全风险。例如:
    • Heartbleed漏洞(2014年)影响了80万个网站,泄露大量敏感数据。
    • BadAlloc漏洞(2021年)波及工业控制系统和1.95亿辆汽车。
    • 微软的漏洞统计显示,2016年70%的漏洞与内存安全相关,2023年仍占50%。
  • MSLs的作用:通过语言内置机制(如边界检查、自动内存管理)从根源上预防漏洞,而非依赖开发者手动规避。

元素周期表

2. 内存安全语言(MSLs)的优势

  • 安全设计
    • 默认防止缓冲区溢出、数据竞争等漏洞。
    • 减少对静态分析工具和开发者经验的依赖。
  • 效率与可靠性
    • 降低崩溃率,提升系统稳定性(如Android采用Rust后,内存漏洞占比从76%降至24%)。
    • 长期节省成本(减少漏洞修复和运维支出)。
  • 开发效率
    • 编译时或运行时检查加速调试,让开发者更专注于功能创新。

3. 关键挑战

  • 迁移难度
    • 现有代码库(尤其是C/C++)的改造复杂,需权衡重构成本与收益。
    • 性能敏感场景(如加密、嵌入式系统)可能面临效率损失。
  • 生态与工具支持
    • 部分MSL(如Rust)的库和工具链尚不完善。
    • 开发者需学习新语言(如所有权模型、垃圾回收机制)。
  • 行业协作需求
    • 需学术界、政府和企业共同推动培训、标准制定和生态建设。

4. 实施建议

  • 渐进式迁移
    • 新项目优先使用MSL(如Rust、Go、Java)。
    • 旧系统分模块重构,高风险组件(如网络服务、加密模块)优先。
  • 技术选型
    • 根据场景选择语言(如高性能需求选Rust,快速开发选Python/Java)。
    • 结合静态分析、编译器选项等补充非MSL代码的安全性。
  • 组织支持
    • 制定内存安全路线图,投资开发者培训。
    • 政府通过项目资助(如DARPA的C转Rust工具)推动技术落地。

5. 未来方向

  • 研究重点
    • MSL在资源受限环境(如嵌入式系统)的适用性。
    • 自动化工具(如代码转译器)的可行性。
  • 政策与行业驱动
    • 客户需求(如采购要求MSL开发)倒逼厂商转型。
    • 开源社区(如OpenSSF)推动关键基础设施的MSL化。

结论

内存安全语言是减少软件漏洞的关键策略,但需平衡安全、性能与迁移成本。通过战略规划、跨领域协作和持续投入,MSL有望成为未来软件开发的基石,显著提升网络安全韧性。

共有 50 条讨论

  1. 本文件中存在两个主要问题:

    – 它将数据竞争保护与内存安全混为一谈,且这种混淆具有不一致性。Java 和 C# 被列为 MSL(内存安全语言),但实际上它们完全允许数据竞争发生。更根本的是,数据竞争本身并非攻击者利用的对象,除非这些数据竞争导致实际的内存损坏(如释放后使用、双重释放、越界访问、访问分配器元数据等)。因此,不将数据竞争的自由度作为内存安全性的要求更为准确,一方面是因为否则像Java和C#这样的语言尽管被列入列表,但实际上不符合定义,另一方面是因为在内存安全存在的情况下,数据竞争从安全角度来看并非大问题。

    – 该文档未提及Fil-C。如果提及时附带保留条款(如“新项目”、“性能问题”等)尚可理解,但完全不提及则显得不妥。

    1. > 从根本上说,数据竞争并非攻击者利用的对象,除非这些数据竞争导致实际的内存损坏(如释放后使用、双重释放、越界访问、访问分配器元数据等)。

      这绝对不正确。经典的数据竞争之一是当你以非原子方式执行以下操作时:

        new_total = account.balance;
        new_total -= purchase_price;
        account.balance = new_total;
      

      这是一种巨大的安全漏洞,因为它允许双重支付。Alice购买了价值$1000的商品和价值$1的商品,但系统并未从她的账户中扣除$1001,而是仅扣除$1,因为第二次交易的写入操作覆盖了第一次交易的余额减少操作。

      另一个常见的漏洞是符号链接。你检查符号链接的目标,然后访问它,但在检查和访问之间,链接发生了变化,现在你泄露了机密信息或覆盖了特权数据。

      数据竞争是完全独立于内存安全性的严重漏洞。

      1. 该文件系统示例是一个 TOCTOU 竞争,而不是数据竞争,因此它也可能在 Rust 或类似语言中发生。虽然 TOCTOU 竞争和数据竞争都是竞争条件的一种类型,但它们并不是同一回事。许多竞争条件是我们生活经验中常见的一部分——如果你曾经想过“哦, 我需要买更多牛奶”然后去商店,但与此同时你的室友、伴侣、同事或其他人在出去买牛奶,那么当你带着牛奶回来时,牛奶太多了,哎呀,这就是一个竞争条件,具体来说是TOCTOU竞争——我们检查了牛奶,然后购买了更多牛奶,与此同时其他人改变了牛奶的数量。

        数据竞争与现实世界体验不同。机器的实际工作方式对我们来说太过复杂,难以理解,因此在编写C等高级语言时,我们被提供了一个高度简化的“顺序一致性”幻觉——即事情以某种顺序发生。如果我们不遵循规则来维护这种幻觉,数据竞争就是现实“渗入”的结果。

      2. 我一生中修复了无数个紧急的安全漏洞,但你的数据竞争示例从未出现过,甚至一次都没有。

        逻辑错误(如确保在setuid进程中强制执行策略)比数据竞争更常见,且无法通过任何语言或类型系统预防。

        1. 这取决于你所在的领域。如果你只是在修补 CVE,它出现的频率会更低,因为如果你实现了那种数据竞争,其中第一行和最后一行是独立的 SQL 查询,你不会因为数据库软件而获得 CVE。然后它只有在有人开始利用它,或者竞争如此严重以至于在正常使用中发生,然后在季度末会计开始询问为什么数字对不上时才会被发现。

          1. 没有内存安全的语言能保护你免于写错SQL查询。

            数据库中的竞争条件并非编程语言意义上的“数据竞争”,除非我们在讨论。应使用的查询语言

            1. > 没有内存安全的语言能保护你免于写错SQL查询。

              这就是重点。

              > 数据库中的竞争条件并非编程语言意义上的“数据竞争”

              只有在足够庞大的官僚体系中,你才能启动“别人的问题”模式,并将责任推给数据库管理员。但这并不能帮你找回丢失的资金。

              1. 整个讨论都是关于编程语言的内存安全,而不是任何语言都无法预防的安全漏洞

                1. 这再次是重点。人们有了新工具,但并非所有问题都能用它解决。

                  许多编程语言都提供了处理数据竞争的工具。SQL 支持事务,多种语言提供了比较并交换(compare-and-swap)原语等。

    2. > Java 和 C# 被列为多语言支持(MSL),但它们完全允许数据竞争。

      在 Java 中,数据竞争意味着顺序一致性的丢失。人类通常无法理解缺乏顺序一致性的程序,因此典型的 Java 团队可能无法调试该程序,但程序仍然具有明确的行为定义——而且很可能你根本不想调试这种奇怪的非顺序一致性行为,你只是希望他们修复数据竞争。

      在 C# 中,对于所有位模式都有效的简单对象,数据竞争并不危险。如果你对整数 k 进行竞争,那么 k 就会被破坏,不要过多思考 k 的值,它确实有一些值,但试图推断其值对你来说不会有好结果。对于哈希表等复杂对象,这是未定义行为。

      而在 C 或 C++ 中,所有数据竞争都是立即的未定义行为,你输了,游戏结束。

      1. 唉,我想我写这段话时脑子短路了。对此表示歉意。上述关于 C# 的评论(关于简单对象与复杂对象的区别)实际上指的是 Go,一种截然不同的垃圾回收语言。对于 C#,这种情况的具体文档并不完善,因此最好不要依赖无法获得明确保证的内容,但从原则上讲,它与 Java 类似。

      2. 在 Fil-C 中,数据竞争的行为与 Java 类似。

        YOLO-C出现不良数据竞争行为的原因是:(1)数据竞争可能导致内存安全违规,(2)编译器被允许采取比严格必要更宽松的优化策略。Fil-C通过使语言本身具备内存安全特性来解决(1)问题,通过在编译器中采用不同策略来解决(2)问题。

    3. 一个不包含数据竞争自由的内存安全定义可能更精确,但可能不够完整。

      在垃圾回收语言中,数据竞争确实难以转化为漏洞。

      问题是,C 和 C++ 中的数据竞争确实会与其他内存安全漏洞结合形成漏洞。

      从第一性原理出发的定义仍缺失,但假设其形式为“所有内存访问均不受未定义行为影响”。那么,指针是否在范围内,或者是否没有线程同时改变该位置,似乎是相当相似的限制。

      Rust 确实提供了控制并发性的方法,例如通过 &mut 引用来表达独占访问。因此,也有先例表明,相同的机制可用于确保引用的有效性(非悬空)以及没有并发访问。

      1. > 问题在于,C 和 C++ 中的数据竞争确实会与其他内存安全漏洞结合形成利用。

        因为 C 和 C++ 并非内存安全。

        > 从第一性原理出发的定义仍然缺失,但假设它采取“所有内存访问均不受未定义行为影响”的形式。那么,指针是否在边界内,或者没有线程同时修改该位置,似乎是相当类似的约束。

        我认为从安全专家所说的“内存安全”语言开始倒推是有用的,因为他们真正想表达的是,“我无法使用我熟悉的攻击方法来攻击用这些语言编写的程序”。

        基于此,仅说“无未定义行为”是不够的,仅关注内存访问也不够。

        WebAssembly没有未定义行为,但指针被定义为整数(即C程序员梦想中的无未定义行为的结构化汇编语义)。因此,攻击者可以在 WebAssembly 内存中进行越界访问(OOB)和不安全访问(UAF)数据攻击。攻击者唯一无法做到的是控制指令指针或逃逸 WebAssembly 内存(除非 WebAssembly 嵌入器采用弱沙箱策略,此时攻击者可同时实现这两点)。总体而言,我认为 WebAssembly 意义上的内存安全并非真正的内存安全。它过于易受攻击。

        要像安全专家所说的“MSLs”那样实现内存安全,你还需要考虑函数调用等因素。根据语言的不同,你可能还需要考虑其他因素。

        我认为安全人员所说的“内存安全”是以下几点的结合:

        1) 不存在未定义行为(UB)。每个结果都是明确定义的。

        2) 指针(或你语言中类似指针的构造)只能用于访问其原始分配的内存(即指针携带能力)。

        而且,这些要素必须“强力”结合;即语言中不存在任何操作可以绕过指针能力的强制执行。

        Java和C语言都具备(1)和(2)的强力结合。

        但简而言之,确实存在这样一个问题:从第一性原理出发对内存安全性的定义尚不明确,因为该领域尚未就该定义的具体内容达成共识。这一问题具有争议性,因为你可以认为,根据我的定义,Rust 并非内存安全的(在 Rust 中确实可能出现未定义行为)。而且,你可以认为 Wasm 满足(2)因为“分配”就是“整个内存”。我甚至不确定我喜欢自己的定义。在某个时候,你必须对“分配”是什么说得更清楚。

    4. 他们不会提到一个在 GitHub 上有 900 个星标的单人实验项目。

      这本应是一种可全国推广的实用策略,而非沦为另一个https://xkcd.com/2347

      1. > 他们不会提及一个在GitHub上拥有900颗星的个人实验项目。

        这似乎不是选择技术的明智方式。

        他们确实提到了像TRACTOR这样的项目。Fil-C在TRACTOR项目框架下的任何项目中都遥遥领先。

        > 这本应是一个可全国推广的实用策略,而非沦为另一个https://xkcd.com/2347

        解决办法是为必要的事物提供资金,而不是抱怨必要的事物没有资金。国防部可以做到这一点

        1. > 选择技术的方式似乎不太妥当。

          这是为政府选择技术的一种非常明智的方式。

          拥有一个酷炫的概念验证,其关键人物依赖度为1,与拥有一个无数政府机构可以依赖的解决方案(用于数百万美元的数十年软件项目)是完全不同的两件事。

          他们不能仅仅因为你个人维护“菲尔的不可思议垃圾收集器”而依赖它,直到政府项目的生命周期结束。也许你认为他们可以,但要向政府提供这样的保证需要付出更多努力。

          他们将TRACTOR列在已资助的项目中(关键是,它尚未被列为推荐解决方案)。申请Fil-C的资金,如果获得批准,它也可能会被列在那里。

          TRACTOR 方法对实验性项目也有更高的容忍度,因为它是将 C 转换为 Rust 的一次性转换。它只需要工作一次,而不需要连续工作几十年。Rust-lang 组织旨在提供认真的长期支持,早已不再依赖单个开发人员。

          1. > 这是政府选择技术的一种非常明智的方式。

            不,这不是,因为政府有足够的资源来组建团队重新创建一个类似 Fil-C 的项目,甚至只需为 Fil-C 增加人力。

            仅需一名开发者在业余时间花费 1.5 年即可实现 C 语言的内存安全,这表明原帖的论点是错误的。问题不在于人们不使用内存安全语言,而在于没有人资助仅为实现 C 语言的内存安全。

            1. 你创建了一种比C语言慢20-50%且带有垃圾回收器的语言。

              这并非人们使用C语言的初衷。你将其包装成“内存安全的C语言”,但实际上它拥有更精细的ASAN机制。这虽有价值,但并未彻底颠覆现有叙事。

              对于运行无法修复的遗留C代码,已经存在开销更低的解决方案。它们不够精确,但这要么对安全性无关紧要(例如存在正确的沙箱边界),要么性能至关重要,人们愿意接受不完整的强化措施,尽管存在风险。

              对于新开发,当较慢的GC语言适用时,已有大量更方便且不易崩溃的语言可供选择。

              已经存在采用类似指针标记方法的 CHERI 技术,但他们选择在硬件层面实现,因为他们清楚软件模拟会让解决方案失去吸引力。

              1. > 你创建了一种比 C 语言慢 20-50% 且带有垃圾回收器的语言。这并非人们使用 C 语言的初衷。

                谁说的?

                大多数用C语言编写的软件并不注重性能。我的shell即使慢4倍我也不在乎。

                我使用的绝大多数图形界面软件也是如此,包括浏览器。

                > 你有一个更精细的ASAN。

                Fil-C和ASAN的区别在于Fil-C是内存安全的,而ASAN不是。

                这与“精细”无关。

                > 这并没有推翻整个叙述。

                叙述是C不是内存安全的语言。这个叙述是错误的。

                如果叙述是“C只有在愿意付出性能代价时才是内存安全的”,那随它去吧。但这不是人们所说的

                > 对于运行无法修复的遗留 C 代码,已经存在开销更低的解决方案。它们不够精确,但这要么对安全性无关紧要(例如存在正确的沙箱边界),要么性能至关重要,人们愿意接受不完整的强化措施,尽管存在风险。

                不,没有。Fil-C 是 C 代码中唯一的内存安全解决方案。

                Hwasan、mte 等都不是内存安全的。Asan 也不是内存安全的(而且可能也不便宜)。不知道你还在考虑什么。

                > 已经有了 CHERI,它采用了类似的指针标记方法

                Cheri 和 Fil-C 均不使用指针标记。两者均采用指针能力机制。Fil-C 的能力机制更安全(实际上可防止内存释放后使用)。

                Fil-C 比 Cheri 更快,因为我可以在高性能通用硬件上运行 Fil-C。在我 x86 系统中,Fil-C 的运行速度比最快的 Cheri 机器快几个数量级

        2. 说得好,兄弟! 🙂

          嗯,我理解的情况是,目前有许多供应商/提供商/发行版/仓库可以分发您的内存安全构建版本,但目前仍在分发不安全的构建版本?

          我好奇像 Tor 项目 [1] 这样的组织是否会更愿意“官方”分发 Fil-C 构建版本,毕竟安全性是他们产品的核心。(我指的是他们的“洋葱路由器”[2],而非(必然)整个浏览器。)

          我设想一旦某些组织开始正式发布Fil-C构建版本,采用率可能会加速。

          另外,你是否与Ladybird浏览器团队沟通过?他们似乎对Fil-C表现出兴趣。

          [1] https://www.torproject.org/

          [2] https://gitlab.torproject.org/tpo/core/tor

            1. 有道理。但也许那篇帖子已经四年了,这反而强化了Fil-C的价值主张。尽管人们可能希望摆脱他们的C代码库,但及时完成这一转变所需的资源往往并不容易获得。

    5. > 因为在内存安全环境下,数据竞争从安全角度来看并非大问题。

      需注意的是,数据竞争可能导致本应内存安全的程序实际上不再安全。例如Go语言

        1. 据我所知,Go中的切片并非原子更新,因此其组件可能发生数据竞争,导致潜在的未定义行为(UB)。

          值得一提的是,根据我在这里阅读的内容(例如[0]),这种情况尚未在非演示环境中被利用,但谁知道第一个此类漏洞何时会出现。

          [0]: https://news.ycombinator.com/item?id=42043939

          1. 我认为这只是意味着Go并非完全内存安全。

            或者,这意味着“Go仅在没有数据竞争的情况下才是内存安全的”。

            我的观点是,将内存安全与数据竞争处理分开讨论是奇怪的。这会导致 OP 中提到的困惑:不清楚他们是在说内存安全包含数据竞争自由,即即使存在竞争(如 Java 或 Fil-C),内存也是安全的,还是说内存安全包含数据竞争自由,即 Rust 的类型系统使用相同的基本技术处理内存安全和数据竞争。

            1. 没错,我明白你的意思,认为你有道理。只是想进一步探讨Go语言与数据竞争的(感知到的?)问题。

      1. > 内存安全就像软件行业的全球变暖。

        这是一种隐蔽的长期问题,挑战着我们那些奖励短期思维的系统,如果我们不采取行动,它将慢慢摧毁我们。

        我完全同意。

      2. jart将Carmack和Musk放在同一水平上有点可悲且发人深省,难怪会遭到反对。

  2. 一个缺失的重要环节是将不安全语言中的依赖项替换为安全语言编写的依赖项。

    通常只有少数几个地方会处理用户控制的数据,因此将生成PDF文件缩略图等功能的依赖项切换为安全依赖项是有效的。

    编辑:还有一点是编译不安全代码为WebAssembly或其他形式的沙箱化,这一点未被提及。

    1. 逐步替换关键依赖项也为大型旧代码库提供了实用的迁移路径,因为完全重写在经济上是不切实际的。

  3. 啊,看来政府中的 Rust 间谍在 DOGE 的大清洗中幸存了下来,我一直在想他到底发生了什么 😉

    1. 这并不是说有什么特殊利益集团在推动这项特定技术,它只是目前最优的解决方案。如果你想解决这个问题,这就是解决方法。

    2. 据我所知,亚历克斯·盖诺(Alex Gaynor)曾在联邦贸易委员会(FTC)大力倡导内存安全与Rust语言。但他在特朗普政府上台前就离开了(或被解雇了?)。

      我不知道现在是谁在继承他的事业。考虑到 OP 的文件中 16 次提及 Rust,而 Java 只提及 4 次,可能有人在继承他的事业。但我们必须看看 CISA 在因任务范围扩大而资金被削减后会发生什么变化。

  4. > 2021年发现的58个在野零日漏洞中,67%是内存安全漏洞。

    这个数字与二十年前大致相当。

    最大的区别在于,二十年前的对手是脚本小子,而现在是多个国家资助的成熟团队。

    1. 值得一提的是,并非所有内存安全漏洞都可被利用或具备理论上的利用途径。如今许多此类漏洞与理论上的加密漏洞类似,即“某一天”可能被开发出利用能力。不仅是利用缓解措施,安全开发实践也使得漏洞难以被利用,甚至理论上的利用也变得不可行。

    1. 可能是因为它使用了计数字节字符串并进行了边界检查?

      1. 在Delphi中,什么阻止了你进行UAF或OOB数组访问?

        1. Delphi数组进行了边界检查。UAF可以通过使用引用计数字符串等机制缓解。虽然不是完全安全,但比C和C++安全得多。

  5. 因此,Perl 及其受污染数据跟踪机制不被视为安全。这很奇怪。

    1. 它明确这么说了吗?我在 PDF 中找不到“Perl”。那里只有 MSL 的示例。Perl 没有出现在示例列表中并不意味着它不是 MSL。这是一个非穷举列表。

  6. > Ada、C#、Delphi/Object Pascal、Go、Java、Python、Ruby、Rust 和 Swift 等 MSL 提供针对内存安全问题的内置保护

    它们提供默认保护,但在大多数语言中很容易被覆盖。其中一些语言要求您使用这些覆盖来实现常见的数据结构。

    > MSL 可以防止整个类别的漏洞,例如缓冲区溢出、悬空指针和许多其他常见弱点枚举 (CWE) 漏洞。

    如果以某种方式使用的话。

    > Android 团队做出了一项战略决策,即在所有新开发中优先考虑 MSL,特别是 Rust 和 Java。

    他们只做了这些吗?

    > 初始阶段投资于培训、工具和重构。此类投资通常可通过减少停机时间、降低漏洞风险及提升开发效率带来的长期节省来抵消。

    这是一个极具争议的普遍性主张。

  7. 降低现代软件开发中的安全事件发生率

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

你也许感兴趣的: