今天,我们宣布成立字节码联盟,这是一个新的行业合作关系,通过共同实施标准和提出新标准来推动 WebAssembly 的浏览器外未来。我们的创始成员是 Mozilla、Fastly、英特尔和红帽,我们期待着欢迎更多成员加入。
我们展望一个默认安全的 WebAssembly 生态系统,修复当今软件基础的漏洞。基于 WebAssembly 社区快速出现的进步,我们相信我们可以实现这个愿景。
我们已经在将这些解决方案用于现实世界的问题,并且这些问题正在 走向生产。但作为一个联盟,我们志向更加远大……
为什么
作为一个行业,我们每天都在让用户面临越来越大的风险。我们正在构建高度模块化的应用程序,其中 80% 的代码库来自 npm、PyPI 和 crates.io 等包注册中心。
利用这些蓬勃发展的生态系统并非坏事。实际上,这是好事!
问题在于,当前的软件架构并非为安全构建,而坏人正在利用这一点……而且这种情况正在以惊人的速度加剧。
坏人利用的是我们让用户信任我们。当用户启动你的应用程序时,就像用户将他们房屋的钥匙交给了你的代码。他们说“我相信你”。
但随后你邀请了所有你的依赖项,并给了它们一套完整的房屋钥匙。这些依赖项是由你不知道的人编写的,而且你没有理由信任他们。
作为一个社区,我们有一个选择。WebAssembly 生态系统可以提供解决方案……至少,如果我们选择以默认安全的方式设计它。但如果我们不这样做,WebAssembly 会使问题更加严重。
随着 WebAssembly 生态系统的不断发展,我们需要解决这个问题。这是一个无法单独解决的问题。这就是字节码联盟的用武之地。
什么
字节码联盟是一个由公司和个人组成的团体,共同组建了一个行业合作关系。
我们共同构建了坚固、安全的基石,可以使使用不受信任的代码变得安全,无论你在哪里运行它——无论是在云中,还是在用户桌面上本地运行,还是在小型物联网设备上运行。
有了它,开发人员可以像今天一样高效,以相同的方式使用开源,但不会危及用户安全。
这套通用、可重复使用的基石可以单独使用,也可以嵌入其他库和应用程序中。
目前,我们正在合作开发以下内容:
运行时
- Wasmtime 是一个独立的 WebAssembly 运行时,可以用作 CLI 工具或嵌入到其他系统中。它非常可配置且可扩展,因此可以作为许多用例特定运行时的基础,从小型物联网设备一直到云数据中心。
- Lucet 是一个用例特定运行时的示例。它非常适合高速 CDN 和边缘计算,使用 AOT 编译和其他技术来提供低延迟和高并发。我们正在对其进行重构,使其核心使用 Wasmtime。
- WebAssembly 微型运行时 (WAMR) 是另一个用例特定运行时。它非常适合资源极其有限的小型嵌入式设备。它提供了一个小的占用空间,并使用解释器来保持内存开销低。
运行时组件
语言工具
- cargo-wasi 是一个轻量级的 Cargo 子命令,它将 Rust 代码编译为目标 WebAssembly 和 WebAssembly 系统接口,以供浏览器外使用。
- wat 和 wasmparser 解析 WebAssembly。 wat 解析文本格式,而 wasmparser 是用于解析二进制格式的事件驱动库。
我们预计,随着联盟的不断壮大,这套项目也会不断扩展。
我们的成员也在领导 WASI 标准本身的工作,以及 Rust 到 WebAssembly 工作组的工作。
谁
字节码联盟的创始成员是 Mozilla、Fastly、英特尔和红帽。
我们从一个轻量级治理结构开始。我们打算随着时间的推移逐渐将此结构正式化。你可以在我们的 常见问题解答 中了解更多信息。
正如我们之前所说,这件事太大了,我们无法独自完成。因此,我们很高兴欢迎新成员加入联盟。如果你想加入,请发送电子邮件至 hello@bytecodealliance.org。
以下是我们认为这很重要的原因
WebAssembly 正在改变 Web,但我们相信,随着 WebAssembly 继续扩展到浏览器之外,它可以在软件生态系统中发挥更大的作用。这是新技术曙光来临的独特时刻,我们有机会修复破损的地方,为原生开发构建新的、默认安全的基石,这些基石具有可移植性和可扩展性。但我们需要采取有意识的、跨行业的行动,确保以正确的方式实现这一点。Mozilla 与字节码联盟中的合作伙伴一起,正在构建这些新的安全基础——从小型嵌入式设备到大型计算云。
—— Luke Wagner,Mozilla 首席工程师,WebAssembly 联合创始人
Fastly 很高兴帮助将字节码联盟带给社区。Lucet 和 Cranelift 已经共同开发了多年,我们很高兴正式化它们之间的关系,并帮助它们更快地一起发展。这是计算历史上的一个重要时刻,标志着我们有机会重新定义软件如何在客户端、来源和边缘构建。字节码联盟是我们为社区做出贡献并与社区合作的方式,以创建互联网未来将建立的基础。
Tyler McMullen,Fastly 首席技术官
英特尔作为创始成员加入字节码联盟,以帮助将 WebAssembly 的性能和安全优势扩展到浏览器之外,应用于各种应用程序和服务器。字节码联盟技术可以帮助开发人员使用各种语言扩展软件,并充分利用领先的计算平台的全部功能。
—— Mark Skarpness;英特尔架构、图形和软件副总裁;数据中心系统堆栈总监
红帽坚信开源技术在帮助提供从操作系统到浏览器再到开放混合云的计算基础方面所发挥的作用。Wasmtime 是一项激动人心的发展,它帮助将 WebAssembly 从浏览器转移到服务器空间,我们在那里尝试使用它来改变应用程序的信任模型,我们很高兴能参与其中,帮助它发展成为一个成熟的社区项目。
—— Chris Wright,红帽高级副总裁兼首席技术官
所以,这是个大新闻!🎉
要了解更多关于我们共同构建的内容,请继续阅读。
问题
在过去 20 年中,我们构建软件的方式发生了翻天覆地的变化。在 2003 年,公司很难让开发人员重复使用代码。
现在,你平均代码库的 80% 都是使用从注册中心下载的模块构建的,比如 JavaScript 的 npm、Python 的 PyPI、Rust 的 crates.io 等等。甚至 C++ 也正在转向启用可组合模块的生态系统。
这种新的应用程序开发方式使我们这个行业变得更加高效。但它也在我们的安全中引入了巨大的漏洞。正如我上面提到的,坏人正在利用这些漏洞来攻击我们的用户。
用户将他们房屋的钥匙交给了我们,而我们却像发糖果一样把访问权限给了他们……这不是因为我们不负责任。这是因为这些软件包非常有价值,但没有简单的方法来减轻使用它们带来的安全风险。
不仅如此,不仅是我们自己的依赖项会加入进来。它们所依赖的任何模块——间接依赖项也会加入进来。
这些开发人员可以访问什么?
- 机器上的资源——比如文件和内存
- API 和系统调用——他们可以使用这些工具来对这些资源进行操作
这意味着这些模块可能会造成很大的破坏。这可能是故意的,比如恶意代码,也可能是完全意外的,比如存在漏洞的代码。
让我们看看这些攻击是如何运作的。
恶意代码
恶意代码是由攻击者自己编写的。
攻击者通常使用社会工程学将他们的软件包引入应用程序。他们创建了一个具有有用功能的软件包,然后偷偷地添加一些恶意代码。一旦代码进入应用程序,用户启动应用程序后,该代码就可以攻击用户。
这就是黑客如何 从用户那里窃取价值 150 万美元 的加密货币(以及另外近 1300 万美元),始于今年 3 月。
- 第 0 天(3 月 6 日):攻击者向 npm 发布了一个模块:electron-native-notify。这看起来很有用——它可以帮助 Electron 应用程序以跨平台的方式发出原生通知。它当时还没有任何恶意代码。
- 第 2 天:为了实施偷窃,攻击者必须将这个模块引入加密货币应用程序。他们选择的是一个帮助用户管理其加密货币的应用程序 Agama Wallet 中的依赖项。
- 第 17 天:攻击者添加了恶意有效负载。
- 第 41-66 天:应用程序重建,并拉取了依赖项的最新版本,以及 electron-native-notify。此时,它开始将用户的“种子”(用户名/密码组合)发送到服务器。攻击者随后可以使用这些种子来清空用户的钱包。
- 第 90 天:一名用户向 npm 报告了 electron-native-notify 中的可疑行为,npm 通知了加密货币平台,该平台将易受攻击的钱包中的资金转移到了安全的钱包中。
让我们看看这种恶意代码需要哪些访问权限才能实施攻击。
它需要种子。为此,它获得了访问保存种子的内存的权限。
然后它需要将种子发送到服务器。为此,它需要访问套接字,以及访问用于打开该套接字的 API 或系统调用。
随着越来越多的攻击者意识到我们的信任体系的脆弱性,恶意代码攻击正在上升。例如,发布到 npm 的恶意模块数量从 2017 年到 2019 年增加了一倍以上。而 npm 的安全副总裁指出,这些攻击正在变得更加严重。
在 2019 年,我们看到了更多以经济利益为动机的攻击。早期的攻击集中在恶作剧——删除文件并试图窃取一些凭据。非常基础的攻击,有点像抢劫。现在最终用户是目标。而[攻击者] 正在努力保持耐心,保持隐秘,进行真正的精心策划的攻击。
易受攻击的代码
漏洞是不同的。模块维护者并没有试图做任何坏事。
模块维护者只是在他们的代码中有一个错误。但是攻击者可以使用该错误来欺骗他们的代码执行不应执行的操作。
为了举例说明,让我们看一下 ZipSlip。
ZipSlip 是在许多生态系统中的模块中发现的漏洞:JavaScript、Java、.NET、Go、Ruby、C++、Python……不胜枚举。它影响了数千个项目,包括来自惠普、亚马逊、Apache、Pivotal 和更多公司的项目。
如果一个模块存在此漏洞,攻击者可以使用它来替换文件系统中任何位置的文件。例如,攻击者可以使用它来替换 node_modules 目录中的 .js 文件。然后,当 .js 文件被要求时,攻击者的代码将运行。
那么它是如何工作的呢?
攻击者将创建一个文件,并为其提供一个包含“../”的文件名,从而构建一个相对文件路径。当易受攻击的代码解压缩它时,它不会对文件名进行清理。因此,它将调用 write
系统调用并使用相对路径,将文件放置在攻击者希望它在文件系统中出现的位置。
那么,易受攻击的代码需要什么权限才能让攻击者得逞呢?
它需要访问包含敏感文件的目录。
它还需要访问 write
系统调用。
漏洞也在增加,在过去两年中增加了 88%。正如 Snyk 在他们的 2019 年开源安全状况报告 中所报道的那样。
2018 年,npm 的漏洞增加了 47%。Maven Central 和 PHP Packagist 的披露分别增长了 27% 和 56%。
面对这些风险,你可能会认为修补漏洞是一个高度优先事项。但正如 Snyk 所发现的那样,只有 59% 的包对已公开的漏洞有已知修复程序,因为许多维护者没有时间或安全知识来修复这些漏洞。
这会导致普遍的漏洞,因为这些易受攻击的模块被其他模块依赖。例如,一项 对 npm 模块的研究 发现,高达 40% 的包依赖于至少包含一个公开已知漏洞的代码。
我们今天如何保护用户?
那么,在当今的软件生态系统中,你如何保护你的用户免受这些威胁呢?
- 你可以运行扫描程序来检测你的代码和你依赖项代码中的可疑编码模式。但是,这些自动化工具无法捕获许多东西。
- 你可以订阅一个监控服务,当你的依赖项中发现漏洞时,它会提醒你。但这只适用于已发现的漏洞。即使漏洞被发现,维护者也可能无法快速修复。例如,Snyk 在 npm 生态系统中发现,对于前 6 个包,修复时间的中位数(从漏洞包含开始测量)为 2.5 年。
- 你可以尝试进行手动代码审查。每当依赖项更新时,你都需要审查更改的行。但是,如果你的依赖树中有数百个模块,这可能需要你每隔几周审查 10 万行代码。
- 你可以固定你的依赖项,这样恶意代码就无法进入,直到你有机会进行审查。但这样一来,漏洞的修复就会被推迟,你的应用程序会更长时间地处于漏洞状态。
这一切都造成了一个两难境地,让你想要放弃,只希望一切顺利。但作为一个行业,我们不能这样做。对我们的用户来说,风险太大了。
所有这些解决方案都试图捕获恶意代码和易受攻击的代码。但如果我们从不同的角度看待这个问题呢?
问题的一部分在于这些模块所拥有的权限。如果我们剥夺了这些权限呢?
我们作为一个行业之前就面临过这种挑战……只是在不同的粒度上。
当你在计算机上同时运行两个程序时,你怎么知道一个程序不会干扰另一个程序呢?你是否必须信任程序会表现良好呢?
不,因为操作系统中内置了保护机制。操作系统用于保护程序相互干扰的工具是进程。
当你启动一个程序时,操作系统会启动一个新的进程。每个进程都会获得自己的内存块来使用,并且无法访问其他进程中的内存。
如果它想要从另一个进程获取数据,它必须请求。然后数据将通过进程边界发送。这确保了每个程序都控制着自己内存中的数据。
这种内存隔离确实使得同时运行两个程序更加安全。但这并不是完美的安全。恶意程序仍然可以干扰其他一些资源,例如文件系统中的文件。
VM 和容器的开发是为了解决这个问题。它们确保在某个 VM 或容器中运行的东西无法访问另一个的 文件系统。使用沙箱,可以剥夺对 API 和系统调用的访问权限。
那么,我们能否将每个包放在它自己的隔离单元中呢?它自己的沙盒进程呢?
这将解决我们的问题。但也将带来一个新问题。
所有这些技术都比较重量级。如果我们将数百个包封装到它们自己的沙盒进程中,我们很快就会耗尽内存。我们还会使不同包之间的函数调用变得更加缓慢和复杂。
但事实证明,新技术为我们提供了新的选择。
在我们构建 WebAssembly 生态系统时,我们可以设计各个部分的组合方式,以便为你提供与进程或容器相同的隔离级别,但没有缺点。
明天的解决方案:WebAssembly “纳米进程”
WebAssembly 可以提供这种隔离,使其能够安全地运行不受信任的代码。我们可以拥有一个类似于 Unix 的许多小型进程,或者像容器和微服务的架构。
但是这种隔离的重量级要轻得多,它们之间的通信不会比普通的函数调用慢多少。
这意味着你可以使用它们来封装单个 WebAssembly 模块实例,或者一小部分想要彼此共享内存等内容的模块实例。
此外,你不必放弃良好的编程语言功能,例如函数签名和静态类型检查。
那么它是如何工作的呢?WebAssembly 的哪些特性使得这一切成为可能呢?
首先,每个 WebAssembly 模块默认都是沙盒化的。
默认情况下,模块无法访问 API 和系统调用。如果你想要让模块能够与模块外部的任何内容交互,你必须显式地为模块提供函数或系统调用。然后模块就可以调用它。
其次,还有内存模型。
与直接编译为 x86 之类的直接编译的普通二进制文件不同,WebAssembly 模块无法访问其进程中的所有内存。它只能访问已分配给它的内存块。
理论上,脚本语言也会提供这种隔离。脚本语言中的代码无法直接访问进程中的内存。它只能通过它作用域内的变量来访问内存。
但在大多数脚本语言生态系统中,代码广泛使用共享全局对象。这实际上与共享内存相同。因此,生态系统中的约定使得内存隔离对于脚本语言来说也是一个问题。
WebAssembly 本来也会出现这个问题。在早期,有些人希望建立一个将共享内存传递到每个模块的约定。但社区小组选择了更安全的约定,默认情况下将内存封装。
这为我们提供了两个模块之间的内存隔离。这意味着恶意模块无法干扰其父模块的内存。
但是我们如何从我们的模块共享数据呢?你必须将它作为函数调用的值传递进来或传出去。
不过,这里存在一个问题。默认情况下,WebAssembly 只有少数几个数字类型,这意味着你只能跨模块传递单个数字。
这就是第三个特性发挥作用的地方——接口类型提案,我们在 8 月演示了该提案。使用接口类型,模块可以使用更复杂的值进行通信,例如字符串、序列、记录、变体以及这些值的嵌套组合。
这使得两个模块很容易交换数据,但它是一种安全且快速的方式。WebAssembly 引擎可以在调用者和被调用者的内存之间进行直接复制,而无需对数据进行序列化和反序列化。即使两个模块不是从相同的语言编译的,这也适用。
因此,这就是我们确保恶意模块无法干扰应用程序中其他模块的内存的方式。
但我们不仅需要对这些模块之间如何处理内存采取预防措施。因为如果应用程序实际上能够对该数据进行任何操作,它将需要调用 API 或系统调用。而这些 API 或系统调用可能访问共享资源,例如文件系统。正如我们在之前的文章中所讨论的那样, 上一篇文章,大多数操作系统处理文件系统访问的方式在提供我们所需的安全性方面确实存在缺陷。
因此,我们需要实际上将权限概念融入到 API 和系统调用中的 API 和系统调用,以便它们可以授予不同的模块对不同资源的不同权限。
这就是第四个功能的用武之地:WASI,WebAssembly 系统接口。
这为我们提供了一种方法,可以将这些不同的模块彼此隔离,并为它们提供对文件系统和其它资源的特定部分的细粒度权限,以及对不同系统调用的细粒度权限。
因此,有了它,我们便掌握了钥匙。
还缺什么?
目前,我们还没有一种方法可以将这些密钥通过依赖关系树传递下去。我们需要一种方法让父模块将这些密钥传递给它们的依赖项。这样,它们就可以将依赖项所需的密钥(仅此而已)传递给它们。然后,这些依赖项可以为它们的子项执行同样的操作,一直传递到树的底部。
这将是我们接下来要进行的工作。从技术角度讲,我们计划使用一种针对每个模块的细粒度虚拟化形式。这是一个研究人员在研究环境中证明的概念,我们正在努力将其引入 WebAssembly。
总而言之,这些功能使我们能够实现类似于进程的隔离,但开销要低得多。这种使用模式被称为 WebAssembly 纳米进程。
它仍然只是 WebAssembly,但它遵循一种特定的模式。如果我们将这种模式构建到我们使用的工具和约定中,我们就能让第三方代码在 WebAssembly 中实现安全重用,这是迄今为止其他生态系统无法实现的。
旁注:目前,每个纳米进程都由一个 wasm 模块组成。未来,我们将专注于工具链支持,用于创建包含多个 wasm 模块的纳米进程,从而实现原生风格的动态链接,同时保留纳米进程的内存隔离。
有了这些基础,开发人员将能够构建隔离包的依赖关系树,每个包都拥有自己独立的小型纳米进程。
纳米进程如何保护用户?
那么,这如何帮助我们保护用户的安全?
在这两种情况下,都是因为我们遵循了最小权限原则才让我们保持安全。让我们来了解一下它是如何做到的。
恶意代码
为了实施攻击,模块通常需要访问它本来无法访问的资源和 API。使用我们的方法,模块必须声明它需要访问这些内容。这使得我们能够轻松地识别出模块何时请求执行不应该执行的操作。
例如,窃取钱包的模块需要访问指向其发送种子信息的服务器的套接字,以及打开网络连接的能力。
但这个模块应该做的只是获取传递给它的文本并将其交给系统,以便系统能够显示它。为什么模块需要打开与远程服务器的网络连接来执行此操作?
如果 electron-native-notify 从一开始就请求了这些权限,那将是一个严重的警示信号。EasyDEX-GUI 的维护者可能不会将其纳入其依赖关系树中。
如果恶意维护者试图在模块已加入 EasyDEX-GUI 之后,稍后偷偷地添加这些访问请求,那么这将是一项重大变更。模块的签名将发生变化,当调用代码没有提供模块预期的导入或参数时,WebAssembly 会抛出错误。
这意味着无法偷偷地获取对新资源或系统调用的访问权限。
因此,维护者不太可能获得与服务器通信所需的必要基本访问权限。但即使该维护者设法诱使应用开发人员授予他们这种访问权限,他们仍然很难获取种子信息。
这是因为种子信息位于另一个模块的内存中。模块有两种方法可以访问另一个模块内存中的内容。而且这两种方法都不允许恶意模块偷偷地进行操作。
- 拥有种子的模块导出种子变量,使其自动对应用中的所有其他模块可见。
- 拥有种子的模块将它作为参数直接传递给恶意模块中的函数。
对于如此敏感的变量,这两种方法似乎都极不可能发生。
不幸的是,攻击者还有一种不太直接的方法可以访问另一个模块的内存,即旁路攻击(例如幽灵攻击)。操作系统进程试图提供所谓的时序保护,这有助于防御这类攻击。但遗憾的是,CPU 和操作系统没有提供比进程更细粒度的时序保护功能。
你可能从未想过要使用进程来保护你的代码。为什么大多数人不需要担心?因为托管服务提供商会负责处理,有时会使用复杂的技术。
如果你现在使用隔离进程来构建你的代码,你可能仍然需要使用它。在这里将进程切换到纳米进程需要仔细分析。
但有许多情况下不需要时序保护,或者现在人们根本没有使用进程。这些是纳米进程的理想候选者。
此外,随着 CPU 发展到提供更廉价的时序保护功能,WebAssembly 纳米进程将能够迅速利用这些功能,届时你将不再需要为此使用操作系统进程。
易受攻击的代码
这如何防御攻击者利用漏洞进行攻击?
与恶意模块一样,漏洞模块不太可能合法地需要攻击者所需的访问权限组合。
在我们的 ZipSlip 示例中,即使是合法的使用,漏洞模块也需要访问危险的系统调用,即 `write` 系统调用,它允许该模块写入文件。
但它不需要访问 `node_modules` 目录来进行任何合法操作。它只能访问其父模块传递给它的目录,即允许解压缩 zip 文件的目录。
这使得漏洞模块无法执行攻击者的意图(除非父模块有意地将非常敏感的目录传递给漏洞模块)。
如上文警告中所述,应用开发人员可能会传入一个敏感目录,例如根目录。但为了使此操作生效,此错误必须发生在顶级包中。它不可能发生在传递依赖项中。通过使所有这些访问权限都明确化,这使得在代码审查中更容易识别出这类疏忽大意导致的错误。
纳米进程的其他优势
使用纳米进程,我们可以继续构建高度模块化的应用……我们可以在保持开发人员生产力优势的同时,确保用户的安全。
但纳米进程还能帮助我们做什么?
任何地方都适用的隔离
这些纳米进程(这些像容器一样的小东西)可以适应常规进程、容器和虚拟机无法适应的各种地方。
这就是为什么我们能够将每个包或一组包封装到这些微小的微进程中的原因。由于它们非常小,因此不会增加你的应用的体积。但依赖关系树并非唯一使用这些纳米进程的地方。
以下是我们为它们看到的另外几个用例,以及联盟中的合作伙伴正在努力开发的一些用例。
处理同一台机器上数万名客户的请求
Fastly 为互联网 13% 的流量提供服务。这意味着要响应数百万个请求,并努力以尽可能低的开销来实现,同时确保客户的安全。
他们提出了一种使用 WebAssembly 纳米进程的创新架构,它使得能够安全地在一个进程中托管数万个同时运行的程序。他们的方法完全隔离了请求与以前的请求,确保了完整的虚拟机隔离。它还有一个额外的好处,就是冷启动速度快得多。
针对原生应用中的单个库进行软件故障隔离
在某些应用中,大部分代码都是内部编写的,只有少量库是由组织外部的不可信开发人员编写的。
在这些情况下,可以使用纳米进程为单个库创建一个轻量级、进程内沙箱,而不是将你的整个依赖关系树封装在纳米进程中。
我们已经开始在Firefox 中构建这种沙箱,以保护用户免受第三方库(例如字体渲染引擎、图像和音频解码器)中的错误的影响。
改进的软件可组合性
几十年来,软件开发最佳实践一直在推动越来越模块化的架构,这些架构由小型、类似乐高积木的组件组成。
这始于上面描述的库和模块,它们在同一个进程中、同一语言中运行。最近,出现了服务,然后是微服务,它们提供了隔离的安全优势。
但除了安全之外,服务提供的隔离还使软件更加可组合。这意味着你可以将用不同语言或同一语言的不同版本编写的服务组合在一起。这为你提供了更多乐高积木来玩。
但这些服务无法像库那样随处可用,因为它们太大。它们通常运行在一个进程中,该进程运行在一个容器中,该容器运行在服务器上。这意味着在将你的应用分解成这些服务时,你通常必须使用一种粗粒度的方法。
使用 wasm,我们可以用纳米进程替换微服务,并获得相同的安全性和语言独立性优势。它为我们提供了微服务的可组合性,而不会增加重量。这意味着我们可以使用微服务风格的架构以及它提供的语言互操作性,但采用更细粒度的方法来定义组件服务。
加入我们
这就是我们对用户更安全未来的愿景。我们相信 WebAssembly 不仅有机会,而且有责任提供这种安全保障。
我们希望你能加入我们!
关于 Lin Clark
Lin 在 Mozilla 的高级开发部门工作,专注于 Rust 和 WebAssembly。