View in English

  • Apple 开发者
    • 入门汇总

    探索“入门汇总”

    • 概览
    • 学习
    • Apple Developer Program

    及时了解最新动态

    • 最新动态
    • 开发者你好
    • 平台

    探索“平台”

    • Apple 平台
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    • App Store

    精选

    • 设计
    • 分发
    • 游戏
    • 配件
    • 网页
    • Home
    • CarPlay 车载
    • 技术

    探索“技术”

    • 概览
    • Xcode
    • Swift
    • SwiftUI

    精选

    • 辅助功能
    • AI 与机器学习
    • App Intents
    • Apple 智能
    • 游戏
    • 安全性
    • Xcode Cloud
    • 社区

    探索“社区”

    • 概览
    • “与 Apple 会面交流”活动
    • 社区活动
    • 开发者论坛
    • 开源

    精选

    • WWDC
    • Swift Student Challenge
    • 开发者故事
    • App Store 大奖
    • Apple 设计大奖
    • Apple Developer Centers
    • 文档

    探索“文档”

    • 文档库
    • 技术概述
    • 示例代码
    • 《人机界面指南》
    • 视频

    发布说明

    • 精选更新
    • iPadOS
    • iPadOS
    • macOS
    • watchOS
    • visionos
    • Apple tvOS
    • Xcode
    • 下载

    探索“下载”

    • 所有下载
    • 操作系统
    • 应用程序
    • 设计资源

    精选

    • Xcode
    • TestFlight
    • 字体
    • SF Symbols
    • Icon Composer
    • 支持

    探索“支持”

    • 概览
    • 帮助指南
    • 开发者论坛
    • “反馈助理”
    • 联系我们

    精选

    • 《开发者账户帮助》
    • 《App 审核指南》
    • 《App Store Connect 帮助》
    • 即将实行的要求
    • 协议和准则
    • 系统状态
  • 快速链接

    • 活动
    • 新闻
    • 论坛
    • 示例代码
    • 视频
 

视频

打开菜单 关闭菜单
  • 专题
  • 所有视频
  • 关于
  • 简介
  • 转写文稿
  • 加固你的 App:关键策略助你增强安全性

    欢迎参加在库比提诺 Apple Developer Center 举办的全天活动,一起学习如何增强 App 的安全性并保护用户数据。无论你是想加固现有 App,还是正着手启动新项目,都可以直接向 Apple 工程师学习如何从底层开始增强 App 安全性。

深入了解现代 App 面临的安全挑战,探索一整套旨在帮助保护用户数据的工具和技术。学习如何利用 Memory Integrity Enforcement、指针认证和内存边界安全等强大功能保护你的 C 和 C++ 代码库。了解如何将 Swift 应用于安全性要求最高的组件,充分利用其内在的安全性和现代化的抽象机制,编写安全且高性能的代码。此外,还能就如何为新建项目和现有项目制定清晰的安全路线图 (从宏观策略到落地实践) 获得指导。

    资源

      • 高清视频
      • 标清视频
  • 搜索此视频…

    您好,欢迎来到位于 库比蒂诺的 Apple 开发者中心。 我是库尔特,一名技术布道者。 加入全球开发者关系团队。

    今天,我和我的同事们很高兴能分享这些技术。 通过减少内存安全漏洞来提高应用程序的安全性。 有很多内容要讲,但首先, 我想和大家分享一下我们的场地情况。 Apple 开发者中心。

    开发者中心是Apple Park的一部分。 这里是举办今天活动的绝佳场所, 而且还有交流区域。 以及协作。

    这间房间就是大苏尔。 这是一个很棒的空间,布置得也很好。 我太喜欢这个屏幕了。 它旨在支持各种各样的活动, 包括像这样的现场演示、录音室录音等。 还有现场直播,我们现在也 在进行现场直播。你好,世界。

    这里还有实验室和简报室。 以及可举办各种规模活动的会议室。

    这是全球四个开发者中心之一, Apple 会举办各种活动,包括研讨会、 工作坊等,邀请设计师和开发者参加。 这里有人去过开发者中心吗? 太好了。无论您是新来的还是老朋友, 我们都非常欢迎您。

    我们的团队非常乐意与开发者交流。 我们的目标是帮助您为苹果 平台开发出最好的应用程序。 自 Web 25 时代以来,我们已在全球范围 内举办了300多项活动,包括实验室、 研讨会和演讲。 下周,我的同事们 我正在 New York 市主持 一个关于新设计的研讨会。 参加研讨会的学员将有机会 亲身实践液态玻璃的操作。 随着他们更新代码和设计。 如需了解您附近或线上即将举行的活动, 请访问开发者查看活动详情。 在开始今天的议程之前, 以下是一些建议, 帮助房间里的人们全天保持联系。 使用AppleWi-Fi网络。 你可以访问,无需密码。 如果您需要充电,每个座位上都提供电源插座。 它会在扶手的前面。

    今天收看节目的各位, 这些演讲将被录制下来, 并在活动结束后在线提供观看。 无需录制视频或进行直播。

    不过,如果您想拍照,我们非常欢迎。

    活动结束后,我们还会发送后续信息及链接。 请查阅演示文稿中提到的所有资源, 以免错过任何重要信息。

    预赛结束后,我很兴奋 能和大家分享更多关于今天活动的信息。

    今天的主题是内存安全漏洞及其解决方法。 人们的电子设备已成为他们 生活中不可或缺的一部分。 他们的设备中包含了大量个人数据和隐私信息。

    因此,它们成为攻击者的主要目标。 获取此信息。

    今天的演讲将涵盖以下内容: 通过查找并修复内存漏洞来保护 您的应用程序免受攻击者的侵害, 应用通用保护措施,甚至编写内存安全代码。

    对于那些在线的朋友们, 你可以使用 Slideo 向 Apple 工程师提问。 我们有专门的团队负责解答问题, 他们非常乐意为您提供支持。 请大家集中注意力讨论今天的主题。 团队期待回答有关这些示例的问题。 在Slido的演示文稿 和您的一般应用程序安全问题中, 您可以浏览其他人提出的问题, 并为您喜欢的问题点赞。 并阅读团队的回答。 这是一个很棒的资源, 它是由你们的问题驱动的,所以请尽管提问。

    在正式开始技术讲解之前, 我谨代表 Big Sur 舞台, 欢迎一位特别嘉宾。 请和我一起欢迎苹果公司平台安全主管, 皮埃尔-奥利维耶 · 马特尔。 皮埃尔。

    谢谢你,库尔特。

    大家早上好。 我的名字是皮埃尔·奥利维耶·马特尔。 我是安全工程部门的平台安全负责人 以及建筑设计团队。 首先,我要感谢大家今天到场或在线参与。 很高兴见到这么多朋友。 现在,我们都知道我们的设备 对人们来说变得多么重要。 谁在使用这些设备, 以及如何保护这些设备及其存储的数据, 无论是沟通交流,还是亲密时刻, 健康或财务信息, 这就是我们构建隐私保护的原因 从一开始就将安全性融入到我们的产品中。 Apple 通过硬件的深度 集成实现了最佳安全性。 软件和服务。 过去15年里, 我们在产品中内置了许多 行业领先的安全防御措施。 这些包括安全启动、 它从第一条指令开始就保证了软件的完整性。 以及诸如 Touch ID、 Face ID 等身份验证技术以及光学 ID 到末尾终止 为 iMessage 信息或 iCloud 钥匙串提供支持的加密协议。

    我们的北极星始终未变。 我们认为安全性应该默认内置,并且易于使用。 这项工作的成果是,安全研究人员 同意 iPhone 是最安全 可靠的消费级移动设备。

    现在,许多犯罪行为背后的共同点是: 包括那些针对业内其他平台的平台。 它们利用的是内存安全漏洞。 例如,在 Apple, 我们一直在努力提高内存安全性。 通过使用像 Swift 这样的内存安全语言进行开发。 并大规模部署全新的缓解措施, 今天你们就会听到其中一些故事。 最近,我们推出了内存完整性强制执行。 搭配我们最新的iPhone和Mac机型。 现在, Emmi 是一项重大 突破,它结合了独特的 Apple 芯片硬件的优势与我们 先进的操作系统安全性相结合 为行业提供首创, 我们所有设备始终提供内存 安全保护,且不会影响性能。 设备性能。

    但本章并未随着我们向用户交付安全设备而结束, 因为 Apple 设备的安全性也 依赖于 root 权限。 取决于周围生态系统的强度。 因此,我们对安全生态 系统的关注贯穿了第三个阶段。 我们与您(我们的开发者 社区)合作的政党软件生态系统 提供安全的应用程序。 这需要应用商店和平台安全功能协同运作。 这就是为什么这项合作如此重要的原因。 攻击者不会区分平台以及 在其上运行的应用程序。 他们会寻找最薄弱的环节。 因此,随着我们系统性地提高平台安全标准, 我们需要在各个方面推广它。 事实上,我们必须在各个方面都提出这个问题。 这意味着要共同努力,使这些技术得到广泛应用。 尽可能地。因此,我们很高兴 与大家分享我们一直在做的工作。 并使您能够在您的应用程序中采用。 我们采用了许多相同的先进 防御措施来帮助保护所有用户。 所以,我再次感谢各位今天能和我们在一起。 欢迎。那么,现在我把话筒 交还给库尔特,让他开始吧。

    谢谢。

    谢谢你,皮埃尔。奥利维尔。

    以下是今天剩余时间的议程概述。 我们的技术课程从一本实用指南开始。 采用与 Apple 保护其应用 程序相同的内存安全策略。 包括何时以及如何 为了应用每一层防御措施, 从像类型化分配器这样的快速胜利开始, 更长期的投资,例如快速重写 以及增强的安全扩展。

    然后我们稍作休息,伸展一下身体。 或许还可以喝杯含咖啡因的饮料。 接下来是对记忆、 完整性、执行力等方面的深入探讨。 以及指针认证。 两项强大的硬件软件安全功能涵盖 它们如何防止内存损坏攻击? 以及如何确保你的代码不会破坏他们的保护措施。 我们将休息80分钟吃午饭。 然后返回这里进行另外三场演讲, 首先提供一份在 C 和 C+ + 中采用 边界安全的实用指南, 涵盖 C 语言中编译器强制执行的边界注解 并加强了 C++ 标准库中的检查。

    短暂休息后,我们就进入了最后阶段。 我们将在此了解 Swift 内置的内存 安全保证与 C 语言相比有何不同。 以及 C+ +, 如何使用新的低开销 span 类型来编写性能关键型代码, 以及如何逐步进行 逐步迁移不安全的代码库 使用严格的内存安全模式和 C 互 操作特性来使用 Swift。

    然后,在当天的最后一节课上, 学习如何使用地址消毒器 Xcode 中的线程清理器 可以主动查找缓冲区溢出。 在代码库中免费使用,排除错误和数据竞争。 包括消毒器如何补充内存完整性保护。

    最后,我们为在座的各位结束今天的议程。 在开发者中心大厅集合。 参加一个可以和Apple工程师交流的聚会 以及其他开发者同行。

    现在,让我们正式开始我们的技术讲解。 我谨代表Big Sur欢迎马克的到来。 标记。

    谢谢。

    早上好。我是苹果公司安全 工程部的马克 · 米切尔。 以及建筑团队,还有我的同事 Devin。 今天我将介绍一个保护框架,你可以用它来保护 保护您的应用程序免受内存安全漏洞的侵害。

    德文精心设计的策略层次分明、环环相扣。 今天我要谈的是 iPhone 为何能赢得如此盛名。 作为目前最安全的消费电子产品。

    在今天的课程中,我们将讨论这些功能是什么。 解释它们的用途,并举例说明 Apple 如何使用它们。 为了保护我们的应用程序、操作系统和客户。

    借助 Xcode, 您现在可以使用 与 Apple 公司相同的技术。 保护您的应用程序用户。

    应用程序已经渗透到我们生活的方方面面。 它们是每个人都信赖的、 用来处理个人信息的重要工具。 位置、浏览记录、历史记录、照片、消息、 财务信息,以及同时进行的这些操作, 这些应用程序和用户都连接到互联网。 因此,它们中的安全漏洞 可能会使用户容易受到攻击。 这些攻击造成的损失包括欺诈。 从身份盗窃到敲诈勒索,甚至在极少数情况下, 现实世界中的威胁。

    人们希望自己的数据能够得到隐私和安全保障。 如果这一承诺没有得到履行, 那就是对信任的背叛。

    安全是保障隐私的技术基础。

    我首先概述一下内存安全的含义。 以及它可能导致的各种类型的漏洞。

    接下来,我将对一些安全问题进行概述。 可用于保护应用程序的工程策略 并描述我们如何在自己的项目中成功运用它们。 最后,德文将向你展示如何将这些策略付诸实践。 使用Xcode的增强型安全功能。

    那么,我们所说的内存安全是什么意思呢?

    内存安全漏洞是最常见的漏洞类型之一。 在软件领域,攻击者利用内存损坏来改变软件。 或破坏程序行为。

    在 Apple, 安全工程师从预期 用途的角度考虑内存安全问题。 以及程序中的非预期行为。

    在正常使用情况下, 该程序能够按照开发者的预期运行。 并且只执行合理的操作。 所以你可以把程序的运行流程想象成一个迷宫。 迷宫中的路径就是密码。 开发者打算将其用于某些操作, 其他路由代表未使用或无法访问的代码路径。

    而那些无法到达的路径可能是 在你未使用的框架中,会执行诸如转换之类的操作 通过摄像头或发送电子邮件。

    攻击者可以利用这些内存安全漏洞来尝试攻击。 诱使程序进入意想不到的状态,从而让他们 执行的操作与开发者的预期完全不同。

    在这个例子中,内存安全漏洞 可能存在于内存安全漏洞的任何位置。 这条预定的路径会引出解决迷宫的全新方法。 或者通过创建或采用新路径来运行非预期代码。 这可能意味着调用该代码来强制 应用程序打开摄像头。 或者发送电子邮件。

    内存安全是安全的基础,没有内存安全, 我们无法保证更高级别的安全性能。 例如,你的密码哈希方案有多强都无关紧要。 如果攻击者能够 轻易获得访问权限利用内存安全漏洞攻击系统。

    举个例子,这是一个简单的登录功能。 它会从用户输入的文本字段中复制密码。 检查是否为调试版本,如果是则跳过身份验证。 这样一来,开发者或许可以 在办公桌前更快地进行测试。 并调用一个函数将密码副本与……进行比较 一些硬编码的密钥,并返回登录结果。 你们当中特别细心的人可能已经注意到了。 那个巨大的红色 X。显然存在内存安全漏洞。 这段代码里有错误,你真的不应该复制它。 这里,开发者在栈上 分配了 32 个字符的空间。 保存一份密码副本 但是复制 API 并不知道有多少可用空间。 它会复制该文本字段中所有字符。 数据被写入指定的缓冲区,导致栈缓冲区溢出。

    在内存的后台,大致就是这样发生的。 本地密码存储位于内存使用之前。 用于保存调试登录标志。

    如果密码长度为 18 个字符,那当然没问题。

    但如果攻击者发送超过 32 个字符呢?

    嗯,在这种情况下, 因为这个程序是用一种语言编写的。 这样做不安全,它会开始覆盖相邻的内存。 这里是用来存储调试登录标志的内存。

    结果是,无论开发者的初衷如何, 最终都无法达到预期效果。 或者当程序执行到此 if 语句时, 将该值传递给此函数。 如果该值已被攻击者覆盖或更改, 他们可以强制跳过身份验证并返回 true。

    但实际上,这段代码中存在一个更严重的问题。 覆盖调试标志可能使攻击 者无需身份验证即可登录。 但在内存中,调试登录标志之后是地址 程序将调用以进行比较的函数指针。

    如果攻击者提供更长的密码, 它们可能会溢出本地密码缓冲区, 导致调试登录标志失效。 并将该函数指针更改为他们想要的任何内容。 这意味着当程序尝试调用其比较函数时, 实际上,它会进行比较。 它实际上会调用攻击者选择的任何函数。 现在攻击者完全控制了这个过程。 访问这些其他代码路径,并有可能窃取用户数据。

    所以,这个例子就是缓冲区溢出。 这违反了内存安全性的其中一项属性。 但实际上有五个轴被束缚住了。 安全措施确保所有通道畅通无阻。 内存操作发生在内存分配的范围内。 终身安全机制确保内存仅在有效期内使用。 而且没有被用于其他用途。

    类型安全确保程序员不会意外访问 以与预期不同的方式存储数据。

    保证初始化确保内存在使用前已完成初始化。

    最后,线材安全机制确保 不同种类的线不会相互干扰。 在彼此的记忆中。

    这就是一次攻击的典型表现。 一般来说,攻击者在攻击 任何应用程序时都会寻找…… 不仅能够访问该应用程序中的数据, 而且还会泄露数据。 并访问该应用程序中的数据, 但我们将以此作为第一步。 这是引发一系列漏洞的第一步, 这些漏洞会继续攻击其他系统组件。 甚至可以提升到内核权限 为了破坏平台安全模型, 因为他们不仅想访问整个系统中的文件, 但也包括位置数据、 照片、联系人、麦克风等其他资产。

    因此,应用程序往往是更大规模攻击的入口。

    与此同时,现代应用程序提供 功能如此强大,以至于其中 许多功能都获得了访问权限。 对于大多数正常使用中的这些资产而言。 随着操作系统安全态势的不断发展, 我们有理由预期,未来…… 攻击者可能仅仅满足于利用你的应用程序漏洞。 并收集他们从该环境中获取的数据, 无需继续访问平台的其他部分。 所以,我们现在都这样做真的非常重要。 在推进的同时保障安全。

    现在我将详细讲解Apple正在使用的策略。 为了保护应用程序和服务(例如消息), Safari 和邮件内存安全漏洞。

    在任何足够复杂的代码库中。 人们普遍认为,漏洞总是存在的。 太过分了。

    因此,在Apple,我们依赖于多层安全工程。 两者相辅相成。

    将会有更深入的探索。 融入到我今天将要介绍的许多技术中, 但我来这里是为了介绍这些概念, 以及 Apple 是如何考虑应用这些概念的。 以及一些评估其功效的方法 以及在应用程序中使用它们所需的工作量。 我将谈谈五种策略。

    我将介绍如何消除内存安全漏洞。 在你的代码中使用内存安全的语言, 例如 Swift。

    如何通过减少可用资源来使漏洞无法访问 攻击面防止

    漏洞被利用采取缓解措施, 以减少漏洞利用造成的损害 (如果发生漏洞利用)。

    最后,介绍一些查找技巧 并消除代码中的漏洞。

    最全面的方法 防御内存安全漏洞 是使用一种几乎不可能创建 它们的语言首先。

    Apple 对 Swift 进行了大量投资, 将其视为一种安全且高性能的语言。 而且它开箱即用,具备内存安全功能。 它不仅为你的许多应用程序提供支持, 但也包括我们操作系统的部分组件。 这其中也包括越来越多对安全至关重要的代码库。 例如WebKit、复杂的解析、 甚至像安全隔区这样的嵌入式环境也会处理固件。

    当然,许多应用程序都有庞大的现有代码库。 在 C 语言家族中。 它们不具备内存安全特性。

    增强型安全功能确实提供了 诸如 F 频段安全之类的工具。 及其 C 语言注释 以及 C++ 标准库的加固 帮助您降低乐队安全事故的风险。 但请把它们看作是通往记忆安全的垫脚石。 它们并非最终目标, 因为它们没有解决其他四个方面的问题。

    从这五个方面比较基于 C 语言的语言和 Swift, 基于 C 语言的编程语言不提供这些保护措施。 而 Swift 通过使用边界 检查数组来提供边界安全。 以及其他 Swift 标准库抽象。 它通过自动计数和所有权记录提供终身安全保障。 它通过要求在运行时检查 类型转换来保证类型安全。

    Swift 通过要求你初始 化变量来保证初始化的有效性。 在使用之前。 而且,凭借 Swift 并发性, 它通过防止数据竞争来提供线程安全。

    以上就是关于如何快速浏览的简要介绍。 使内存安全漏洞成为不可能。 稍后请查看“在 Swift 中 编写安全敏感代码”课程。 了解更多信息。

    Apple大量采用的第二种策略 在安全至关重要的代码库中, 这被称为攻击面缩减。 这里的理论是,作为开发人员和安全工程师, 你不可能知道代码中所有的漏洞在哪里。 而你所依赖的框架可能也是如此。 但是,您可以限制攻击者可能接触到的代码量。 精简到满足您功能所需的最小集合 这样就大大降低了存在漏洞的可能性。 在您的应用程序中实际使用的代码中。

    Apple在整个系统中都使用了这种技术。 为了减少攻击者可利用的空间, 这涵盖了各种限制性技术 哪些复杂的文档格式可以被解析为基于条件的功能 判断交易对手是否值得信任 在锁定模式下禁用所有功能。

    例如,格式限制方面: 如果你知道你的应用程序只会 发送和接收 JPEG 图像, 然后允许你的接收 端代码处理其他格式的图像需要编写 数百万行解析代码。攻击者无需费力寻找漏洞。 因此,在这种情况下, 产生立竿见影效果的最佳方法是 提高安全性的方法之一是检查 格式良好的 JPEG 文件。 或者配置核心图形处理, 使其仅为您的进程解析该格式。 如果流量不符合预期,就直接停止流量。

    您的应用可能无意中暴露的另一个地方 并非绝对必要的攻击面是 直接使用 Web 视图渲染 Web 内容时 例如应用内浏览 以及通过您可能从第三方导入的框架, 例如广告SDK。

    从 iOS 26.4 开始, WebKit 新增了两个强大的选项, 称为增强型安全 Webviews。 这些模式允许您控制您的应用程序, 控制你的应用处理来自网络的内容的方式, 并增加了一些额外的安全加固措施。

    默认情况下, Wkwebview 提供所有功能。 并直接在您的应用程序内优化WebKit的性能 同时保留 Safari 的所有 安全缓解措施和架构。 第一种新的模式限制模式, 最大程度地提高兼容性,保持完整的网络兼容性 WebKit目前支持的 同时移除了大部分复杂的框架代码 并增加最新硬件安全缓解措施的使用。

    第二种新模式,即限制封锁模式,则更加严格。 它还允许您选择应用程序的 Web 视图。 进入与封锁模式相同的极端防护级别, 它会移除很少使用的网络技术 并进一步减少了攻击者的选择。

    在 Apple, 我们已经 开始采用这些增强型安全 Web 视图。整个操作系统,包括邮件系统。 快速浏览: iOS 26.4 中的强制 门户快捷方式和 Apple 广告。 现在正是你尝试它们的好时机。

    减少攻击面能为安全投资带来巨大的回报。 但前提仍然是内存安全漏洞将始终存在。 任何剩余的用不安全语言编写的代码。

    在这种情况下,次优的防御策略是 影响恶意行为者利用这些漏洞的能力。

    我们通过结合多种技术来实现这一点, 这些技术被称为安全缓解措施。 以及缓解措施这一概念。 抱歉,缓解措施的概念并不新鲜。 它们在过去三四十年里不断 发展演变,并一直存在至今。 Apple一直在努力发明和提供新的技术。 而且很多功能都可以在您的应用程序中使用。 当我们确信它们使用起来是安全的, 很多情况下甚至无需您 进行任何操作即可默认开启。

    缓解措施包括早期防御缓冲区溢出。 类似 clang 的栈保护器, 用于保护不可执行栈 以及ASLR。 到现代硬件辅助技术,例如内核完整性保护 Xcode 26 中存在 快速权限限制。确实存在。 有新的、功能强大的软件 以及可用于保护应用程序的硬件辅助缓解措施。

    指针认证是一种由编译器辅助的硬件技术。 它可以检测并预防整类漏洞。 免受剥削并 提供强制措施,即使漏洞确实发生, 您的应用程序代码流程完整性得以保持。

    内存完整性强制执行技术随 iPhone 17 和 iPhone 17 Pro 一起推出。这是 五年研究的成果,建模与工程它是建立在安全分配 器提供的强大基础之上的, 结合增强的记忆标记功能, 扩展和安全策略,称为标签保密性强制执行。 那已经很多了,而且这些课程还会有更多内容。 那将更多地涉及这些缓解措施。 在稍后的“采用内存完整性 强制执行和指针认证”会话中。 你可以在 security.apple.com 博客上阅读关于我的所有信息。

    Apple认为内存完整性强制执行代表 内存安全性的最重大升级 在消费者操作系统发展史上。

    然而,在极少数情况下, 老练的攻击者或许仍然能够 找到一个可以利用的漏洞 尽管我们采取了缓解措施,但仍然遭到利用。

    在这种情况下,我们的最后一道防线被称为遏制。

    回顾一下前面提到的链条。 在攻击者已经发现的情况下 并利用了应用程序中的内存安全漏洞。 他们现在完全控制了它。

    理想的目标是将攻击者困在这样的环境中。 所以他们无法触及应用 程序数据或平台的其他部分。

    自iPhone诞生之初以来, 应用程序在沙箱中运行,这有助于防止恶意访问。 并保护用户数据。 当然,该应用程序仍然可以访问其自身的数据。 并使用提供这种能力的系统服务。 这些应用程序使用的框架 可以抵御高度复杂的攻击者。 虽然还需要更极端的隔离措施。 不可能直接断开整个应用程序的连接。 系统就是这样。 他们需要过多的权限才能执行 诸如绘制用户界面之类的操作。 或者获取在这种环境下生存所需的资源。 因此,对于像“信息”这类安全 至关重要的应用程序来说,需要一种新的方法。 还有 Safari。 Apple 设计了一种多进程架构。 转移对攻击者提供的复杂数据的处理 进入一个高度沙盒化的环境中, 几乎没有任何访问权限 占用系统资源,例如其他服务甚至内核。

    这样做的结果是, 我们可以转移未知漏洞带来的风险。 即使达成妥协,也会陷入一种境地, 将攻击者困在一个缺乏资产的环境中。 比如消息或饼干。 环境也几乎没有提供任何选择的机会。 提升权限并以更高的安全性访问用户数据。 在 Xcode 26 中, 您的应用也可以利用这些功能。 在这些受到严格限制的安全环境中, 它们被称为增强型安全扩展。 我将详细介绍消息系统在接收 到照片时如何使用这种架构。

    所以消息系统将其称为安全扩展。 防爆门。安全策略很简单。 在解析之前,将所有收到的资产视为恶意资产。 并分解成原始类型或在防爆门内引爆。 这项政策意味着,权限更高的短信应用, 只需要验证从防爆门接收到的简单类型即可。 复杂且有风险的解析操作是在最低权限下进行的。

    所以信息通过互联网到达信息应用程序,然后, 不尝试对其进行任何解析或处理, 直接将其送至安全防爆门流程中的防爆门。 现在是时候开始处理和解析图像了,记住, 可能是攻击者发送的。 显然,在绝大多数情况下,该图像是有效的。 消息将其从一种潜在的复杂类型转码而来 转换成更简单的格式, 以便进行验证并用于向用户显示。 如果图像无效,则会发生故障。 一条消息会直接中断流量。 如果攻击者能够成功利用 Blast Door 中的漏洞, 不过,它们都包含在第二个过程中。 它无法访问消息数据库或系统的其他部分。

    所以 Blast Door 现在 需要将其图像返回到消息中。 这里需要记住的重要一点是,我们应该考虑 安全遏制措施可能受到损害。 因此,任何离开它并返回 到消息中的数据都不可信。 这可能是成功转码的图像。 或者,它也可能完全是攻击者控制下的恶意数据。

    因此,最重要的事情是 这种架构确保消息安全。 并安全地验证其接收到的数据 是否为格式良好的图像。 并且符合我们预期的格式, 它结合了之前两种内存安全策略, 迅速且攻击面缩小。 为了安全地进行验证 并尽可能减少需要公开的代码量。 图像验证通过后, 可以放心地把它写进成绩单里。

    这只是Apple如何运用隔离机制的一个例子。 在我们安全最敏感的代码库中。

    最后,第五个可行的选择是找到 并尽可能消除代码中的漏洞 在尽可能地推行其他策略的同时, 也要独立完成这些策略。 这不足以阻止攻击。 但它可以有效地找到最薄弱的环节。 并像攻击者一样,找出最容易下手的目标。

    在 Apple, 我们采用 人工和人工相结合的方式。 以及自动化技术,以帮助我们发现代码中的漏洞。 我们在开发过程中和开发 完成后都会进行代码审计。 并使用 clang 静态分析器来辅助人工审核 以及在持续集成工作流程中。

    我们利用模糊测试技术, 该技术利用工具自动生成大量数据。 向程序输入大量数据,试图偶然发现漏洞, 以及地址消毒器之类的工具 线程清理器不仅可以帮助调试代码, 而且还要在开发过程中主动发现漏洞。 并结合模糊测试等技术。

    所以,这就是 Apple 思考分层方法的一个框架。 编写内存安全代码。您可以使用我所有的工具。 刚才讨论的是如何通过 某种方式尽可能地消除漏洞。 使用像Swift这样的内存安全语言。

    通过减少攻击来使漏洞无法利用 可用的表面。 通过对代码应用缓解措施, 反而使漏洞可以被利用。

    控制漏洞造成的损害。 如果在使用增强型安全扩展程序时发生这种情况 进行复杂的解析。 最后,确定 并尽可能消除代码中的错误。 在实施其他策略的同时,尽可能地采取这些措施。

    现在我把话筒交给德文。 领导苹果公司开发安全语言和工具的负责人。 谢谢。

    谢谢,马克。你好,我是德文·考夫兰。 我在Apple领导开发者安全工具小组。

    Xcode提供了一系列精心挑选的保护措施。 为您的应用提供最先进的安全保障。 我将详细介绍这些具体的保护措施, 以及如何启用它们。 并说明如何将它们结合 起来以提供最大程度的保护。

    安全就是在保证内存安全的同时权衡各种利弊。 最关键的权衡是安全收益 以及工程方面的努力,例如代码更改和测试。

    把这种权衡想象成一个图表,纵轴代表收益。 以及横向上的易用性。

    有些保护措施虽然容易实施,但效果却不明显。

    有些难度更高,但安全性更高。

    权衡利弊的关键在于用 最小的努力实现最大的收益。 在获得最大利益的同时。

    右上角是最佳位置。

    在 Apple, 我们的目标是 紧密地共同设计硬件和操作系统, 以及提供接近该最佳平衡 点的保护措施的编程语言, 同时保持良好的性能。

    例如,内存完整性强制执行具有很强的安全性。 采用成本大幅降低 并且比仅使用软件保护具有更高的性能。

    我将介绍四种不同类型的保护措施。

    漏洞查找工具,可帮助您 在安全漏洞发布前发现它们。 并为更强有力的安全措施铺平道路。

    全应用保护,可提高整个应用的安全性。 通常只需点击一下按钮即可。

    C 和 C++ 代码加固 这将有助于您为现有的不 安全代码库添加约束安全性。

    以及提供完全内存安全性的Swift。

    这些策略与马克描述的策略相同。 但我会深入探讨运行它们所需的实际技术。

    马克按照新代码库的保护级别 从高到低的顺序排列了这些级别。 就从这里开始吧。 从一开始就为整个应用程序提供最高级别的保护。

    但当对安全系统进行改造时 转移到一个已经遭受攻击的现有应用程序上, 制定策略,选择现在可以采取 哪些保护措施,这一点很重要。 这需要一些时间。

    我会谈谈它们。 按易于部署且能快速取得安全 成效的措施的采用顺序排列, 极其强有力的保护措施, 但需要更长时间才能付诸实施。

    我来帮您了解这些不同的技术。 这样你就可以尽可能地确保你的应用安全。

    我先从查找漏洞的工具说起。

    这些并非已发布应用程序 中正在运行的有效保护措施。 相反,它们用于构建 以及调试时间,帮助您在产品发布前发现错误。 它们也为未来铺平了道路 通过及早发现漏洞,从而更好地推广技术应用。

    这些工具位于右下象限。

    它们使用起来很简单。 只需运行它们,即可开始解决问题。

    他们找到了可以采取行动的漏洞, 但不可能找到所有漏洞。 所以尽管它们是一种极好的预防措施, 采取其他保护措施至关重要。 我稍后再谈。

    好消息是,运行这些工具 可以更容易地做到这一点。

    我要介绍的第一种漏洞查找工具是消毒器。

    他们会重新编译你的应用 程序带有额外的检测功能, 有助于在应用程序运行时发现错误。 它们适用于基于 C 的语言和Swift。

    消毒液的优点在于它们非常…… 误报率很低。如果消毒程序报告有漏洞, 那就确实存在漏洞。

    地址清理器通过跟踪来发现内存安全漏洞。 哪些内存位置有效,哪些无效?

    它非常擅长查找 Mark 遇到的那种缓冲区溢出问题。 之前已经展示过,并且在修复免费漏洞后 可以使用。程序员释放内存时, 不小心留下了一个悬空指针。

    它可以检测堆、栈以及全局变量中的内存损坏。

    它还提供了内存分配和释放的回溯信息。 为了帮助您快速修复错误。

    线程清理器会发现发生的数据竞争。 当一个线程干扰时 另一个线程访问内存时,两个线程之间没有同步。

    即使在自动引用计数的情况下, 这也会导致内存损坏。

    接下来是clang静态分析器。

    它甚至无需运行您的应用程序即可发现错误。 相反,它模拟了程序中可能的运行路径。 这意味着你不需要测试覆盖率就能找到bug。 但缺点是该工具可能会出现误报。 也就是说,它可能会报告 一个实际上并不存在的错误。

    该分析器支持 C、 C + + 和 Objective-C。

    它能发现许多安全敏感漏洞,包括缓冲区溢出。 冻结后使用、使用未初始化的内存 以及不安全的 API 使用。

    在目标构建设置中启用这些检查 然后从产品菜单中选择“分析”来运行分析器。

    当分析器发现错误时, 它会显示问题以及问题发生的控制流路径。

    箭头显示了导致程序出错的程序路径中的每一步。 注释描述了沿途的关键事件。

    例如,在使用中。 释放内存后,路径显示了内存的分配和释放位置。 后来被使用。

    这样就很容易理解和修复漏洞。

    地址消毒剂,线头消毒剂 Clang静态分析器是关键的缺陷查找工具。 帮助你开发代码。 在构建和测试时使用它们。 他们会找到一些虫子,但不可能找到全部。

    跑步。这些工具是让跑步更轻松的第一步。 在您的应用程序中采取更强有力的保护措施。 需要明确的是,还有更多工作要做。 但它们确实为增加其他 必要的保护措施铺平了道路。

    接下来,我将介绍整个应用程序的保护措施。 这些功能为您的整个应用程序 提供了极佳的基础安全防护。 包括您的代码、库和系统框架。 它们很容易添加,并且能极大地提升安全性。

    我将介绍五种不同的全应用 保护机制以及类型化分配器硬件。 内存标记、指针认证、只读内存 以及增强的安全扩展。

    第一种完整的应用程序保护措施是类型化分配器。

    这是一个新型系统分配器, 可以防止空置后使用攻击。 它非常棒,因为它位于右上角的最佳位置附近。

    它防护性能好,而且非常容易上手。

    释放后使用漏洞是一种内存损坏形式。 程序分配内存并将其冻结。 但却意外地留下了一个悬空的指针。 被释放的内存类型被称为受害者。

    攻击者通过操纵程序来利用悬空指针。 分配另一种类型。 同一地点的攻击者, 然后导致受害指针访问攻击者的数据 仿佛他是受害者一样。 这是类型混淆。 如果施暴者能够诱骗受害者撤退。 攻击者。攻击者将数据控制为指针。 它们可能会修改非预期内存, 甚至调用意料之外的代码。

    使用类型化分配器, 编译器和操作系统协同工作。 为了防止类型混淆, 需要以概率方式分配不同类型的类型。 位于不同的内存桶中。

    在对施暴者进行分类时, 受害者很可能会被归入不同的类别。 因此攻击者无法依赖类型混淆。

    有一篇很棒的帖子 在Apple安全研究博客上探讨下一代 Xnu 内存安全 详细阐述了如何将此方法应用于内核。

    启用类型化分配器。 转到签名和功能编辑器。 添加增强的安全功能并启用构建设置。

    今天就开启它。它能提供非常强大的防盗保护。 释放漏洞后。 只需勾选一个复选框并重 新编译你的应用程序即可。

    第二种全应用保护方式是硬件内存标记。 它对缓冲区溢出和释放后使用漏洞极其有效。 以及堆分配的内存。

    这是CPU的一项功能, 旨在与类型化分配器配合使用。 内核标签保密性用于提供内存完整性强制执行。

    它适用于 iPhone 17 和 iPhone Air。 以及基于M5的 Mac 和 Vision Pro。

    硬件内存标记非常接近右上角那个最佳位置。 它防护性能高,而且易于安装。 同时保持较低的性能开销。

    它通过以下方式防止内存损坏。

    类型化分配器将一个标签与每个 指针和每次分配关联起来。

    然后,CPU 会确保标签和指针正确无误。 并且与内存中的标签匹配。 否则,则表明存在释放后使用错误或缓冲区溢出。 因此,硬件会安全地捕获内存错误, 而不是允许内存损坏。

    以下是一个缓冲区溢出的例子。

    受害者指针具有与其指向的内存相匹配的标签, 攻击者指针也是如此。 缓冲区溢出攻击 者通过攻击者指针溢出到受害者的内存中。 但是由于攻击者指针有一个标签, 而受害者内存有另一个标签, CPU 检测到不匹配, 而不是允许攻击继续进行。

    内存标签不匹配会导致应用崩溃 因此,在启用内存标记诊断功能之前, 请先启用保护测试。 以及地址消毒器和线程消毒器下,

    然后修复任何内存损坏。

    要了解有关如何启用硬件内存标记的更多信息, 在开发者上观看“使用内存完整 性强制执行保护您的应用”视频。

    即使启用了安全分配器和硬件内存标记, 某些内存损坏漏洞仍然可以被利用。

    第三种全应用保护方式是指针认证。

    采用指针认证时,硬件…… 编译器和操作系统协同工作 通过加强应用程序中 控制的完整性来提供深度防御。

    它适用于iPhone 10 S 及更新机型, 以及所有Apple 芯片 Mac 电脑。

    在控制流完整性攻击中, 攻击者利用内存损坏劫持了应用程序的控制权。

    这使得程序处于开发者从未预料到的意外状态。

    回想一下马克之前描述的栈缓冲区溢出问题, 攻击者导致密码缓冲区溢出。 覆盖附近的函数指针。

    通过提供超长的密码, 攻击者可以更改函数指针的值 他们想要什么都可以。 然后他们可以调用程序中的任何函数, 导致意想不到的情况发生。

    采用指针认证。

    CPU 对指针进行加密签名。 并在使用前对其进行验证。 如果指针的签名与预期不符, 硬件会安全地捕获异常,而不是调用意外代码。

    在缓冲区溢出示例中。 这将防止攻击者调用任意函数。

    指针认证与内存完整性强制执行机制配合良好。 作为一道最后的保护屏障。

    它在福利采用率图表中稳居中心位置。 它具有良好的防护性能, 但可能需要一些时间来适应。 尤其是在复杂的C++代码库中,

    所以要彻底测试你的应用。 启用后请确保不会出现崩溃。

    而且,当它与类型化分 配器结合使用时,效果最佳。 以及硬件内存标记, 在处理指针认证之前,请先采用这些方法。

    这需要构建一个通用二进制文件, 一个同时包含 arm64 和 arm64 e 切片的程序, 这样,您的应用就可以在没有 硬件支持的旧设备上运行。

    这意味着你应用中的所有库也必须是通用的。

    所以,如果您依赖于供应 商提供的二进制库或框架, 你需要与他们合作, 才能获得该依赖项的通用版本。

    第四种保护措施是只读平台内存。

    动态加载器负责加载应用程序中的代码。 及其库。如果攻击者能够得逞, 它将成为一个极具吸引力的目标。 为了破坏动态加载器的元数据, 他们可以利用它完全控制你的应用程序。

    只读平台内存可防止攻击者修改数据。 所以只有动态装载机本身才能更改它。 它提供有效的安全保护,而且非常容易上手。

    大多数应用程序无需进行任何更改即可兼容。

    以确保兼容性。使用系统 API 修改 De Wilde 和 Objective-C 运行时元数据。 不要直接修改它们。

    最后一种类型的完整应用程序 保护措施是进程外增强安全扩展。

    正如马克所描述的那样, 这种方法是一种遏制形式。 它提供了非常强大的安全保障, 虽然实施起来需要一些投资。

    实现方法是将处理不受信任 数据的代码移到扩展程序中。然后使用

    Secure System Xpc API 从您的 主应用程序传输数据。 处理扩展程序中不受信任的数据, 将结果传递回主应用程序,并确保对其进行验证。

    现在,遏制措施的前提是 攻击者能够利用内存损坏来破坏扩展程序。

    目标是将攻击者限制在进程内。 因此,他们将无法访问主应用程序中的数据。

    要采用此方法,请评估您的应用程序 中哪些部分会处理不受信任的数据。

    重构代码库,将这些部分分离到不同的流程中。 并验证扩展程序的响应。 别相信它。

    这是对其他保护策略的补充。 它不能替代它们。 因此,请在为整个应用程序启用 内存完整性强制执行后再采用此方法。 而且,扣除这些因素可能需要一些时间。 与指针认证并行进行。

    整个应用程序的保护只是其中的几个步骤。 或者只需在Xcode中执行几个步骤即可。 启用类型化分配器硬件内存标记、指针认证 以及只读平台内存。 通过采用增强的安全功能。

    然后创建一个增强型安全扩展。

    采用这些全应用保护措施 为您的整个应用程序提供强大的基本安全级别。

    这些保护措施很棒, 但如果你的应用程序存在攻击面, 则需要更多保护。 在基于 C 语言的语言中。

    收养霍拉普之后, 保护措施应采取更强有力的手段。 对于处理不受信任输入的不安全代码库。

    Xcode为 C 和 C++ 都提供了保护。

    我先从C++开始。

    大多数代码库都使用容器类的标准库。 以及其他广泛使用的抽象概念。

    C+ + 标准库的加固措施可防止越界访问。 以及像标准 span 和 vector 这样的类。 它能安全地捕获内存损坏, 而不是允许内存损坏发生。

    对于大量使用标准库的代码库, 安全加固位于安全采用权衡的右下象限。 它非常容易上手,而且能提供可靠的保护。

    为了提供更全面的保护。 Xcode 的 C+ + 安全缓冲区 使用选项提供了更强的保证。

    在这种模式下,编译器会拒绝 不安全的原始指针运算。 相反,它需要使用惯用的库抽象。 例如标准的 span、字符串和向量。

    这样,编译时检查的组合就实现了。 为了防止原始指针运算和运行时保护, 而经过强化的 C+ + 标准库为该 语言带来了边界安全性。

    与 C+ + 不同, C 语言不具备 高级语言特性,例如 作为易于使用的库抽象的运算符重载 用于有界安全指针运算。

    为了填补这一空白, Apple 为C 语言 创建了一个新的语言扩展,该扩展可以烘焙 在安全范围内直接 并正在努力将此扩展纳入语言标准。

    以下是安全索引的工作原理。 将指针转换为内存地址需要知道 指针指向的内存边界信息。 为了确保安全,编译器会通过 报错阻止对该索引的操作。 当无法确定指针边界时。 然后您可以为注解添加边界, 告诉编译器如何确定边界,

    它会插入边界检查, 以便在运行时安全地捕获异常。 越界访问。

    这样,程序员提供的注释就结合了起来。 编译器生成的运行时边界检查带来了边界安全性。

    Xcode C 和 C++ 边界安全特性位于 位于象限的左上角。 它们提供强有力的保障。 消除一整类漏洞, 但它们确实需要对源代码进行修改。 在您已经实施了整个应用 程序保护措施之后再应用它们。 例如内存完整性强制执行, 为最敏感的 C 和 C+ + 代码 增加更多安全保障。

    到目前为止,我谈到了旨 在对现有防护措施进行改造的工具。 添加到您不安全的 C、 C + + 和 Objective-C 代码库中。 这些都是强有力的保护措施。 但要实现完全的内存安全, 需要使用内存安全的语言。

    Swift 具有内存安全性, 并充分利用了平台的安全保护机制。 在操作系统硬件和语言层面。

    Swift 6.2 提供了 一系列新的轻量级库。 为解析器中的底层使用而设计的抽象 以及其他对安全性和性能要求极高的应用场景。

    例如,新的跨度类型族允许访问 持续的无主内存。

    Span 完全内存安全。 它使用了高级功能

    并使用Swift标准库来保证生命周期和绑定。 安全跨度也很快。 如果您需要保证零运行时开销,那它就非常棒。 为了终身安全和高度优化的边界检查。

    Swift Span 结合了编译 时检查以确保生命周期安全性。 运行时安全检查既能保证安全性,又能降低开销。

    有两种策略可以保护您的应用程序, Swift采用了新的代码 以及编写和重写现有的安全敏感代码。

    两种策略都要用。

    Swift让编写内存安全的代码变得容易。

    如果你还没有开始用 Swift 编写新代码,那就从现在开始吧。

    你今天编写的代码将成为攻击者未来攻击的目标。 所以,用 Swift 编写新功能。 当你重构或现代化现有子系统时, 借此机会也用Swift编写这些代码。

    为了保障安全,不要等到出现机会主义的重构。 相反,主动用Swift重写这些组件。

    最需要重点关注的领域是解析器, 尤其适用于媒体和协议方面。 复杂状态机。 控制对象、生命周期以及 任何处理不受信任输入的内容。

    现在您知道如何保护您的应用 程序免受恶意攻击者的侵害了。

    这些保护措施与 Apple 对其自家 应用程序所使用的保护措施相同。 它们在分层时经过精心制作 并应用于代码库中的关键位置。 他们会为您提供一流的安全保障。

    采纳这些建议。在 Xcode 中启用 增强的安全性,包括内存完整性。 强制执行和指针认证, 为您的整个应用程序提供基本级别的保护。

    包含对不受信任输入的处理以及增强的安全扩展, 使用绑定安全扩展来保护您不 安全的 C 和 C+ + 代码库。

    而且要使用 Swift, 它对所有新代码都是完全内存安全的。 以及针对安全表面的定向重写。

    如今安全比以往任何时候都更加重要。 立即采取这些措施来保护您的应用和用户。 以及他们的敏感数据。谢谢。

    谢谢Devin和Mark提供的精彩概述。 现在我们稍作休息。 请于太平洋时间上午 11:25 回到 大苏尔的线上和线下地点与我们汇合。

    希望你假期过得愉快。 我希望到场的各位都有机会聊聊。 与你的开发者同事们一起。

    接下来的演示将介绍一种独特的硬件组合。 以及软件安全。 我觉得这作品很棒,相信你也会这么认为。 请和我一起欢迎恩里科。

    谢谢。

    您好,欢迎光临。

    欢迎来到今天的第一次深度分析。 我的名字是亨利·卡佩尔。 我是一名安全工程师, 稍后我的同事们会和我一起上台。 菲利波和奥利弗·亨特。 我们将介绍我们两项最令人兴奋的安全功能。 内存完整性、强制执行和指针认证。 这将是一场高阶演讲。 我们将讨论内存分配器、指针和安全模型。 首先,我将向你介绍长度的概念。 内存完整性强制执行机制旨在保护您的应用程序。 然后菲利波会向你展示如何 针对特定的自定义内存管理实现 这可能会阻碍安全应用,或者根本不起作用。 牢记诚信执法。 接下来我们将转换话题。 奥利弗将登台,从另一个角度 阐述我们的安全战略。 我们如何使用指针认证 防止攻击者实现任意代码执行。

    介绍就到此为止吧。 我们先从内存完整性强制执行开始。 我们之前提到过,我们的大 部分安全问题都与内存有关。 内存完整性强制执行中的损坏漏洞。 我们的使命是使其中很大一部分变得可开发利用。 这太棒了。它们不再对你的应用 程序构成安全隐患了。

    我们将以与使用内存完整性 强制执行相同的方式发布它。 对我们自己而言。 但这并不会阻碍他在这里的发展。 这是因为我们坚信第一方应用程序 第三方应用程序对于保护用户安全同样至关重要。 这就是为什么我们整理了 一些很棒的资源来帮助大家入门。 具有内存完整性强制执行功能。 我们发布了一篇博客,详细介绍了所有安全轴心。 内存完整性强制执行机制。

    我们有一个技术讲座,会一步一步地教你如何操作 在您的应用程序中集成内存完整性强制执行。

    我们还共同完成了一些很棒的结尾 结束围绕同一主题的大量文档。 无需做笔记。 我们会通过电子邮件向你们发送所有这些链接。 如果您稍后观看,它们也会附加到在线会话中。 所以一定要去看看。 他们很棒。今天我就不做全面介绍了。 内存完整性强制执行。 相反,我将把全部精力集中在我们的申请上。 与其安全核心组件进行交互, 利用内存的类型感知安全内存分配器 标签扩展。

    MTV 是一种硬件技术 以及在内存完整性保护方面最大的投资之一, 而且可以说,它也是这项申请影响最明显的一项。 那么我们快速看一下。 这是一个锁和钥匙系统。 内存锁以 16 字节粒度分配。 这比页面粒度要低得多。 而且它非常适合动态分配。 密钥则存储在指针中。 高位、锁和钥匙通常被称为标签。 因此,名称记忆标签软件控制着标签的分配。 这就是我们所说的标签策略。 图中,黄色缓冲区标签七是由软件决定的。 这两个指针也是如此。 左侧硬件则实现了我们称之为检查策略的功能, 只有当锁和钥匙匹配时才允许访问。 即使指针现在有了标签,

    这些对软件的影响不大。 事实上,在任何存储器中, 完整性保护都非常简单直接。 只需在Xcode上点击几下即可。 与其他需要付出相当大的努力才能 采用的技术不同,这项技术无需如此。 我的作品大多基于未经修改的软件,除非…… 我的意思是,这其中肯定有猫腻。 否则我们今天就不会在这里了。 除非你的应用程序实现了自定义内存管理。 我之前已经多次回避过这个话题, 所以我们来详细探讨一下它的含义。 在应用程序中可以发现三种模式。 这就实现了自定义内存管理。 我将按照可能性大小的顺序在这里使用它们。

    第一个是已分配的包装器。 已分配的包装器是封装系统分配API的接口。 这样做通常是为了便于携带, 或者,您需要在内存操作 方面实现一些额外的逻辑。

    它们是迄今为止最常见的结构。 因此,这与我稍后 要谈到的另外两个案例截然相反。 我在这里举一个简单的例子来加深你的理解。 这里我们定义一个名为 Allocate memory 的函数, 实际上,它会调用 malloc。

    你看到的其余代码调用了 malloc 目录, 但却总是跳转到这个界面。 我们先把这个例子搁置一下。 菲利波将以简短的形式登台, 并对此进行详细阐述。

    第二种直接内存匹配模式是池化和缓存策略。 这些策略的目标是 通过回收利用常用物品来提高性能, 这样可以避免与系统分配器进行往返通信。 它们也比较常见,但远不如分配器包装器常见。

    最后,值得庆幸的是, 这种情况在实际应用中极其罕见。 但在框架中则更为普遍。 这些都是完全自定义的内存分配器。 在这种情况下,我们可以直接替换。 这完全绕过了系统默认设置。

    直觉上,这三种模式都是有害的。 它们干扰内存标记完整性执行的安全性 或者完全绕过系统分配器。 因此,不出所料,今天最重要的建议来了。

    使用系统默认分配器。 我打算在这个幻灯片上停留一会儿。 使用默认的系统分配器, 不使用任何包装器和缓存。 甚至是自定义实现。 我们花了很多时间使其可扩展。 而且对绝大多数用户来说速度很快。 在过去的三年里,它被从零开始重新实现。 所以,如果你参考的是三年前的业绩数据, 请重新评估它们。 你或许会感到惊喜。 我们对其进行了重写, 使其在诚信执法方面表现出色。 现在,我们确实理解有些抽象 概念存在是有其合理原因的。 也许你只是有一些运行缓慢的遗留代码。 你必须把它传承下去。 没问题。所以接下来, 我们将看看你如何维护它们。 但要做到这一点, 你需要…… 以满足我们分配者 所达到的极高的安全标准。 要做到这一点,你需要了解 我们为什么那样设计它。 这意味着我们应该了解安全性。 背后的科学原理。

    那么,让我先从攻击者所做的事情说起, 那就是利用系统漏洞。 希望这能引起共鸣。也想借此 机会为大家稍微揭开这个话题的神秘面纱。

    编写漏洞利用程序是调试的逆过程。

    每当你调试内存损坏问题时, 你从犯罪现场开始, 追溯到一段被错误修改的记忆。 或者使用不当,导致程序运行异常。 你试图把各个部分拼凑起来,找出原因。 是什么漏洞?损坏的内存片段在哪里? 起源于?

    在剥削方面,情况则恰恰相反。 你从一些内存数据开始,但这些数据处理不正确。 然后你试图找到一些正在修改的内存。

    更科学地说。 我们采取了一种在业内略显独特的方法, 实际上,它是以攻击者为中心的。

    我们有一段记忆,我们称之为侵略者类型。 正是这个因素滋生了腐败。 这里,“类型”一词发挥了很大作用。 指的是内存位置的内在特性。 它可能是某种数据结构。 它可能是一串字符串,也可能是一组记录。 然后我们来看受害者类型。 这是攻击者可以篡改的宝贵内存。 这就是内存损坏的目标。 内存中可能包含密码、函数指针等信息。 或者某种日后能让攻击 者获得的能力执行任意代码。

    这两种类型的数据都存储在内存中。 所以它们之间有一定的距离。 可能有一名病人,在这种情况下,距离为 1。 或者它们甚至可以重叠, 在这种情况下,距离为零。

    我们假设攻击者可以观察系统。 并执行任意次数的操作。 它们可以与内核通信,浏览文件系统, 任何特权允许他们做的事情。 我们不依赖于限制攻击者的行为。

    如果攻击者控制了该区域, 则攻击者获胜。并预测 攻击者与受害者之间的距离。 就是这样。这就是写入内存的全部本质, 利用数据损坏漏洞。

    这看起来很简单,而且,好的科学通常就是这样。 但这其实意义非常深刻。 我们花了很长时间才到达那里。 这一点很重要,因为它突显了我们有哪些选择。 在防御内存损坏方面。 这表明,我们实际上只有 两种可以利用的杠杆类型。 以及从防守者的角度来看的距离。 整个游戏的目的就是让类型 选择和距离控制变得不可能。 或者攻击者极难预测和控制。 这就是为什么我们所有的安全内存分配器都实现了 四项关键安全特性。

    首先,它们具有类型识别能力。 传统上,内存分配是通过 malloc 完成的。 仅仅是一袋字节, 它所知道的也仅限于请求的大小。 它并不了解分配的意图。 我们的安全分配器则理解类型, 而这种巨大的杠杆作用是通过手动输入实现的, 主要用于内核和编译时自动键入, 这就是我们在用户空间中使用的技术。 一旦我们有了类型信息,我们就可以开始玩了。 我们的分配器可以开始让攻击者难以控制它们。 我们可以将数据类型分离到内存中的不同区域, 而且因为我们有很多种类, 所以我们不可能每种种类都划分一个区域。 那将是理想的选择。 所以我们所做的就是将具有 相似特征的人聚集在一起。 我们在启动时随机化这些集合 这样一来,不同设备上的分组就会有所不同。 这迫使攻击者想方设法改进他们的攻击手段。 他们在一台设备上对每种 不同的组合都进行了运行。

    同样地,我们对内存 中这些类型区域的位置进行随机化处理。 我们再次在启动时执行此操作。 因此,我们引入了设备间的进一步差异。 这再次挫败了攻击者的企图。 预测不同类型类别之间的距离。

    最后,也是 非常重要的一点, 我们利用了内存标记扩展阻止各类攻击。 我们在每个地点和 Freecycle 平台都分配了不同的标签, 并确保标签在整个区域内均匀分布。 我们也强制执行该规定。 两个相邻的对象具有或具有相同的标签, 使得任何介于一号和小号 之间的距离都无法被利用。

    这就是我们的安全分配器如何 运作以防止内存损坏漏洞的。

    现在我将把舞台交给菲利波。 谁来负责你能做什么 为了防止三种常见的自定义内存管理模式 我之前讨论过的。 所以它们不会损害应用程序的安全性。

    接下来该你了。

    谢谢。我叫菲利波,是一名安全工程师。 现在就在这里。正如恩里科所说, 在大多数情况下, 采用内存完整性强制措施并不需要 您这边需要做任何代码更改吗?

    然而,在某些情况下,我们确实需要格外小心。 接下来,我将详细讲解这些内容。 我们将逐一探讨它们如何影响内存、 完整性和执行力。 及其安全模型,并解释最佳解决方案是什么。

    那么,让我们先来看一看。 在或许是最常见的抽象概念中 各种代码库,分配器包装器。

    让我们以恩里科刚才举的例子为例来说明这一点。 分配内存函数 因为它让我们有机会定义 一系列你应该关注的特征。 识别代码库中的分配器包装器。

    首先,包装器是为函数添加逻辑的函数。 系统分配器 API。 它们是通用的,因为它们允许请求使用的内存。 用于存储不同类型的数据, 它们在整个代码库中被用作抽象层。 与分配器交互。

    所以这一切看起来似乎都无伤大雅,对吧? 好,我们来详细了解一下类型 隔离在实践中是如何运作的。 了解分配器包装器的安全隐患。

    这里有一堆代码,用于实现一些数据包处理逻辑。

    别担心,我们不需要把所有内容都看完。 实际上,让我们重点关注 系统分配器接口的调用端。

    这就是类型隔离的基础所在。 实际上,是编译器在为你完成所有工作。

    当你在Xcode中启用类型分配器支持时, 我们启用一个名为“类型 内存操作”的编译器特性。 有了这项内置功能, 编译器将自动推断类型 针对您的每个分配代码站点 并将每个调用重写为类型感知调用, 将类型信息作为额外参数传递。

    你可以想象编译器为每次 内存分配分配不同的形状, 这些形状恰好代表了随后传递下来的信息。 给系统分配器, 它利用这一点来隔离基于位置的信息。 通过实施样式分桶策略来对它们进行分类。

    当你实现一个包装器时, 这些调用点对编译器就变得不透明了。 它只能看到一个形状。 现在,系统分配器会将你 所有的内存分配合并在一起。 因为它只能看到生成的类型信息。 在包装器内的调用点。

    那么,让我们看看我们 能做些什么来解决这个问题。

    当然,如果包装器实现的逻辑并不关键的话。 对你的代码功能而言, 最简单的解决方法就是完全移除包装层。 并直接调用系统分配器接口。

    如果你确实需要保留包装纸的话, 您可以实现类型感知变体,这将正确 向下转发类型信息 通过采用内存操作类型, 将内存分配给系统分配器。 操作系统中使用的编译器技术与此相同。

    那么,让我们一步一步地了解这个过程。

    首先,你需要声明包装器的变体类型。 它在大小参数之后紧 跟一个额外的类型 ID 参数。

    然后,您需要使用下划 线来注释您的未类型化变体。 通过指定相应的类型变体来定义 malloc 类型宏 以及大小参数的位置。 我们指示编译器转换所有对未类型化变体的调用 调用类型感知器, 此外,还要在每个调用位点合成一个类型描述符。

    最后,你需要实现类型感知变体。 但这很简单。 您可以保留所有其他逻辑。 唯一需要做的更改是调用适当的类型 通过转发您获取的类型描述符值, 实现感知 malloc 接口 作为一种论据。

    采用这种方法,您无需进行任何更改。 指向实际使用包装器的代码。 编译器将在每个调用点自动提供类型信息。 为你。

    以上只是一个简要概述,如需了解更多详细信息, 我建议您查阅一下介绍这种方法的文档。

    好了,现在我们明白了如何处理分配器包装器。 现在我们来看一下池和缓存。

    当我们谈到游泳池时, 我们指的是所有实施某种形式回收利用的方法。 为了避免往返,对同类型物体进行处理。 给系统分配器, 每次此类物品被丢弃后又重新投入使用时。 缓存策略也属于这一类。

    在此背景下,关键概念 要理解这一点,就必须明白 这些抽象概念会改变生活。 回收物品的循环利用过程

    这会给安全带来严重隐患。 内存标签提供的保护。 那么,让我通过一个例子来解释一下这些含义。

    这里我们有一个对象池, 每个内存分配都使用内存标记进行标记。

    当我们从对象池中提取对象时, 通常会得到指向该对象的指针。 它将与分配的内存具有相同的标签。

    使用完毕后,我们会把物品放回泳池。 现在考虑一下代码中存在漏洞的情况, 我们保留指向该对象的悬空指针。 这正是可能导致其被利用的原因。 攻击者可以利用的免费漏洞。

    如果我们只是简单地回收利用这个物品。 这意味着悬空指针 新的轻型车仍然会带有有效的标签。 攻击者如果控制了悬空指针,就能…… 在物品回收利用后对其进行改造, 这会导致内存损坏,从而改变应用程序的逻辑。

    为了保护分配免受此类漏洞的影响, 标签应该在对象被回收之前更新。

    重新标记后,任何使用带有旧 标记的指针进行的访问都将失效。 过时的标签将安全失效, 终止您的应用程序并防止被利用。

    那么接下来我将介绍一下 我们如何在实践中实现这一点。

    我们已在数据包处理代码中实施了回收策略。 当我们需要分配一个数据包时, 我们首先尝试从我们 维护的线程本地队列中提取它。 并且我们在释放桶函数中重新填充 当我们处理物品时。 如你所见,这种方法是受……影响的 正是我们刚才看到的问题。 那么,我们该如何做才能 维持 MTA 提供的保障呢?

    当然,你可能已经猜到了。 最佳解决方案仍然是直接使用系统分配器。 这样可以确保每一笔分配都被正确地重新标记。 为您提供您期望从内存中获得的保护 诚信执法。

    我们花了数年时间设计 并实现一个既安全又高效的系统分配器。 在所有情况下,它的性能都优于旧的分配器。 确实,有了线程局部缓存, 我们的系统分配器也针对此类场景进行了优化。

    在极少数情况下,这种方法可能不适合您。 我们鼓励您对代码进行性能分析。 并探索不会改变生活方式的不同策略 物体的循环。 例如,通过分批分配您将使用的对象。

    好的,这种方法非常容易。 调整您的资金池策略 为了保持 MI 所提供的安全特性。

    现在我想花点时间谈谈自定义分配器。 这可能并不一定会在您的应用程序代码中实现。 但它们可能是您正在使用的任何 外部依赖项的一部分。 库历来出于性能原因而实现它们。 或者为了跨平台移植。

    如果存在自定义分配器,一切都将变得不可预测。 理解正在使用的代码非常重要。 自定义分配器无法获得 Ma 的优势,尤其是 您的应用无法享受类型隔离的保护。 以及记忆标记。

    在这种情况下,您应该认真评估安全性。 保留此类实施方案的后果。 所以我想您应该知道我们的建议是什么了。

    将该代码过渡到直接使用系统分配器。 我们坚信这是保障您应用安全性的基础。 但是,我们也知道有些情况下您无法做到这一点。 干脆放弃自定义分配器。

    因此,截至 1 月 26 日, 在我们所有平台上, 我们已将您所需的所有构建 模块都包含在 SDK 中。 在您的自定义分配器中实现对 MT 的支持。

    接下来我们将逐一介绍它们。 但是考虑到每个分配器实现都有其自身的特点, 这取决于你自己去理解。 在您的用例中使用这些构建模块。

    首先,你需要检查运行时是否启用了空值功能。 您可以使用此处所示的操作系统 安全配置 API 来实现此操作。 启用硬件内存标记后, 在支持此功能的设备上, 您的应用将以启用“空”模式运行。 但是同样的代码仍然需要在不 支持空值的设备上运行。 所有依赖于此的操作 仅应在空指令集架构上执行 在确认该过程中确实启用了空窗口功能后。

    接下来,当你分配供分配器使用的内存页时, 你应该要求虚拟机提供支持内存标记的页面, 绕过虚拟机标志。 调用 VM allocate 时缺少一个空标志。 在此阶段,内核将为您提供一个页面 其关联的标签全部为零。

    这就是原因。最后,也是最重要的一点, 你需要标记你的位置。 有几种不同的可能性 在选择标签方案时, 还有许多细微之处需要考虑。 在实施这些方法时。

    简要概述一下可用的 API。 我们来看一个简单的例子, 假设我们重新标记每个位置。 当它被释放回我们的分配器时。

    标签和分配分为两个部分。 选择目标,并将其包含在指针中 然后将标签存储到标签存储器中。 选择标签的第一步是生成一个排除掩码, 该掩码考虑了以下因素: 针对当前与该分配相关的目标。 这样你就可以向硬件发出请求了。 通过排除先前使用的标签来生成 一个新的随机标签。 为空。生成随机标签将返回一个指向的指针。 写入同一内​​存,但高位使用不同的随机标签。

    最后,您需要使用空白的商店标签来询问硬件。 通过传递将新生成的标签存储到标签存储器中。 在带有新标签的指针中 以及需要标记的底层内存块的大小。 这就是在内存分配器中标记内存所需的全部内容。

    以上就是实施支持所需的所有基本工具。 用于内存分配器中的内存标记。

    至此,我们的米兰之旅也告一段落了。 但在我们结束之前, 让我重申一下本节的主要要点。 以及如何才能最好地利用内存完整性强制措施。

    您应该启用硬件内存标记。 并支持 Typekit 分配器 为用户提供切实有效的保护,最佳方案 课堂技术。 在缓解内存损坏漏洞方面。 它真的很容易收养, 尤其是因为在绝大多数情况下, 您可以获得这些技术带来的所有安全优势。 无需对您的代码进行任何更改即可提供此功能。

    然而,正如我们在本次演讲中所看到的, 某些模式会降低保护措施的有效性。 由内存完整性强制执行提供。 我们鼓励您审核您的代码库并找出问题所在, 这样你就能更好地评估你的风险敞口。

    每当你发现其中之一时。 首选方案。解决方案应该是过渡。 直接使用系统分配器。

    这真的能让你兼得所有可能的优势。

    然而,如果有时这样做不可行, 我们已为您提供了相关工具。 以及理解你需要适应这种抽象概念 为了支持内存、完整性, 加强执法并维护根深蒂固的安全属性 进入操作系统。

    现在我邀请奥利弗上台谈谈控制流完整性。 采用指针认证。

    谢谢,菲利波。大家好。 我是奥利弗 · 亨特, 一名从事安全和工具开发的工程师。 Apple的安全工具和编译器。 会议进行到此时, 你已经了解了如何保护你的代码 免受内存安全错误的影响。 具有内存完整性。 强制执行。 MI 使得利用内存 安全漏洞变得极其困难。 但它无法抵御所有可能的攻击。 我将向你展示如何采用硬件 基于指针的身份验证, 为您的应用程序提供身份验证 具有控制流完整性。 这意味着即使攻击者能够绕过我并破坏任意内存, 他们仍然不具备这种能力 控制应用程序将执行的代码 采用指针认证。 硬件、编译器、 操作系统的所有组件协同工作。 为了确保攻击者所针对的指针的有效性 当试图劫持您的应用程序时。 它的运作方式如下。 在引擎盖下,

    使用指针认证, CPU和操作系统会创建指针的加密签名, 然后他们将这些签名嵌入到指针本身中。 这些签名允许硬件检查指针的有效性。 在使用之前,它会先经过验证, 从而为你的代码建立起信任链。 将指针使用时的值一直追溯到过去。 恢复到其原值

    指针认证继续保护这些指针。 在也使用 Mii 的应用程序中。 记忆标记扩展。 通过透明地调整签署方式 以及适应现有标签的身份验证操作, 附加标签。

    有了这些措施,当攻击者试图破坏指针时, 签名已失效,信任链已断裂。

    现在,当你的代码稍后尝试使用该指针时, 硬件检测到此情况后, 会安全地停止您的应用程序。 攻击者已被阻止,无法执行任何恶意代码。

    在 Apple, 我们一直 在开发指针认证 API。 近十年来,我们一直将其 用作预测我们平台的工具。 在那段时间里。 现在该设计已经足够稳定和稳健,您可以采用它。 并在您的代码中采用与我们完全相同的保护措施。 为了保护我们自己的软件。

    虽然指针认证确实会导致重大变化。 为了实现代码生成,我们确保只有少数几个问题。 您的申请可能会出现差异的地方。

    那么,让我们来看看几个大问题。

    长期以来,退货地址一直是攻击者的目标。 多年来,已采取了多种多样的缓解措施。 使用指针认证来保护它们。 这又提升到了一个新的层次。 每当你拨打电话时, 您拥有一个带有指针认证的返回地址。 我们将退货地址本身、 以及有关当前呼叫框架的信息 嵌入到回邮地址中的签名。 然后,这些信息也会被使用。 在能够追踪之前,需要验证回邮地址。

    这样就从返回的那一刻起,信任链就得以维系。 地址首先被记录下来,直到它被使用为止。 甚至可以阻止攻击者重复使用有效签名的密钥。 来自先前调用的指针。

    所有这些都是基本调用约定的一部分。 对于用汇编语言编写的函数来说,它很少可见。

    现在,如果你的代码 确实会交互的话。 除了调用函数这种基本操作之外, 调用堆栈还包含其他内容。 我们也确保了所有 API 而 你用来执行此操作的编译 器特性将继续无缝协作。

    现在我们来看看你明确使用的功能。 用于应用程序的动态控制流。

    我们将从函数指针开始。 因为这些是动态控制流的最基本形式。 在您的申请中。而且还有各种各样几乎不受限制的 C 和 C + + 等语言中的一种 间接代码执行形式。 所以我们确保它们始终都有签名。

    这是C语言中一个重要的常用习语。 C+ + 会将函数指针强制 转换为整数或不透明指针。 我们也知道,这种情况并非总能避免。

    这就是为什么我们确保了指针认证。 该模型在所有这些操作中都保持了嵌入式签名。 无需更改代码。

    这意味着信任链再次得以维系。 攻击者无法修改这些指针。 即使编译器可能不再知道它们 是函数指针。

    但我们知道,从你的代码中,你并不想这样做。 所有操作都使用函数指针。 因此,您正在广泛使用语言支持的动态分发。

    这就是你在Swift虚方法中调用 非 final 方法时发生的情况。 在所有语言中,使用 C + + 或 Objective-C 发送消息。 动态调度建立在多层间接性之上。

    最简单的形式是: 每个对象实例都有一个指向某种类型信息的指针 或者方法表。

    然后,该数据结构还有另一个指针指向 具体到每种方法的实际执行。

    这种交互方式确实存在。 间接攻击对攻击者很有吸引力。 因为每一层都可以独立受到攻击。

    这就是为什么在 Swift、 C+ + 中, Objective-C 在此 链的每一步都得到了保护。

    当我们为该链中的每个指针创建签名时, 我们会包含有关物体类型、身份等信息。 或者对象的位置,甚至是目标方法的类型。

    然后,当你进行动态调用时, 每一步都使用相同的信息进行身份验证。

    通过完成所有这些工作, 我们已确保您的应用程序受到保护,而不仅仅是 不仅会受到内存损坏攻击, 还会受到生命周期和类型混淆攻击。

    但这种程度的保护是指针指向性 设计所依据的少数几个原因之一。 身份验证可能需要您更改代码。

    为了弄清原因,我们先来看看第一步身份验证。

    通过将物体位置信息融入到每个签名中, 我们已确保签名仅在此地点有效。 内存中存在阻止攻击者利用内存安全错误的机制 将一个对象复制到另一个对象上。

    但是,当您使用像 Memcpy 这样的底层函数来复制这些对象时, 这与攻击者所做的事情本质上并无不同。 结果是一样的。 当您尝试使用该对象时, 将会收到身份验证失败的错误提示。

    这是指针认证发生变化的情况之一。 防止现有代码变为未定义状态 表面上看似正常的行为, 实际上却是运行时失败的未定义行为。

    所以我们只介绍了你能获得的最高级别的保护。 采用指针认证。 它的阵列要深得多,但你永远也看不到。

    我们设计了这种实现方式。 这样它就能与您现有的代码兼容。 所以我相信您一定很兴奋 能在自己的应用程序中采用它。

    那么,让我带你了解一下你需要采取哪些步骤。

    嗯,在你开始在自己的应用程序中进行收养之前。 您需要确保所有嵌入的库都符合规范。 或链接到支持指针认证。

    如果你是这些库的作者, 那么这项工作就需要你自己完成。 但如果您使用的是外部开发的库, 您需要联系您的供应商。 让他们采用指针认证 并为您提供通用二进制文件。

    完成上述步骤后,您就可以在自己的应用 程序中开始推广工作了。

    您可以通过选择“启用增强 安全”选项来完成此操作。 在Xcode项目的构建设置中。 这将实现内存完整性强制执行和指针认证。 当你这样 做的时候, Xcode 会自动 配置您的项目构建一个包含熟悉的 arm 64 切片的通用二进制文件以及 硬件使用的额外 64 片臂。 支持指针认证。

    如果你想一次只专注于学习一件事, 您可以只专注于指针认证,只需使用 请改用“启用指针身份验证”选项。

    我们重新设计了所有指针, 我们已将所有基于指针 认证的保护措施设计为可正常工作。 使用您现有的代码, 您的大多数应用程序都将构建 在所有这些保护措施到位的情况下, 程序能够正常运行。 当然,这并不能保证什么。 因此,下一步是对您的应用程序进行全面测试。

    现在,你遇到的一些最先 出现的错误可能是由以下原因造成的: 代码中已存在的错误现在 可以通过身份验证失败来捕获。 这是预期的结果。 既然你已经了解了这些漏洞,你就能修复它们了。

    但你确实有可能写了一些代码。 以与指针认证不兼容的方式。

    对于这些,你需要做出一些改变。

    这些不兼容问题的根本原因是 当你无意中调用了未定义行为时。

    由于这些操作经常重叠, 因此被利用的漏洞也常常重叠。 攻击者会被这些保护措施阻止。

    实际上,大多数代码都不会遇到这些问题。 但让我快速介绍一下几个最常见的来源。 我们遇到的兼容性错误。

    我们看到的最常见的模式来自不安全的情况。 使用 memcpy 等函数复制多态对象。

    我们之前已经讨论过为什么使用 memcpy 或类似函数是不安全的。 当你做这件事的时候。 但你可能不会直接拨打这些电话。 代码和容器类型可能确实会执行这些操作。 而这正是我们看到这些失败的地方。 解决这些错误的方法是采用更高级别的语言特性。 以及数据结构,或者采用库函数来实现对象复制。 以及初始化,因为这些都内置于你的语言中。 他们了解你物品的类型, 他们会完成所有必要的工作,以确保语义正确。 尽可能高效地完成。

    更不常见的情况是在函数 指针的高位存储额外数据。 如果你的代码这样做,这种存储方式会破坏签名。

    要解决这个问题,你需要移动这些数据。 到指针的底部位, 或者干脆将这些数据完全移到指针之外。

    这些是我们观察到的最常见的模式。 在采用指针认证的代码中,但它们仍然非常罕见。 即使在极其庞大的代码库中也是如此。

    但是,如果您确实了解应用程序中的这些模式, 你需要将这些问题纳入你自己的收养工作中。

    现在,在完成您需要的任何更改之后 您的测试表明,您的应用程序运行正常。 你打算把它部署给你的用户。

    和其他任何版本一样, 你可能会收到有关新崩溃的报告。 你想知道这些是否是身份验证失败。

    帮助您诊断这个问题。 当你的程序终止时, 生成的崩溃日志将包含诊断消息(如果 故障可能是由于身份验证失败造成的。

    不过,如果您要自行记录崩溃日志, 您可以检查故障地址的高位。 提供类似的诊断。

    现在,仅仅知道即将发生车祸就足够了。 仅凭身份验证失败是不够的。 那么,让我们来看一个身份验证失败的例子。 这样你就可以看到它们会是什么样子, 以及如何修复它们。

    这里我们有一个非常简单的程序, 它遇到了身份验证失败的问题。

    当你在 Xcode 的调试器中发现错误时。 起初看起来和其他任何一次崩溃没什么两样。

    但由身份验证失败导致的陷阱始终会设置高位。 在故障地址中。 这就是你在这里看到的。 现在,不要过于关注正在被设定的具体存在要素, 因为不同硬件世代之间情况可能有所不同。

    在我们的小型测试程序中, 故障发生在本次调用之后。 到复制对象函数。 我们之前已经了解到,我现有的 代码可能以某种方式复制数据, 从而导致创建无效对象。 那么我们来看一下这个函数。

    不出所料,因为这是一个演示出错的场景, 该函数使用 Memcpy 复制多态对象 因为这种方法以前行得通。

    这个例子可以很清楚地说明, 如果你的代码中确实发生了这种情况, 这种情况同样可能只发生 在定制或特殊包装的容器内。 以及数据结构。

    幸运的是,编译器已经可以帮助你找到这些问题。 及早发布警告,指出这些不安全的操作。 即使未启用指针认证。

    如果你的代码库允许的话, 你应该将这些警告配置为错误。

    现在你找到问题所在了。 你需要决定如何解决 这个问题。您有很多选择。 那么让我们来看其中的几个例子。

    最简单的解决方法是移除无类型内存访问函数。 那是你的内存副本, 你的内存移动操作会复制到标准库。 提供移动、复制和初始化对象的功能。

    这些功能将确保任何必要的工作都能顺利完成。 为确保对象正确初始化,将会执行以下步骤。 如果安全的话,他们会使用 Memcpy 之类的函数。

    但既然你已经打算放弃这些底层函数, 您应该考虑直接采用更高级别的语言特性。

    例如,这些复制操作的安全等效操作就是 使用类似“放置新位置”之类的东西。

    我们见过的最棘手的案例,对你来说非常困难。 解决方法是,如果您正 在使用 Memcpy。 因为你试图保持对象的动态类型。

    解决这个问题可能需要你重构代码。 使用类似语言级多态性的技术。 例如,在这个例子中, 我们用虚拟克隆方法替换了复制方法。 或者采用手动类型感知逻辑来执行这些复制操作。 所以,这就是你要检查的类型。

    显然,这只是一个非常基础的演示程序。 向您展示身份验证失败会是什么样子。 以及你可以采取的解决方法。

    但几乎所有身份验证失败 所有语言中的规则都是以相同的方式规定的。 你需要摆脱底层操作。 而应该利用你所使用语言提供的支持。 它的运行时环境和库。

    我已经多次谈到指针认证如何保护你的代码, 我们甚至还看过一个用 C++ 编写的例子,

    但您可能认为您不需要采用指针认证。 因为你已经收养了 或者正在采用像Swift这样的安全语言。

    但这不足以保护你的代码。

    要了解原因,我们需要看看攻击者是如何行动的。 针对你的代码。

    从根本上讲,攻击者需要两样 东西才能控制你的应用程序。 首先,他们需要一个可以用来破坏 你的应用程序状态的漏洞。

    在这个例子中,使用不安全的 scanf 函数会使攻击者有机可乘。 导致结果缓冲区溢出。

    其次,他们需要能够处理 这种损坏状态结果的代码。

    他们可以利用缓冲区溢出。 覆盖错误处理函数的内容 在调用函数中的错误处理程序时。

    当该错误处理程序被 调用时,它会执行攻击者选择的指令。 他们现在已经接管了您应用程序的控制流程。 并且能够执行他们想要的任何代码。

    所以,当你想到软件漏洞利用时, 你需要考虑的不仅仅是最初的那个漏洞。 你需要考虑攻击者试图利用这个漏洞做什么。 我们现在就来看看这个来电者。 这是一段你以前见过很多次的、 不安全的 C 或 C+ + 代码。 但用安全的语言来说,它是什么样的呢? 我已经用Swift重写了这个函数。 它与 C 版本几乎完全相同。 事实上,正是利用了 相同的漏洞来覆盖错误处理程序。 C 函数中的相应代码也 可以在 Swift 版本中替换。

    我们再次使用非常简单的例子,以便您能够理解。 但无论使用哪种语言,结果都是一样的。 无论最初的错误是什么, 无论利用它有多么复杂。 那是因为当用户运行你的应用时, 他们不仅仅是在运行你自己的代码, 它们正在运行来自所有库的代码 与你的代码同时运行的程序。 所以无论你使用哪种语言, 你需要保护你的代码免受 任何可能存在的漏洞的影响。 在您的进程中运行的任何不安全代码中。

    通过采用指针认证,你就实现了这一点。 你大大增加了攻击者入侵你的应用程序的难度。 即使是那些能够做到的人 规避其他保护措施, 例如内存完整性保护和强制执行。

    而且,您几乎无需对现有代码 进行任何更改即可获得这种保护。

    这就是使用指针认证的方法。 防止攻击者劫持您的应用程序。

    最后,我要感谢恩里科、菲利波两位的到来。 以及我自己,为这篇关于记忆、 完整性、执行力等方面的概述…… 指针认证可以通过硬件实现来保护您的用户, 编译器、操作系统和你的应用程序协同工作。

    我相信你会发现收养 使用这些工具既实用又方便, 而且几乎不需要任何操作。 代码变更,对完整性大有裨益。 以及您应用的整体安全性。

    谢谢。现在让我们把话题转回库尔特。

    谢谢奥利弗、菲利波和恩里科。 真是太棒了。 接下来,开发者中心的各位将迎来…… 我们已安排在休息区享用午餐。 太平洋时间 145, 请继续收看我们的三场精彩演讲。

    希望您或在线的朋友们午餐愉快。 希望您早餐、午餐、晚餐或下午茶过得愉快。 一切以合适为准。

    在开始下午的会谈之前, 我想提醒大家,工程团队仍然 在线回答问题。

    下午的节目开始了。 可以分享几种编程语言。 C 和 C++ 中采用边界安全性的实用指南。 欢迎猫头鹰。

    谢谢。谢谢。

    大家好,我是安全工具团队的成员, 我的同事路易斯也会和我们一起。 今天的课程主题是捆绑安全。 一项消除一整类内存安全性的技术 C 和 C++ 代码中的漏洞。

    本次课程内容如下。 首先,从实际存在的脆弱性出发, 探讨债券安全为何如此重要。

    那么它是如何运作的呢? 为什么你应该采用债券安全策略?

    以及如何在实践中应用它。

    我还会谈到为构建更广泛的债券 安全生态系统所做的努力。

    最后,我们来看看 C++ 的方法。

    以下是一个真实的例子。 最近,一个广泛使用的音频库遭遇了零点击漏洞。 零点击意味着攻击者可以静默执行任意操作。 无需用户操作即可执行代码。 在大多数平台上,这使得攻击 者能够悄无声息地控制设备。 但在 Apple 平台上, 该漏洞利用程序 根本无法运行因为债券 安全技术造就了这一类漏洞。 一个可被利用的漏洞。 而今天,你将获得将同样的防护 机制构建到你的应用程序中的工具。

    内存安全仍然是首要的安全挑战。 系统编程。 你可能已经在努力寻找了。 并使用静态分析、 代码清理器和代码审查来修复错误。 但问题在于,攻击者总能找到新的漏洞。

    漏洞查找工具很有价值, 但仅凭这些还不足以真正改变攻击者的模式。 需要消除整类漏洞,这样即使即使存在漏洞, 也无法利用该漏洞。

    内存安全的完整解决方案是 使用像Swift这样的内存安全语言, 对于新代码来说,这绝对是正确的选择。

    但现有的代码库情况就不同了。 有时数亿行C语言代码 完全重写 C++ 代码需要数十年时间。

    但现在人们需要保护。

    一条切实可行的前进道路。 现有代码至关重要。

    我们需要的是能够彻底消除所有类型漏洞的技术。 不仅仅是找出个别漏洞。

    这些技术必须比完全重写代码更容易采用。

    至关重要的是,它们需要具备逐步适应的能力。 所以您可以立即开始保护您的代码,无需等待 用于大规模迁移项目。

    今天早些时候,马克对五种不同的 …… 进行了精彩的概述。 内存安全漏洞的分类。 这些都是重要的问题, 但每个问题都需要不同的解决方案。

    本次演讲的重点是平衡安全。 这是两大问题之一,另一个是终身安全问题。

    还记得之前提到的音频编解码器漏洞吗? 那是一个安全问题。 这大约占所有内存安全问题的一半。

    这意味着要解决平衡问题。 安全问题将使一半的内存安全问题消失。 我将向您展示如何使用 Apple 的……来保护您的应用程序。 为SI找到了安全技术。

    想象一下,你正在为你的应用构建一个图像滤镜。

    它需要一个缓冲区、 一个大小参数,然后遍历像素。

    但请注意这里的循环条件。 它使用的尺寸小于或等于某个特定值。 引入一个经典的例子:在最后一次迭代中, 由于一个错误而失败。 它会将一个元素写入缓冲区末尾之外。

    虽然这是一个简化的例子, 它代表了越界错误的确切类型, 当与攻击者控制输入结合使用时, 成为无数现实世界犯罪活动的根源。

    以下是防弹安全扩展装置的工作原理 C 语言通过弹跳安全性解决了这个问题。 建立索引需要退信信息。 编译器不允许你在不知道指针 反弹点的情况下访问该指针。

    请查看此处的错误信息。 编译器提示该图像缺少反弹信息。 所以你不能用非零索引来访问它。

    此错误会引导您找到必要的反弹注释。 告诉编译器图像指向多少个元素。

    这就是按大小计数的原因, 告诉编译器,该指针指向 size 个元素。

    现在编译器知道了边界。

    它会在每次内存访问之前自动插入边界检查。

    如果边界安全漏洞触发了程序陷阱 在写出超出范围的内容之前。

    代码中仍然存在该漏洞,但已无法利用。

    C程序使用指针的方式各不相同, 因此,有界安全扩展提供了一组界限。 注解。让我向您展示一种常见的指针模式和 具体应该针对每种情况使用哪种注解?

    我先从指向单个元素的指针开始。

    在 C 和 C++ 中检查绑定安全箱时。 这其中存在明显的规律。 大多数问题都源于数组索引和指针运算。

    在这个图中,有一个包含四个整数的数组。 绿色方框表示有效元素索引为0 到3 的元素。 红色方框区域超出边界。 记忆。 获取指向零点数组的指针 第一个元素。这样是安全的。

    但是使用数组索引或指针运算时, 很容易就能超出数组边界并访问红色区域。

    有趣的是, C 语言中的大多数 指针实际上并不需要指针运算。 它们只是指向像这样的单个对象 t。

    像操作数组那样对其进行 递增或索引操作会导致越界。 既不安全也没必要。

    你需要做的就是取消引用。

    使用箭头键访问成员 或者使用星号运算符直接解引用。

    而这正是这条注释所要表达的内容。

    它表示此指针指向一个元素或为空(单)。 编译器会阻止指针运算和数组索引。 你不可能意外地将指针移动到该单个对象之外。

    因为这些错误是在编译时发现的。 默认情况下是安全的。

    好的,所以单指针模式对大 多数指针来说都很好用。 那些只指向单个对象的指针。 但对于指向多个元素的指针,你确实需要索引。 进行指针运算时,需要反弹信息。 你需要知道你可以安全地访问多少个元素。 有效范围是多少?

    反弹信息总得有个来源吧。 幸运的是,大多数代码已经具备了这种功能。

    考虑过程图像。 指针与其大小一起传递。

    或者考虑消息 T。数据指针就在数据大小旁边。

    这种模式在 C 函数的指针 路径大小中随处可见。

    击发。将它们并排存放。

    信息已经存在了。 它只需要一种明确表达的方式。

    这就是统计依据所在 它允许你声明这些指针的边界已确定。 通过这个其他变量。 你这是在明确表达。 代码中已存在的注解是最常见的注解。 但还有其他方法可以捕捉不同的边界模式。

    我再举一个稍作修改的例子。 假设该函数现在接受一个 void 指针。 处理不透明的序列化数据。 类型未知,也不知道有多少字节可用。

    结构体中也存在同样的问题。 现在想象一下,它接收一个序列 化数据,这是一个空指针。 结构未知,但数据大小跟踪它包含多少字节。

    这里需要使用按标注调整大小的功能。

    它不是像 counted by 那样 统计元素数量,而是统计字节数量。

    现在来看另一个例子。 allocate pixels 函数 将图像分配为像素数组。 它要么返回指向 n 个像素元素的指针。 如果分配失败,则返回 null。

    使用 n 个元素进行计数 除非 n 为零,否则返回类型将不正确。 因为当它返回 null 时, 指针并不指向 n 个元素。 它什么也没指向。

    这里可以用 counted by 或 no。 它说这要么指向 N 个元素,要么就不是。 当指针可能为空而计数不为零时,请使用此注解。

    我涵盖了最常见的问题。 但还有其他家长需要注意的地方,比如尺码五。 或者 null 和 Devi 等人。

    您可以在绑定安全扩展的文档中找到完整列表。

    现在您了解了绑定安全扩展的工作原理, 以下是您应该采纳它的原因。

    它具有两个优点:安全保障强、易于采用。

    首先,强大的安全保证意味着编译器总是 插入必要的余额检查。

    多年来,开发商一直依赖于在像 Unison 和 Fortify Source 这样的机会主义余额检查工具上。

    没错。这些工具很有价值。 但他们只能尽力而为。 这就像玩一场永无止境的打地鼠游戏。 找出我们遗漏的地方。

    弹跳安全彻底改变了比赛规则。 它为您提供有保障的支票账户。 编译器就像你的安全网, 确保退信信息始终存在且始终正确。 这样就从结构上消除了这类漏洞。

    首先,编译器确保反弹信息始终可用, 因此,尝试对不带反弹的指针 进行索引会导致编译器错误。

    在第一个函数中。 图像没有反弹符号。

    因此编译器拒绝了数组访问

    第二个函数通过计数来提供反弹。 所以代码可以编译,编译器会插入跳票检查。

    这意味着如果代码能够编译,就会检查边界。

    没错。这是我最喜欢的功能之一。 你更新缓冲区时忘记更新其大小的情况有多少次? 通过此扩展,编译器实际上可以理解这种关系。 在你的指针和大小变量之间, 并在编译时和运行时强制执行, 这意味着你不会意外越界。 正确性。

    以上面的例子为例。 数据指针与下方的数据大小相关联。 该函数为数据分配了内存,但忘记更新数据大小。 这种经典的错误通常会导致过时的尺寸值领先。 出现越界错误。

    但现在编译器立即检测 到了这个问题,并拒绝编译。

    要解决这个问题,只需更新指针旁边的尺寸即可。

    除了编译时检查之外。 编译器还会插入运行时检查

    因为尺寸字段被硬编码为 100。这里, 编译器会自动确保 100 实际上可以放入其中 最初的分配方案。

    有了这些安全保障, 采用绑定安全扩展是切实可行的。

    首先,大多数指针不需要注解。 智能默认设置可自动处理常见情况。

    您可以逐步进行调整, 同时保持与Abi的兼容性。 您可以一次转换一个文件,而不会破坏兼容性。 与你的代码库的其他部分一起。

    我先从智能默认设置开始。 回到消息处理的例子,该函数 接受一个指向单个消息对象t 的指针, 加上“single”一词来表达这个意思。

    现在,因为这种模式非常普遍, 绑定安全扩展使单个默认值 用于 Abi 边界的指针。 函数参数。结构体字段、全局变量和嵌套指针。

    所以你不需要给这个参数添加注释。 它默认已经是单身了。

    只有当你想要做一些不同的事情时, 比如按某种方式计数,才需要添加注释。

    你现在可能在想,嘿, 我真的需要给每个局部指针都添加注解吗? 在我数百万行代码中? 答案绝对是否定的 因为局部变量没有暴露在 Abi 边界处。 编译器做了一件非常精彩的事情。 它会在后台自动将它们提升为宽指针。 你可以使用完整的指针运算功能, 还可以进行自动运行时检查。 而且无需在本地代码中添加注解, 即可获得所有这些安全性。 它就是管用。

    举个例子。处理图像函数使用局部变量。

    跟踪当前位置并标记数组的末尾。 两者都是自动宽指针。 无需注释。

    当您递增 PTR 时, 编译器会将边界值一起传递下去。

    当你取消引用时。 TR 会自动进行所有安全检查,无需添加注释。

    而采用这种方法最实际的理由是:

    它旨在逐步应用于现实世界。 暂停开发一夜之间重写庞大的 SQL 数据库是不切实际的。

    因为这些注解不会改变指针。 在 Abi 边界处的表现。 无需完全重写。 您可以携带一份高度安全的关键文件, 立即启用债券安全保障。 并将其链接回现有的未注释项目。

    在某些实际场景中, 未注解的 API 将会非常有用。 默认情况下需要系统头文件。 来自这些头文件的指针被视为不安全。 无退信信息。 不,没有跳票支票。 编译器无法验证它们。

    但这使得防反弹代码仍然可以编译。 并能与旧版库互操作。

    举个例子。打开文件句柄会调用 apply open 来获取文件指针。

    因为文件来自带注释的系统头, 编译器会将其视为不安全指针, 并将其赋值给安全变量。 保存 f,

    需要进行显式转换。

    为此,请使用不安全模式进行单个宏操作。 它接受基本指针类型和不安全指针。 这说明我了解这些要点。 这指向一个单独的文件对象,我对此负责。

    目标是逐步审核并移除这些不安全的结构。 上游已添加了适当的注释。

    好了,理论部分就到此为止吧? 现在到了有趣的部分。 以下是如何在您的项目中 应用边界安全的具体方法。

    具体步骤如下。首先,对头部进行注释。 然后逐个处理你的文件。 启用文件绑定安全。 进行修改、测试和调试。 完成所有文件后, 在 Xcode 中全局启用绑定安全。

    我会一步一步地讲解。

    首先,调整 API 合约定义 处的标头中的反弹注解。

    首先来看这个标题。 包括 Peter 勾选点 h。 这提供了反弹安全注释和宏。

    然后用 Peter。 看看 Abi 或者其他单身人士。 当你弯曲你的排气歧管时。 此宏可确保消费者自动将您的 Abi 指针视为单个指针。 并非不安全。

    最后,根据需要添加计数依据或其他反弹注释。

    好了。这个头部现在是防反弹的了。 如果另一个防反弹代码调用此函数, 编译器会在课程站点插入退信检查。

    接下来,选择一个源文件。 启用扩展程序。

    为此,您需要添加编译器标志 -f。 在构建阶段发现了安全隐患,从而启用了该功能。 对于您采用的源文件。

    然后编译并按照编译器诊断信息进行操作。 编译器会立即标记缺失或不匹配的注解。

    启用过程图像的防反弹保护后。 示例二显示诊断信息。

    第一条规定函数定义必须与注解匹配。 在头部声明中。

    第二条信息显示参数图像没有反弹标注。 因此,不允许对其进行索引。

    将按大小计数添加到参数中。 修复了所有诊断问题。

    现在运行测试并调试任何运行时检查。

    如果触发了陷阱, 编译器只是调用了一个错误的注解。 或者像这样,一个真正的程序 错误,只差一个错误。

    Xcode 会立即停止执行, 并准确地告诉我们发生了什么。

    以上引用范围限定。

    你把这里的逻辑改正了,代码就能正常运行了。

    一旦你通过了测试。 该文件已加密。

    您可以随着时间的推移认领剩余的文件。

    当你的整个项目准备就绪时, 在 Xcode 构建设置中启用 Bounce Safety 扩展。

    为此,请转到项目的构建设置。 找到安全部分的设置。 启用 C 语言扩展, 以实现 C 语言中的反弹安全性 是的。从那时起, 目标系统中的所有 C 代码都受到保护, 新编写的代码必须遵循安全规则。

    现在我想明确一点,这不仅仅是一个研究项目。 这一点已在大规模应用中得到验证。

    Apple 已经在使用这项 技术来保护数百万行生产代码。 它位于为 Apple 平台提供 支持的内核的网络协议栈中。 它内置了音频和图像编解码器、安全启动库, N1网络芯片的固件等等。

    目前产品正在通过高度安全、 敏感的系统运送给客户。

    Apple 依靠这项技术 每天保护数十亿用户的安全。

    现在轮到你了。好好利用这个,保护用户安全。

    当然,如果你用 C 语言编写代码, 你肯定会关心性能。 你可能想知道其中有什么猫腻。 因此,性能测量涵盖了内核的网络性能。 在分秒必争的堆栈中。 需要明确的是,在本次测试期间, 所有控制路径上的安全防护功能均已全面启动。

    启用安全约束后, 93% 的测试表明开销完全在以下范围内 测量噪声的容差。

    在少数几个差异可以实际衡量的地方, 差异也保持在 2% 以下。 归根结底,你将获得 Bound Safety 的终极目标。 具有高度实用性。

    代码库实现边界安全性是一项巨大的优势。 但当它随处可用时,它将彻底改变游戏规则。 这就是我们构建一个边界明确、 安全的生态系统的方式。

    目标很简单,就是要能够保护任何平台上的用户。 要实现这一点,不仅仅需要苹果。 目前, clang 的 Swift 分支已经开源了弹跳安全性。

    它还正在积极地向上游合并到 llvM 主线, 以便所有人都能使用它。

    但这还远不止于此。

    Apple正与 C 标准委员会直接合作。 将键安全性标准化到 C 语言本身中。

    最终,这将为所有C语言开发者带来安全保障。 无论他们为哪个平台开发。

    好的,现在我把话筒交给路易斯, 让他来谈谈 C+ + 方法。

    谢谢。 大家好,我叫路易斯 我在 Apple 从事 C+ + 标准库的开发工作。 很高兴来到这里。 现在让我们深入了解一下 C++ 部分。

    C++ 与 C 不同 因为它已经提供了许多包含足够信息的抽象概念 为了保障安全。例如: 标准 span 包含一个指针和一个大小, 因此它知道哪些边界是有效的。 当被访问时。

    标准中的许多其他组成部分也是如此。 类似标准向量、标准字符串、标准数组等的库。

    然而, C+ + 标准并没有要求在这些 API 中强制执行安全性。 事实上,C++ 标准库历来都没有这样做。

    例如,代码右侧 是 C 进程的直接 C+ + 翻译。 UL 之前提出的图像功能。

    它使用标准 span 而不是原始指针。 然而,尽管使用了标准跨度, 此代码默认情况下不强制执行绑定安全性。

    Xcode现在可以更改这一点。

    这是通过一种称为 C+ + 保存缓冲 区的双管齐下的方法实现的。 首先, Xcode 提供了一些工具为了帮助你确保你的 C+ + 代码使用惯用语法访问缓冲区时, 标准库提供的抽象。

    其次,它现在也来了 带有可启用的强化型 C++ 标准库 在其众多 API 中捕获滥用行为。

    现在,为了帮助你在代码中采用更安全的抽象, Xcode现在提供诊断功能,可以标记位置 你的代码中存在一种不 符合惯例的缓冲区访问方式。 例如,这段代码是过程映像的一个版本。 使用原始指针编写的函数。 在这种情况下,新的 Xcode 诊断功能会将此代码标记出来。 因为它使用原始指针进行索引,这是不安全的。

    这样你就能找到地方了。 你的代码中缺少一些访问 缓冲区时使用的惯用结构。 并采取措施解决这个问题。

    在这种情况下,一种方法是 如果改用标准 span 格式 重写代码,错误就会消失。

    现在,为了使使用惯用抽象的代码真正更安全, Xcode 还提供了一个经过 强化的 C+ + 标准库。

    在Xcode设置中启用后, 标准库使用其已包含的现有余额信息。 检查您对某些 API 的使用情况。

    在这个例子中,索引标准跨度 启用强化功能后,将使用已有的边界信息。 确保访问权限有效。

    如果索引无效, 经过强化的标准库保证您的程序能够顺利终止。 这意味着它无法继续运行,并可能造成损坏。 或导致您的应用程序出现问题。

    值得注意的是,API的使用 方式和合约都不会改变。

    经过强化的标准库仍然只是一个符合 ISO 标准的 C+ + 库。

    启用强化库后,Abi 也不会改变。 这意味着无需更改任何代码。 利用增加的安全性。 这使得现在很容易采用加固措施。

    启用新的Xcode诊断功能和标准库强化功能。 导航至安全设置下的构建设置。 并启用 C+ + 中的“强制 使用绑定安全缓冲区”。

    在某些情况下,您可能已有 使用了不安全结构的现有代码, 但你现在还不能用现代的习语来更新它。

    仍然可以利用强化后的标准库 立即生效,且不会在代码中引入新的错误。 您可以通过导航启用强化后的标准库。 在 Apple Clang 语言 C+ + 的构建设置中 并选择启用 C++ 标准库强化。

    还需要注意的是, 这不仅仅是一个调试功能,对吧? 调试功能对于提高工作效率非常有帮助。 但这并不足以使你的代码在运行时真正更安全。 在生产设备上运行。 实际上,使用强化标准库的代码旨在交付使用。 到生产和硬化 标准库经过精心设计,使其具有价值。

    这可以确保您的代码在生产环境中更加安全。 当它在用户设备上运行并处理真实数据时, 这才是安全真正重要的地方。

    当使用强化标准库时, 许多 API 都提供了额外的安全保障。 现在,普遍的思维模式是容器访问 API 以及具有直接大小的容器修改 API 要求更加严格。 事实上,这些API已经包含了必要的信息。 为了检查它们的边界, 检测这些 API 中的滥用行为 能够提供直接的安全价值。

    例如,容器的索引运算符。 他们又恢复了老一套。 那些都硬化了。

    访问可选值也得到了加强。

    一般来说,你可以依赖任何已检查的前提条件。 需要检查符合 ISO C++ 26 标准的强化实现。

    现在也有一些情况下,标准库 默认情况下已提供检查功能。 例如,调用标准函数 当函数为空时,它会抛出异常。 强化措施不会影响这些 API。

    其他一些容易加强安全性的API, 但这样做只能提供有限的安全价值, 而且也没有加强防护。

    为了最大限度地减少加固 措施对应用程序性能的影响, 优先考虑那些真正能提供安全价值的检查。

    例如,从空指针构造字符串在技术上是 不正确。然而,在实践中, 这已经导致程序出现段错误并终止运行。 因此,增加显式检查的安全价值有限。 就这种情况而言。

    最后,迭代器是强化的一个显著例外。 迭代器通常不会存储足够的信息 执行边界检查。 添加该信息需要更改 Abi, 这将使收养变得更加困难。 因此,迭代器访问没有得到强化。

    既然您已经对哪些 API 经过加固有了充分的了解, 我将讨论您可以期待的体验。 在开发强化型应用程序时。

    因此,当安全加固检查失败时, 程序将在 Xcode 内部终止。 你将被带到强化断言失败的地方, 它位于标准库中, 然后您可以使用左侧的堆栈帧选择器进行导航。 用你自己的代码。

    调试控制台将显示一条错误消息,提供以下信息: 关于此次失败的更多背景信息。 然后,您可以使用 Xcode 中 常用的调试工作流程来了解情况。 并解决问题。

    在物流应用中,情况则略有不同。 由于没有调试器可以附加,

    程序将被陷阱终止。 就像 C 语言中边界安全扩展在 C+ + 中发生的情况一样。 就像在 C 语言中一样,这种机制是最快的。 终止课程的最安全方法 因为它给潜在的恶意攻击者提供的选择很少。 在断言失败后,改变程序的控制流。

    具体来说,这意味着不会向控制台记录任何消息。 这样可以防止程序信息泄露。 它还能确保二进制文件保持尽可能小的大小。 尽可能不在可执行文件中存储诊断字符串。

    但这还不是全部。 Xcode甚至还提供了额外的检查模式, 它们在性能上各有优劣。

    我之前提到的强化模式是实际上, 它被称为快速模式。 除了快速模式之外, Xcode 还提供了一种扩展模式, 可以添加一些开销很小的检查。 但并非一定是安全关键型的。

    这种模式非常适合确保严谨的编程,而无需 付出过高的绩效成本。

    除了扩展模式之外, Xcode 还提供了调试模式。 调试模式在交易所执行更全面的检查。 为了获得更大的性能提升。

    例如,在调试模式下, C++库可以检查提供的比较器。 标准排序具备排序所需的属性。 这对于发现应用程序中 细微的语义错误非常有帮助。

    现在,快速模式和扩展 模式都可以在生产中使用了。 调试模式应该只在开发过程中使用。

    也可以在不同的文件中混合使用不同的模式。 这可用于在代码库中逐步采用加固措施。 或者用于精确的性能调整。 例如,您可以启用广泛的功能。 或者快速检查整个代码库, 但禁用强化 在一个对性能要求极高的源文件中。

    由于强化不会影响 Abi, 因此这一切都能无缝运行。

    我非常高兴这些功能现在正式上线了。 在 Xcode 中。事实上, Apple 一直在开发这项技术。 已经持续相当长一段时间了, 并且内部已经采纳了这种方法。 到今天这个程度。

    具体来说,这项技术被 WebKit 全系列产品所采用。 并且存在于操作系统的多个部分, 包括内核的部分内容。

    未观察到明显的性能影响,但发现了多个漏洞。 其中包括一些非常难以捉摸的。

    除了苹果公司自身的经验之外, 业内已有许多案例表明这项技术得到了广泛应用。

    例如,谷歌记录了他们 在整个公司范围内的部署情况。 服务器集群。这对应于 数百万行对性能要求极高的代码。

    经过一些调整后, 他们观察到性能影响低至 0.3%。 他们报告称发现了 1000 多个漏洞, 其中包括安全关键漏洞。

    另一个行业采用的例子是 C++ 26, 它采用了基于快速模式的强化标准库的概念。

    总的来说,这次经历非常积极。 我非常高兴这项功能现在 能在 Xcode 中全面推出。

    好的,我们已经了解了 Xcode 现在提供的工具。 帮助您消除 C 和 C+ + 中的重要漏洞类别。 这意味着您现在可以开始采用绑定安全扩展了。 在你的 C 代码中,逐个文件地进行修改。 你应该先从代码中最敏感的安全部分入手。 然后,当你准备好时,再逐步完成其余的代码。 所以,请在整个项目的 C++ 代码中启用它。 立即启用快速模式 无需更改代码即可提高应用程序的安全性。

    在测试和开发过程中,您还应该启用调试模式。 这将发现一些你原本会错过的细微漏洞, 从而帮助你发现问题所在。在你的代码中, 你访问缓冲区时没有使用标准库的惯用抽象。 启用新的Xcode诊断功能。 如果你想了解更多相关信息,你将…… 我今天做了报告,网上有详细的资料可供查阅。

    记住,人们依赖你的代码是安全的, 每一步都至关重要。 所以,今天就开始体验这些功能吧! 就这样,我把它还给库尔特。

    谢谢你,路易斯。现在我们休息一会儿。 在太平洋时间 2:55 继续我们 当天的最后一次谈话之前。 谢谢。

    欢迎回来。 接下来,我们不再 讨论C 和 C+ +了。 欢迎 Doug 为我们讲解如何在 Swift 中编写安全敏感代码。 道格。

    谢谢。

    我是Swift语言团队的Doug, 我和我的同事 Felix 一起来 谈谈内存、安全和 Swift。

    因此,内存安全是一项重大的安全挑战。 现在。其他报告也讨论了 有助于缓解这种情况的措施。 查找C和C++代码库中的内存安全漏洞, 或者防止它们演变成安全问题。 但这些都不够全面。 或者仅仅定义一种内存安全问题的方法就足够了。 在编程语言本身中。 因此,我们将首先回顾一下Swift的设计。 涉及内存安全问题。

    接下来我们要讨论性能问题。 以及如何使用最近推出的Swift功能 如何在不依赖底层代码的情况下获得最佳性能 以安全为代价。

    现在,向内存安全语言的过渡正在进行中 需要很长时间。 所以接下来我们将讨论 封装不安全代码的最佳实践。 以及如何从 C 系列语言逐步 迁移到 Swift。

    首先,内存安全并非一个笼统的概念。 关于如何防止所有错误。 因此,良好的语言设计可以定义某些类型的错误。 或者让它们更难在你的代码中引入。 但现实情况是,编程错误确实会发生。 所以内存安全就是确保程序 运行方式与编写的预期一致。 这听起来确实很荒谬。 当然,程序运行符合预期。 但正如我们之前讨论过的, 当攻击者利用内存安全漏洞时, 他们可以强制程序执行代码中未编写的操作。 这就是为什么内存安全是 安全问题中如此重要的一部分。 因为任何地方都存在内存安全漏洞,比如, 你的C 和 C++ 代码可以让程序做几乎任何事情。

    现在,内存安全语言 可以防止这种荒谬的情况发生。 现在它可以通过两种方式做到这一点, 因此它可以拒绝编译任何 可能包含内存信息的代码。 安全问题,或者可以在运行时插入检查来检测 当事情出错时。 你可以将这些策略结合起来,事实上, Swift 混合使用了 编译时和编译时技术。 还有运行时检查,我稍后会谈到。 这里只有一条规则, 也就是说,如果出现潜在的内存安全问题, 该计划必须停止。 试图在已损坏的状态下继续运行, 正是编程的弊端所在。 错误会成为内存安全漏洞。

    我们之前讨论过这些, 我们经常会分解内存安全。 将其分解为编程语言需要解决的五个不同维度。 所以这些都是安全保障。 生命周期安全类型安全初始化安全和螺纹安全。 C 语言家族无法为这些轴 中的任何一个提供安全性。 虽然有一些缓解措施可以帮助解决其中一些问题。 但让我们深入了解一下 Swift 以及它是如何处理这些不同领域的。

    安全问题是最容易解决的。 这是为了检查是否访问了内存块。 不要超出那块内存区域。 正如我们在 C 语言中看到的, 你可以将指针调整到已分配内存的末尾之外。 阻塞程序,然后去读取或修改其他不相关的值, 导致内存安全问题。

    这里的Swift代码试图创建同样的漏洞。 有一个包含 12 个元素的数组。 我们尝试获取第15个元素。 Swift将进行运行时边界检查 为了确保这一点能立即捕获类似情况。 就像我们之前讨论过的关于安全保障的问题一样。 这题很简单。后面的题更有意思。 因此,终身保障就是确保这一点的。 当你访问内存时,该内存仍然有效。 现在用 C 语言编写违反生命 周期安全性的代码相当容易。 类似免费使用后再次使用之类的机制, 该代码释放了一些已分配的内存 然后尝试通过这个失效的指针访问它, 现在它可能指的是完全不同的事物。

    Swift 通过取消免费部分来消除 免费使用后出现的问题。 所以,如果你这里有一些代码, 你可以分配一个我的类的实例。 把它放到一个局部变量里。 你可以用它来调用它的方法。 请注意,世上没有免费的午餐。 相反,编译器将插入析构函数。 当该实例不再使用时, 编译器总是会以正确的方式执行操作,对吗?

    Swift 集合类也使用了 相同的自动生命周期管理机制。 例如数组和字典。 现在,数组是在第一行创建的。 一旦不再使用,它​​将被自动取消分配。

    Swift使用了一种称为自动引用计数的技术。 因此,我们选择了这种方法, 而不是其他更传​​统的垃圾收集技术。 因为它在工程设计上取得了非常好的平衡。 当您拥有自动参考计数功能时, 你基本上可以编程 在Swift中,无需考虑内存管理。 所有模式似乎都有效。 而且你大多数时候都没有考虑寿命。 另一方面,还有自动参考计数。 速度非常快,开销很低。 无论从性能还是内存使用方面来看, 而且它没有你可能会看到的那种停顿。 采用更传统的垃圾收集方式。

    然而,有时当 您需要确保运行时开销为零 时保障终身安全。 因此, Swift也提供了不可复制的类型。 这些描述了对特定自有资源的拥有权。 这里存在一种权衡取舍。 也就是说,不兼容的类型具有更严格的编程模型。 你必须开始更多地考虑所有权问题。 但这样一来,你的开销就为零。 我们稍后会在讨论中再谈到不可复制的类型。

    类型安全可以防止再次以错误类型访问内存。 C 语言让你能够轻松地创建类型安全问题。 以几种不同的方式。 假设我们将一个整数写入内存中的这个单元格。 之后,我们可以通过工会成员或者 使用明确的演员阵容来解决这个问题。 并将其作为文件读取,导致类型混淆。

    Swift 提供了与 C 语言联合体 和类型转换等特性等效的功能, 但它们在结构设计上都考虑到了安全性。 Swift枚举具有区分性。 受歧视的工会 这意味着它们对当前处于 激活状态的选项进行了编码。 这里是资源枚举。 它可以存储文件 或者它可以存储一个整数标识符来访问资源。 你可以使用switch语句进行模式匹配, 这确保您永远不会访问错误的成员,因为这些是。 这是配对访问。

    斯威夫特的刻板印象也类似。 所以这里的 Swift 代码 定义了一个类和一个子类。 该子类重写了原有方法, 并引入了自己的其他方法。 非常经典的面向对象编程。 接下来我们要尝试一下向下抛投。 现在,这里的 as 问题会将给定的对象 向下转型为我的子类。 它产生一个可选值 其中要么包含作为我的子类的实例, 如果向下转型失败,它将为空。 所以如果它是子类的一个实例, 代码将在主体内部运行。 如果可以的话,我们可以调用另一种安全的方法。 如果向下转型失败,则可选值为空。 if 语句的主体部分不会被执行。 这样就消除了铸造过程中 可能存在的记忆安全漏洞。 同时还能帮助避免因类型 转换错误而导致的编程错误。

    初始化安全性是另一个相当直接的问题, 所以,这就是防止内存被读取 而没有被重新初始化的原因。 C 语言不需要它。 所以你可以构造一个局部变量 然后直接在初始化之前使用它。如果你尝试 用 Swift 做同样的事情, Swift编译器将拒绝此尝试。 因此,它指出您尚未赋值或初始化该变量。 并在编译时拒绝它。 所以不可能存在内存安全问题。

    最后也是最棘手的一点是螺纹安全。 因此,违反线程安全意味着 在任何地方发生数据竞争。 你的程序中可能引入内存安全问题。 即使这样,这种情况也可能发生。 你的语言在其他所有方面都是安全的。

    所以在这个例子中,这是一个共享的可变资源。

    函数 replace resource 将会改变该资源的值。 包括将其类型设置为此标识符。

    想象一下,这一切都发生在同一个线程中。

    现在在另一个帖子里, 有一个使用资源的功能正在切换 在同一共享资源上。 如果这里有一场数据竞赛, 这可能意味着某个线程将其视为一个文件。 与此同时,另一个线程正在覆盖该整数。 用整数覆盖实际的文件实例。 Swift 6 并发模型通过 以下方式防止了这些数据竞争: 确保不会同时访问共享的可变状态。 此外, Swift 提供安全的处理方式 具有不会在高层引入数据竞争的并发性。 其中之一就是演员。 因此,演员是概括其状态的类型。 并防止并发修改。 这里的资源变量现在是角色状态的一部分。 因此, Swift 确保资源的使用。 替换任何可能影响该 状态的资源将永远不会并发运行。

    这是通过 Swift 的 async/await 模型来强制执行的。 因此,对 Actor 上的方法 调用始终是异步的。 因为调用者可能需要等到执行者完成代码执行。 在其他帖子里讨论过,等时机成熟后再进行讨论。

    Swift 还提供了用于 数据竞争安全的底层原语。 这里的互斥锁类型描述的是只能被访问的内容。 一次只能处理一个线程。

    对存储数据的每一次访问 互斥锁是通过这个宽度 锁定函数实现的,该函数提供 临时访问该数据。 在闭包内部,互斥锁确保只有 单个线程能够执行操作。 同时进行关闭操作。

    因此, Swift Memory 将安全性融入了语言的设计之中。 这里有一个很难实现的益处。 只有亲身经历过才能体会。 因为当你使用内存安全的语言时, 不仅仅是不用担心什么吗? 我是在引入内存安全漏洞 还是在寻找内存安全漏洞? 你干脆完全不再去想这件事了。 这样你就可以把所有注意力 都集中在代码的正确性上。 以及其他与内存安全无关的问题。

    如果你之前接触过 C 语言家族, 你可能想知道这种内存安全是否…… 以牺牲性能为代价。

    所以从宏观上看, Swift 是一种原生编译语言。 它为其内存安全模型提供了高效的实现方式。 对很多项目来说,这就足够了。

    但对于非常底层的代码来说,它有 为了匹配不安全的 C Swift 62 的性能,我们引入了 这里提供一些安全的抽象概念来提供帮助。

    我们来看一个 C 语言的例子。 这是一个解码器。 对于使用游程编码的图像。 所以在循环中,它每次 从输入缓冲区读取四个字节。 这是计数和像素数据。 然后,它将结果通过输出缓冲区写入。 在此过程中,可以看到它正在手动执行边界检查。 这段C代码中还有很多不成立的假设。 实际上已经验证过了。所以我们假设计数参数 正确描述相应缓冲区的大小。 我们假设输入缓冲区和输出缓冲区 不会以某种奇怪的方式产生混淆。 而且不会被其他线程修改或释放。 程序运行时。

    以下是同一段代码的Swift译文。 因此,输入缓冲区是一个数组, 它封装了数据和计数。 这样你就能知道了。 而且它还能确保数据不会丢失。 或者,即使在多线程程序中, 该函数运行时也可以对其进行修改。

    现在数组访问会进行边界检查。 因此,错误将被 捕获。在运行时而不是成为内存安全漏洞。 但我说过我会谈谈表演。 那么我们来看看。 因此,在循环中进行正确的边界检查后, 编译器通常可以完全优化掉边界检查。 只有在证明这样做是安全的之后。

    现在 Swift 的写时 复制数组使用引用计数。 在他们的实施过程中。 同样,编译器通常可以优化 掉所有引用计数相关的操作。 在这个例子中,它确实做到了。 但是,此追加操作会将一个新元素添加到数组中。 如果数组中已经没有足够的空间, 它将需要分配新的内存,以容纳更多存储空间。 然后将所有现有元素复制到其中。 这比我们之前使用的 C 语言实现要慢。 它只是通过指针输出像素而已。

    这里还有第二个问题, 也就是说,这种设计迫使客户端自己拥有数组。 我们可以要求他们提供自己的副本。 举个例子。我们有一个数组,里面有一些数据, 但它有一个简单的标题,包含一个宽度 以及实际输出图像的高度, 接下来是我们实际想要解码的图像数据。 现在,主要问题是我们需要 复制所有这些游程编码像素数据, 只是为了调用我们一直在开发的解码函数。 因为我们的解码过程需要读取整个像素数组, 那需要额外分配堆内存, 并且需要复制所有图像数据。 这是我们绝对不希望看到的。

    这里还有一个相关问题, 也就是说,我们的解码过程会分配结果数组。 一直都在堆里。 现在,这种特定的图像解码操作或许可以接受。 但可能还有其他来电者希望你放置这些像素。 在某个特定的地方, 或许会写入一个已经分配 好的固定大小的帧缓冲区。 而要做到这一点,你必须再次复制结果。 所以这类问题在基层经常会出现。 对性能要求极高的代码库, 你可以使用数组切片或泛型 集合来部分解决这个问题, 但这些东西可能很难处理。

    所以,这就是新的 span 系列类型的用武之地了。 因此,跨度提供了对连续 内存的安全、低开销访问。 如果你在 Swift 中使用了 不安全的缓冲区指针类型, 你可以把跨度看作是它们的安全对应物。

    span 的核心思想是它引用连续的元素。 它不拥有的记忆。 你可以把它想象成一个指针加上长度, 因为这就是它在记忆中的表示方式。 现在,Span 提供了全面的内存安全保障。 它以编译器检查的方式提供终身安全保障。 完全没有运行时开销。 它通过对所有访问进行边界检查来提供边界安全。 但最重要的是,它并不拥有它所引用的存储空间。 相反,它可以与各种拥有 存储设备的类型进行互操作。 你可以获取一个指向写时 复制数组的 span 引用 或者使用固定长度的行内数组。 它还可以与不安全的指针类型进行交互, 这在与不安全语言交互时非常重要 或者早于 span 的代码。

    span 类型族是在 Swift 6.2 中引入的。然而, 我们认为跨度非常重要, 因此我们重新部署了这些类型。 更早版本的苹果操作系统。 这样,你现在就可以收养它们,而无需…… 提高部署目标。

    好了,是时候采用 span 作 为游程解码器的输入了。

    这并不需要太多东西。 我们刚刚将参数类型从数组更改为 span。 之所以可行,是因为 span 提供了 相同的 API。 用于访问数组元素。 因为这段代码只是在读取数据, 它并不是要修改它。 它没有返回副本。 这个函数实际上不需要做任何其他更改。 现在发生变化的是,API 合约调用者现在需要 提供一个跨度而不是数组。 那么让我们回到调用者的代码, 看看如何实现这一点。

    首先,每个数组都有一个 span 属性, 用于生成一个 span 元素。 这样我们就可以让代码像这样编译通过。 只需在末尾添加 span 元素即可。 这样做确实可行。但是,它并不会提高性能。 因为我们还在创建这个新数组, 在将 span 纳入其中之前, 先复制所有数据。 我们可以做得更好。 所以,不如这样:我们将要做的是 从原始数组中取出一个跨度。 然后只把我们关心的那部分传递下去。 解码函数。 读取代码时,只会接收到与其 相关的 span 元素。 但是没有额外的配额,也没有副本。 同时始终保持内存安全。

    这个例子确实让人觉得 span 元素可以简单地替换成其他元素。 对于数组而言,并非如此。 因此,它引用的是其他人拥有的存储空间。 因此,为了确保安全性且不增加任何运行时开销, span 类型本身有一些必要的限制。

    所以,span 就是我们所说的不可转义类型。 它是用上面所示的波浪号转义语法编写的。 您可以使用相同的语法。 定义你自己的不可转义类型, 使其行为类似于 span。 现在有一种无法避免的类型, 你可以像之前那样,把它作为参数传递给函数。

    但是,你不能从函数中 返回任何 span 元素。 因为只有在以下情况下, 你才能返回不可转义类型的值: 它的寿命与其中一个参数有关。

    让我们深入研究一下第一个运行 函数的主体部分,看看这意味着什么。 所以这段代码的作用是找到 与第一个元素匹配的一系列元素。 当循环内部发现第一个不 匹配项时,循环就会跳出。 关键不在于逻辑本身,而在于最终的回报。

    那么,我们该如何回归呢? 我们返回直接从获取的数据参数中提取的跨度。 这只是其中的相关部分。 这样没关系。这意味着给我们提供信息的来电者 使用跨度将使生成的跨度保持足够长的时间。

    然而,假设这段代码不是复制数据, 而是复制了数据呢? 将其放入一个新数组中。 该数组存储在一个局部变量中。 然后它尝试从该副本返回一个 span。 编译器在这里会产生错误。

    原因是,这个新创建的数组存储在局部变量中。 这就是它得以存活的原因。 我们不能将一个 span 对象 返回给调用者的局部变量, 因为局部变量会在我们退出程序后立即消失。 所以如果你从C的角度来考虑, 我所说的这些限制,你可能觉得似曾相识。 想象一下,你获取了一个局部变量的地址。 你必须非常小心那个指针 为了确保它不会被保存到其他地方, 然后在你的函数返回后使用它 局部变量消失了。 Swift将这些限制融入了语言本身。 这样你就不会犯下危及内存安全的错误。

    自身提供的内存只读访问权限。 还有另一种可变跨度类型,它提供修改访问权限。 这样你们既能写作也能阅读。

    您可以从任何连续的元素中 使用可变的 span 属性。 我之前提到的集合, 目的是将不可变跨度存储到其存储中, 然后像你预期的那样,使用下标修改其中的元素。

    现在,这里安全模型的一个关键部分是可变的 span需要独占底层内存。 这意味着,如果你有一个指 向某块内存的不可变跨度, 其他人都无法访问那段内存。 不适用于写作,也不适用于阅读。

    因此,在我们的代码中, 如果我们是创建第二个 span, 该 span 可以访问同一个数组 内部的内容。然后尝试进行突变, 编译器将在此处产生错误,以阻止任何访问。 在突变仍在进行时将其储存起来。

    这种排他性模式是Swift的基础。 实际上,它从一开始就存在了。 它确保了在使用诸如变异 方法之类的操作时内存安全。 以及输入/输出参数。 大多数Swift程序员甚至都不知道它的存在。 因为它非常罕见 为了应对这些会实际触发内存安全违规的情况, 但它的存在是为了提供内存安全保障。

    好了,现在是时候采用可变跨度了。 在游程解码函数中,不返回堆。 已分配数组。这个正在运行 比跨度需要多花一些功夫。 但我们还是要从函数签名开始。 关键在于,这里不是创建新的存储, 而是返回现有存储。 来电者将告诉我们地点。 将结果通过此输出参数输出。 它被传递进去是因为当你 修改一个可变 span 时, 结果会显示在那里。 请注意,这里不是将内容追加到输出数组中, 现在像素直接写入。 通过这个输出参数到达它们的最终位置。 这意味着该函数中不再进行任何内存分配。 读写缓冲区的设置完全由调用者负责。 就像我们在 C 语言中做的那样。 说到打电话的人。 所以它之前实际上依赖于 早期解码返回的是一个数组。 所以让我们回顾一下。 现在这个函数需要什么呢? 这样做的目的是分配它自己的像素数组。 然后它会将一个可变跨度传递给该数组, 供我们的 LED 代码使用。 填充生成的像素。

    当然,这个调用者选择进行堆分配。 但换个打电话的人可能会做出完全不同的选择。

    举个例子。这个结构代表的是320。 通过 240 帧缓冲区像素。 它以内联数组的形式表示,以避免堆内存分配。

    图像解码操作接受一个可变引用 再次使用 Inout 将帧 缓冲区传递给帧缓冲区。 然后它将不可变跨度扩展到这些像素中。 并将此信息传递给我们的 LED 解码器。

    看看这里发生了什么? 解码后的信号直接进入帧中。 不。无需额外复制。无需分配内存。

    采用跨度对于性能和内存 安全来说都是一项重大胜利。 我们鼓励您在Swift代码库中采用它。 如果您正在寻找可能需要采用 span 的地方, 有两个出发点。 从性能角度来看, 查找对性能要求较高的代码, 这些代码使用了数组或数据类型。 如果发现额外的副本或堆内存分配, 请改用 span 工具。 就像我们进行解码操作一样。

    从内存安全角度来看, 首先替换代码中所有不安全的缓冲区指针类型。 正如它们的名称所示, 不安全的指针类型无法维护内存安全。 应尽量少用。 Span 是大多数不安全指针 应用场景下的安全替代方案。 虽然可能需要进行一些重构才能实现。 现在我将把话筒交给我的同事菲利克斯, 让他进一步谈谈如何应对 在不影响内存安全性的前提下, 使用 Swift 中的不安全结构。

    谢谢你,道格。

    大家好,我叫Felix,来自安全工程部门。 以及架构团队。 下一个议题是安全地使用不安全的代码。

    如今,内存安全的语言代表着编程的未来。

    正如 Doug 所解释的, 通过使用像 span 这样开销很低的原始函数, 安全的代码运行速度非常快, 而且不会引入内存安全漏洞。

    与此同时,如今不安全的代码无处不在。 字面上地。

    即使安全语言在以前难以 使用的地区也开始流行起来。 之前还有数十亿行不安全代码需要处理。 这件事几十年前就众所周知了。

    不幸的是,这是基于工程学常识得出的结论。 重写代码是有风险的。 新的实现方式可能会引入或重新引入错误。 同时,现有实施方案也在不断发展演变。 由于这种实现方式不安全, 它与这种实现方式存在竞争关系。

    这就是为什么制定计划很重要的原因。 通过逐步进行小幅重写, 逐步从不安全的代码迁移过来。 风险更容易控制。 成功的几率会大大提高。

    而这一点之所以在今天至关重要, 是因为 Swift 具有独特的特性。这些功能旨 在实现从 C、 C + +的增量迁移, Objective-C 也是可行的。

    这些工具中的第一个是严格的内存安全。 没有比这更安全的工作方式了。 与其编写不安全的代码, 不如启用严格的内存安全机制。 这非常重要。

    严格的内存安全机制会揭示 所有不安全代码的使用情况。 这非常有用,因为…… 虽然 Swift 经常在不安全的事物 名称中加上“不安全”二字, 某些操作本身就存在安全隐患。

    例如,在这段代码中, 复制数组函数声明了两个数组变量 x 和 y, 它使用 memcpy 将一个文件 复制到另一个文件。

    代码并没有表明 Memcpy 会执行任何不安全的操作。 但是 Memcpy 是一个 C 函数,所以它 接受不安全指针 xy 会被 隐式转换为不安全指针。 这可能会导致内存安全漏洞,且没有明显的迹象。

    启用严格内存安全后,编译器会发出警告。 对于这种以及所有其他使用不安全代码的情况, 请参考以下说明: with expression 使用了不安全的结构, 但没有标记为 unsafe。

    这个问题可以通过在调用前面添加 unsafe 关键字来解决。

    我花点时间详细说明一下。 在编译器之外使用 unsafe 关键字。 注意,它有两个主要功能。

    首先,这是对代码库审核人员的安全警告。 “不安全”这个关键词表明 某些东西可能存在安全隐患。 危险正在发生。

    其次,也是更重要的一点, 这是在提醒您,您必须验证 编译器无法验证的内容。 核实。

    再以 Memcpy 为例, 代码是正确的,因为两个数组都包含四个字节。 但是,编译器并不知道这 是调用该函数的前提条件。 不安全关键字提醒您验证 Memcpy 是否正确使用。

    在Xcode项目中启用它。 在 Swift 语言选项中 查找严格的内存安全设置。

    在一个 Swift 包中, 通过添加以下代码启用严格的内存安全: 对软件包描述中严格的内存安全设置。

    即使已启用严格内存安全机制, 现在也依然如此。并确保不安全代码可见。 不过,最好还是完全避免使用不安全的功能。

    只有两种情况下, 使用不安全的代码才是真正必要的。 首先,要与不安全的库进行互操作。

    其次是实现安全的基本原理。 很多情况下,同一颗小小的、 不安全的宝石实际上有两个方面。

    使用安全的语言编写新代码有很多好处, 但是,不安全的实现方式仍然可能是最佳工具。 某些任务。

    这是因为不安全的库很常见, 而且撇开安全性不谈, 或许仍然是最成熟的。

    当可以用安全的语言重写不安全的库时。 那当然是更好的选择。 但并非总能在任意的时间范围内实现。 安全的重写需要时间和专业知识,直到…… 而且我们拥有相关专业技术。 Swift可以帮助确保库的安全使用。

    Doug 之前已经介绍过解码功能。 假设它已经实施 在一个不易重写的外部 C 库中。

    当 Swift 检测到该头部信息时, 它会将该函数暴露出来,如下所示: 不安全的接口。它接受相同的参数:一个源指针, 源大小、目标指针和目标计数。 而且这并不理想,因为它仍然使用了不安全的值。 但它仍然可以从Swift中调用。

    然而,如果启用严格的内存安全机制, 编译器会生成相同的不安全结构。 警告。

    最好为不安全的函数编写一个安全的包装器。 就像Doug展示的那样实施。 该包装器接受一个 span 作为输入, 一个可变 span 作为输出。

    那份包装上会多次提到“不安全”这个词。 这是因为每次从 span 中 取出指针的操作都是如此。 或者,使用这些指针的代码需要标记为不安全。 但是,进行安全审计很容易。

    这段代码会从每个 span 中 取出一个不安全的指针, 并将该值连同相应的计数一起传递给 C 实现。

    没有必要对Swift界面的用户进行审计。 因为它会传递跨度,而跨度是安全的。 这里没有错误,但如果真有错误的话, 在这个实现方案中,它将会实现。 C 语言实现中也可能存在内存安全漏洞。 然而,调用该 Swift 模块的程序被证明没有问题。 当红色代码得到安全实现时。 这种风险完全消除。

    现在,我们来看看如何实现安全的基本原理。 还可能发生其他事情 封装不安全代码时,可能会出现外部库的问题。 某种需要手动创建和销毁的资源。

    以下是解码函数。 我对其进行了修改, 使其不再使用单个无状态解码函数, 现在你需要创建一个包含 RL 的解码器对象, 然后你需要用 RL destroy 命令将其销毁。

    RL解码功能与之前基本相同。 但现在它需要解码器作为第一个参数。

    此模式需要显式的 DNN, 因为可复制结构体不能有 DNN。 这通常是通过类来实现的。 如今,很多封装不安全 资源的代码看起来都像这样。

    初始化程序调用 RL 初始化函数 然后初始化程序调用销毁函数。 解码功能将与之前基本相同。

    这种方法可行,但效率不够高。

    一等实例是动态分配的。 对于使用寿命长的物品来说,这通常不是问题。 但是反复创建和销毁实例 会导致避免对 malloc 和 free 进行不必要的调用。 而且,使用动态分配的成本更高。 封装另一个动态分配。

    二类实例采用引用计数法。 这样可以确保复杂的内存管理情况的安全。 但物品最终可能会付出代价。 即使生命周期非常简单,也能获得引用计数流量。

    最后,类实例的所有字段都 具有细粒度的互斥性检查。 在运行时,这允许比您能做的更灵活的别名操作。 关于结构体。但是,再 说一遍,操作简单的类型可能根本 无法从中受益,反而还要付出代价。

    类实例用途广泛, 但它们可能比必要的要臃肿得多。 这些都是小额的间接成本。 但是,如果与完全不进行 任何检查的不安全实现相比, 它们很快就会累积起来。

    从 Swift 6 开始, 你可以在这些用例中使用不可复制的结构体。

    结构体不需要动态分配内存。 所以这是造成运营成本下降的一个原因。 而且他们还有课程独家性审核机制, 这些机制大多是可以核实的。 在编译时。因此, 程序一开始就拥有的线程数较少。 而且它们更容易通过优化消除。

    然而,共享一个不可复制的结构 体比共享一个类要受限得多。 或者一个可复制的结构

    体,多亏了 decode one 函数中引用类型的灵活性。

    多次引用同一个解码器没有问题。 通过它们中的任何一个调用 decode 方法。

    但是,对不可复制的结构体 执行同样的操作是错误的。 编译器会立即诊断出对象 A 被移动到了对象 B 中。 然后又被再次使用。

    简而言之,对于简单的对象管理, 与类相比,不可复制的结构 体在性能方面有很多优势。 由于它们的灵活性较差, 您可能并非总能使用它们。 它更具体。但作为交换, 您将获得更可预测的性能。

    我最不想看到的 讨论的是从 C 调用Swift。

    目前所有主流平台都存在 C 或 C + + 内核不安全的问题。 所有安全语言都必须调用该核心。 不知何故,与 C 语言混合使用是 安全语言的正常且预期的功能。 然而,这往往很困难。

    难点之一在于……

    不同语言有不同的期望, 当一种语言呼应另一种语言时, 两种语言的预期都必须得到满足。 例如,在一种使用垃圾回收机制的语言中, 将对象指针传递给 C 可能需要特殊的协作。 使用垃圾回收器防止物体静止时垃圾回收器移动。 C语言的参考文献。另一个原因是大多数语言 彼此不太了解对方。 例如,在许多可以调用 C 语言的语言中, 互操作需要 glucose。 那描述的是 C 头文件 因为目标语言的编译器无法读取C语言头文件。 编写这段代码很繁琐。

    但值得庆幸的是, Swift 在设计之初就 考虑到了与不安​​全语言的互操作性。

    Swift消除了大部分此类复杂性。 通过嵌入完整的 C 编译器, 为开发者提供服务。

    它可以解析头文件中的任意声明,并使之生效。 通过解析桥接头文件中的内联函数, 可以实现对内联函数的解析。 项目可以包含来自其桥接头文件的任意头部信息,

    Swift可以提供声明。 通过生成自己的头文件来向C 编译器提供信息。

    我将 Doug 早期的解码 示例重新展示在屏幕上。 Doug 展示了使用 span 实现是最灵活的选择。 该实现被用作独特的后端 另外两个函数以完全不同的方式返回像素。 第一个返回数组的函数 第二个函数通过引用返回其输出。 进入 X 模式帧结构。

    从 Swift 6.3 开始, span 实现也 可以作为 C 函数的后端, 例如道格最初使用的那个。

    这是 Doug 介绍的 C 语言实现。 最后再仔细看一下。 因为我要把它删除, 换成一个 Swift 实现。

    为此,首先需要 是一个具有兼容原型的Swift函数 以目标 C 函数为中心。

    这里有一个现成的代码, 它接受一个输入指针、一个帐户, 输出指针、帐户、 然后,它在输入指针上创建一个跨度, 在输出指针上创建一个跨度。 它调用了通用的现成代码Swift后端。

    接下来需要将该函数暴露给 C 语言。 有两种方法可以做到这一点。

    第一种方法是在函数中添加 at c 属性。

    在 C 语言中使用时, Swift 会转换其类型。 转换为合理的对应 C 类型。 例如, Swift 的 Int32 类型转换为 int32t。 我将看到基本的整数类型 定义转换为其预期的对应类型。 参见图表开头。请看。 短到短。参见末尾到整数。 等等。请注意, int 会转换 为指针除以 t。 而且。 这是我们的代码接受的原因 实现时返回指针 div t 而不是大小 t。 当要实现的函数已经在桥接头文件中可见时。 还有第二种选择, 即使用 C 语言组合的 at 实现, 例如在使用Objective-C方法时 在实现过程中,而不是 在生成的头文件中发出声明。 这要求编译器查找已存在的声明 用于从桥接头文件解码的功能。

    使用此方法时。 编译器会验证Swift原型是否与C原型匹配。 如果它们不兼容,则会产生错误。

    当仅使用 C 语言时, Swift 必须选择参数类型。 但是当在 C 语言实现中使用时, 它能够适应其他一些转换。 例如,如果 C 原型使用​​ T 尺寸, Swift 也接受接收 int 而 不是 Uint 的实现。

    现在,这是一个回顾刚才发生的事情的好机会。 我的右边有一个名为 cats 的 C 函数。 以及能够识别图片中宠物的狗。

    它接受一个指向图像结构的指针 以及指向宠物记录缓冲区的指针。 它管理用于解压缩图像的像素内存, 它调用解码函数来解压缩图像。 最后,它还能识别猫狗,找到猫的踪迹。 照片里还有狗。

    在此之前,它会调用读取代码的 C 实现。 右边是之前狗狗们展示过的。

    但是,通过在 C 语言中实现, 无需更改任何内容。 在一位学者看来,猫 现在狗狗的功能采用了安全的红色代码实现方式。

    而且这在Swift中也能无缝运行。 因为Swift编译器能够匹配解码实现 使用与 clang 识别为红色 代码时相同的 C 函数原型。

    这就是C点所需要的一切。 而实现方面则是将 C 代码库迁移 到 Swift 的绝佳工具。 并用安全的语言编写新功能 当 C 调用者需要使用它们时。

    现在你已经了解了跨度移动 仅类型以及 C 之间的交互 对于Swift,接下来你需要这样做。 首先,请在代码中严格遵守内存安全原则。

    这将使您能够考虑到代码库中的不安全操作。 接下来,开始使用 span 安全地访问连续内存。 首先替换掉不安全缓冲区指针的使用。 然后通过封装不安全的资源来创建安全接口。 尽可能使用移动类型类,否则使用其他类型类。

    最后,开始逐步迁移你的不安全代码。 利用Swift的互操作特性。 感谢您的关注。 内存不安全是当今最大的安全漏洞来源。 我期待着与大家共同建设一个更安全的世界。

    接下来是今天的最后一个报告,一个简短的报告。 Xcode中使用消毒器的实用概述 找出代码中的错误。 请和我一起欢迎丹上台。

    大家好,我是丹, 我是Apple的一名软件工程师。 我在安全工具团队工作,负责消毒工具的开发。 我今天来这里是为了谈谈如何发现安全漏洞。 在现有代码中添加消毒器。

    本次演讲中,我将介绍消毒剂的概念。 然后我要谈谈其中两种消毒剂。 还有线材消毒剂,这两种消毒剂是最有效的两种。

    首先,我将介绍一下总体概念。

    消毒剂是发现害虫的工具。 它们会在你的代码中添加 额外的簿记和运行时检查。 由于严格的运行时检查, 它们可以帮助你发现你之前不知道存在的漏洞。 在您的客户受到影响之前。

    它们也可能非常宝贵。 用于查找那些看似不 可能发生的崩溃报告的根本原因。

    既然我已经定义了什么是消毒剂, 接下来我将谈谈地址消毒剂。 从宏观层面来看, 地址清理器会发现并抛出一个错误。 当你的程序对内存使用了无效信息时。

    与 guard malloc 等仅 检测堆内存问题的工具不同, 地址清理器还可以检测堆栈 和全局变量区域中的问题。 它还提供字节级精度的检查。 这意味着即使是最轻微的越界访问也会被发现。 我将介绍几种类型的漏洞,这些漏洞都与此有关。 消毒液可以找到。

    在这段代码片段中。 我计划将这个 Nsstring 变量 original 做成一个原始副本。 虽然可能不太明显,但它存在越界内存访问漏洞。

    我在这里解释一下原因。 我获取指向右侧所示原始底层字节的指针。

    然后我为副本分配原始点长度字节。

    事情就是从这里开始出错的。

    原始点数长度无法提供字节数。 它实际上给出了UTF-16代码单元的数量, 挥手表情符号由其中两个组成。

    因此,原始副本现在指向分配 5 个字节。

    接下来,我将原始原始 数据中的字节复制到副本中。 但最后两个字节将超出分配的范围。 这正是那种很容易被忽略的棘手漏洞。 并可能被利用。

    幸运的是,地址消毒器可以检测到 并警告我这里存在缓冲区溢出。

    地址清理器检测到的另一 种漏洞类型是释放后使用。

    在这个 C+ + 示例中, 我维护了一个标准的制表符向量。 并将指针保持在右侧当前活动的标签页上。 我可以看到向量中预留了一个元素的位置。 但它还没有人居住。

    我打开了我的第一封邮件, 并将其设置为当前活动标签页。

    然后我打开另一个标签页。 新闻。这迫使该载体扩大其容量。

    这意味着要创建一个新的内存 空间并将元素移动到那里。

    不幸的是,发生这种情况时, active 值并未更新。 现在指向已释放的内存。

    当我尝试通过活动地址重新加载标签指针时 清理程序抛出一个错误, 表明使用了已释放的内存。

    如示例所示。 Objective-C 支持 C、 C + + 和 Objective-C。 这包括手动和自动参考计数。

    最后, Swift也是如此。 虽然纯 Swift 不太可能出现 任何无效的内存使用情况。

    但值得注意的是, Address Sanitizer 也支持跨语言。 这里我有一个Swift数组numbers。

    为了在 C 函数中使用此数组, 我将使用一个不安全的缓冲区指针。 注意指针 nums 指向数组的第一个元素。

    现在我将调用求和函数。 为此,我需要传入指向数组 起始位置的指针及其长度。

    sum 函数会遍历数 组中的每个元素并将它们相加。

    但我犯了一个典型的错误。 我没有使用小于 Len 的迭代, 而是使用了小于等于。 因此,最终迭代将会超出边界。

    地址清理器会捕获到此错误, 并抛出有关缓冲区溢出的错误。 值得注意的是,这是使用某种 安全漏洞时容易遇到的那种漏洞。 C 的界限安全扩展解决了这个问题。

    以下是关于地址消毒剂你需要了解的信息。 首先,它仅用于开发过程中, 不可用于运行时加固。

    对于这样一款功能强大的工具来说。 它的内存和运行时开销都很低, 大约是其他方面的 2 到 3 倍。

    它非常擅长在测试过程中发现 并修复那些尚未被发现的漏洞。 对你的顾客来说,尽可能多地进行测试非常重要。 启用后,

    启用此功能可进行自动化测试, 例如单元测试和 UI 测试。 在开发过程中进行手动测试时启用此功能。 并充分利用该工具。 触发极端情况和罕见路径非常重要。

    在开发过程中启用地址清理器。 首先,打开产品菜单,导航至架构编辑器, 然后打开方案子菜单并选择“编辑方案”。

    从这里导航到运行方案。 然后在诊断选项卡下, 选中“地址清理器”复选框。

    为测试计划启用地址清理器。 打开产品菜单,然后选择 测试计划并编辑测试计划。

    打开配置选项卡,滚动到运行时清理部分。

    然后选择地址消毒器行并将其设置为开启。

    现在我将演示如何使用地址清理器进行测试。

    好的,这是我从混合语言 示例幻灯片中截取的代码。 我使用我的Swift数组数字。 然后我将获取指向此对象的非安全缓冲区指针。 然后在那里调用我的 C 函数 sum。 我将遍历每个元素,并将它们相加。 并将结果返回给Swift。

    然后我会把数组和结果打印出来。 我们现在就开始运行吧。

    首先,我可以看到我的程序运行成功了。

    但这个值肯定不正确。

    我现在要在产品方案编辑方案下启用地址清理器 并选中“地址消毒器”复选框。

    因此,点击运行按钮会使用清理功能重建。 我立刻就看出它缠住了什么东西。 此行出现堆缓冲区溢出错误。

    在左侧,我可以看到导致 这种情况发生的调用堆栈。 同时也能立即看到这一点 在分配 64 字节堆内存之后。

    在这个幻灯片菜单中, 我可以看到分配情况,不出所料, 那是我构建那个Swift数组的时候。

    回到堆叠框架中。 我正在进行实时调试会话 这样我就可以看到变量的当前值。 我可以看出我和 Len 是一样的, 这说明我的循环边界条件有问题。 正如我在幻灯片中讨论的那样。 那是因为这里多了一个等号。 所以,我删除了它, 然后重新构建了我的应用程序。

    现在我可以看到它运行成功了。 而且,我现在能从 sum 函数中得到正确的值了。

    好的。我现在要继续看幻灯片了。

    接下来我要介绍的是Fred消毒剂。

    Fred Sanitizer 可以检测 数据竞争和其他并发问题。

    当一个线程写入内存位置时,就会发生数据竞争。 另一个线程在没有同步的情况 下访问了同一内存位置。

    在右侧的代码示例中, 我从待处理队列中取出队列头部, 发送它,释放它,然后增加头部。 这样做原本没问题, 直到另一个线程也在做同样的事情。 现在我有两个工作线程。 这里可以进行一些交错操作。

    Fred 一号读取值为零的头部。

    Fred two 也读取了值为零的头部。

    Fred 发送待处理消息并释放对象。

    然后,它将 head 的值加一。

    这就是数据竞赛。 根据对 Fred two 的读取 和对 Fred one 的写入发生的顺序而定。 在这种情况下, Fred 2 的指数 值可能会有所不同。 Fred 2 的 head 值过时, 导致其显示为待处理零值。 刚刚被弗雷德释放的地址消毒器检测到了使用情况 这里发生自由状态后, 并且在此执行过程中发生了释放后​​的使用。

    这是同一段代码在相同的两个线程上运行的结果。 但这次它们之间没有交错操作。 Address Sanitizer 无法 检测到此次执行中的问题。

    但这是个好消息。尽管我观察到了预期的结果。 线程清理器仍然检测到这里 存在问题,并向我发出警告。 第二条帖子里的这段话是数据竞赛的一部分。 Fred Sanitizer 还向 我展示了它正在争抢的内存访问量。

    这是在线程一上写的。 这就是为什么线材消毒剂非常有用的原因 用于查找那些看似无法 重现的错误报告的根本原因。

    为了解决这个问题,我使用了串行调度队列。 为了确保每个部分都能原子性地运行, 并且读取操作也必须正确执行。 右侧会同步预先写入的内容。 我已经用 dispatch async 包裹了这些语句。它们将在专用串行队列上运行。

    使用Swift并发可以避免任何数据竞争。

    线程清理器的启用方式与地址 清理器的启用方式相同。

    请注意,这两者是互斥的。 所以一次只能启用一个功能。

    对于自动化测试, 您可以为测试计划创建两种不同的配置, 一个启用了地址清理器, 另一个启用了线程清理器。

    消毒器在内存完整性强制执行中发挥着重要作用。

    地址清理器可帮助您查找和修复无效的内存访问。 启用 Emmy 后,这些操作会导致崩溃。

    线程清理器可帮助您查找数据竞争, 这通常会导致释放后使用。

    幸运的是,当发生释放后使用时, Emmy 会导致崩溃。 这可以防止剥削。 但会给您的客户带来挫败感。

    使用线程清理器查找并修复漏洞将保障客户安全。 而且很开心。

    接下来你需要这样做。 首先,在 Address Sanitizer 中找到测试计划的配置, 尤其是当你的代码库中使用了 C、 C + + 或 Objective-C 语言时。

    然后去使用线材消毒剂 如果您使用任何 C 或预并发程序, 请将其添加到您的测试计划中。 Swift。

    采用 Swift 并发机制来防止 Swift 代码中出现数据竞争。

    最后,下次当你收到一份无法 解释的崩溃报告时…… 或者,如果出现问题,请使用消毒剂进行检测, 看看是否能发现任何问题。 或许他们能提供一些线索, 说明这种糟糕的局面是如何发生的。

    谢谢。接下来交给库尔特。

    谢谢丹。今天的报告就到此结束了。 非常感谢所有演讲嘉宾。

    为了补充今天所涵盖的内容, 了解更多关于开发方面的知识, 最好的方法之一就是…… 对于 Apple 平台, 可以通过WWDC的视频观看。 以及其他与 Apple 活动相关的活动。 有数百个视频, 您可以在 Apple 开发者网站或 开发者应用程序中观看它们。 你甚至可能会注意到一些熟悉的面孔。

    感谢各位亲临开发者中心或在线参与本次活动。 现在我要在网上和大家道别了。 再见。 欢迎各位到大厅享用茶点。 谈话内容令人耳目一新。谢谢。

开发者页脚

  • 视频
  • Meet with Apple
  • 加固你的 App:关键策略助你增强安全性
  • 打开菜单 关闭菜单
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    • App Store
    打开菜单 关闭菜单
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    打开菜单 关闭菜单
    • 辅助功能
    • 配件
    • AI 与机器学习
    • Apple 智能
    • 音频与视频
    • 增强现实
    • 商务
    • 设计
    • 分发
    • 教育
    • 游戏
    • 健康与健身
    • App 内购买项目
    • 本地化
    • 地图与位置
    • 安全性
    • Safari 浏览器与网页
    打开菜单 关闭菜单
    • 文档
    • 下载
    • 示例代码
    • 视频
    • 文档归档
    打开菜单 关闭菜单
    • 帮助指南与文章
    • 联系我们
    • 论坛
    • 反馈与错误报告
    • 系统状态
    打开菜单 关闭菜单
    • Apple 开发者
    • App Store Connect
    • 证书、标识符和描述文件
    • “反馈助理”
    打开菜单 关闭菜单
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • Mini Apps Partner Program
    • News Partner Program
    • Video Partner Program
    • 安全赏金计划
    • Security Research Device Program
    打开菜单 关闭菜单
    • 与 Apple 会面交流
    • Apple Developer Center
    • App Store 大奖
    • Apple 设计大奖
    • Apple Developer Academy
    • WWDC
    阅读最近新闻。
    获取 Apple Developer App,并在 bilibili 和微信上关注我们。
    版权所有 © 2026 Apple Inc. 保留所有权利。
    使用条款 隐私政策 协议和准则