WASI标准:在Web之外启动WebAssembly

3月27日,我们在Mozilla宣布了WASI(WebAssembly系统接口,WebAssembly系统接口)的标准化。

原因:开发人员开始在浏览器之外使用WebAssembly,因为WASM提供了一种快速,可扩展,安全的方式来在所有计算机上运行相同的代码。 但是,我们尚未为这种发展奠定坚实的基础。 在浏览器之外,您需要某种方式与系统进行通信,即系统界面。 但是WebAssembly平台还没有它。

内容: WebAssembly是概念计算机而非物理计算机的汇编程序。 它可以在各种体系结构上工作,因此,概念性OS在不同的操作系统上工作需要系统接口。

这就是WASI:这是WebAssembly平台的系统接口。

我们努力创建一个系统界面,该界面将成为WebAssembly的真正伴侣,并具有最大的可移植性和安全性。

谁:作为WebAssembly开发团队的一部分,我们组织了一个分组,将标准化WASI 。 我们已经收集了感兴趣的合作伙伴,并正在寻找新的合作伙伴。

我们,合作伙伴和支持者认为这很重要的原因如下:

Mozilla研发总监Sean White:
“ WebAssembly已经在改变人们交付新型引人入胜的内容的方式。它可以帮助内容开发人员和创作者。 到目前为止,所有内容都可以通过浏览器工作,但是有了WASI,WebAssembly将使更多用户和不同地方的更多设备受益。”

首席技术官Tyler McMullen迅速:
“我们将WebAssembly视为在边缘云上快速安全地执行代码的平台。 尽管有不同的环境(边缘和浏览器),但由于有了WASI,您不必将代码移植到每个平台上。”

Node指导委员会CTO Miles Borins:
“ WebAssembly可以解决Node的最大问题之一:如何在保持可移植性和安全性的同时,达到接近本机的速度并重用用其他语言(例如C和C ++)编写的代码。 WASI标准化是朝着这一目标迈出的第一步。”

npm的联合创始人Lori Voss:
“ Npm对npm生态系统的潜在WebAssembly功能感到非常兴奋,因为它使获取本机代码在服务器端JavaScript应用程序中运行变得更加容易。 我们期待这一过程的结果。”

所以这是一个大事件!

当前有三种WASI实现:


WASI示范行动:


接下来,我们将讨论Mozilla关于该系统界面应如何工作的建议。

什么是系统接口?


许多人说像C这样的语言可以直接访问系统资源。 但这并非完全正确。 在大多数系统上,这些语言无法直接访问诸如打开或创建文件之类的内容。 为什么不呢

因为这些系统资源(文件,内存和网络连接)对于稳定性和安全性来说非常重要。

如果一个程序意外破坏了另一个程序的资源,则可能导致崩溃。 更糟糕的是,如果程序(或用户)专门入侵他人的资源,则它可能窃取敏感数据。



因此,您需要一种方法来控制哪些程序和用户可以访问资源。 长期以来,系统开发人员想出了一种提供这种控制的方法:保护环。

通过保护环,OS实质上在系统资源周围设置了保护屏障。 这是核心。 只有它才能执行诸如创建文件,打开文件或打开网络连接之类的操作。

用户程序在称为用户空间的内核外部运行。 如果程序要打开文件,则应请求内核。



这就是系统调用概念出现的地方。 当程序需要向内核请求某些操作时,它将发送系统调用。 内核检查联系用户,并查看他是否有权访问该文件。

在大多数设备上,访问系统资源的唯一方法是通过系统调用。



操作系统提供对系统调用的访问。 但是,如果每个操作系统都有自己的系统调用,那么它们是否不需要编写不同版本的代码? 幸运的不是。 该问题使用抽象解决。

大多数语言都有一个标准库。 进行编码时,程序员无需知道他为哪个系统编写。 它只是使用接口。 然后,在编译时,您的工具链会选择要用于哪个系统的接口实现。 此实现使用来自操作系统API的功能,因此它是特定于它的。

这是出现系统接口概念的地方。 例如,如果您为Windows机器编译printf ,它将使用Windows API。 如果是为Mac或Linux编译的,则使用POSIX。



但是,这给WebAssembly带来了问题。 即使在编译过程中,我们也不知道要针对哪个OS优化程序。 因此,您不能在WebAssembly上的标准库的实现内使用任何一个OS的系统接口。



我已经说过WebAssembly是概念计算机汇编程序 ,而不是实际计算机的汇编程序 。 类似地,WebAssembly需要用于概念性而非真实OS的系统接口。

但是,即使没有此系统界面,也已经有可以在浏览器外部运行WebAssembly的运行时。 他们是如何做到的? 让我们看看。

WebAssembly现在如何在浏览器之外工作?


生成WebAssembly代码的第一个工具是Emscripten。 它在网络上模拟特定的OS系统接口POSIX。 这意味着程序员可以使用标准C库(libc)中的函数。

为此,Emscripten使用其自己的libc实现。 它分为两部分:第一部分被编译为WebAssembly模块,第二部分以JS-glue代码实现。 这种JS胶水将调用发送到与OS通讯的浏览器。



大多数早期的WebAssembly代码都是使用Emscripten编译的。 因此,当人们开始想在没有浏览器的情况下运行WebAssembly时,他们开始运行Emscripten代码。

因此,在这些运行时中,您应该为JS-glue代码中的所有功能创建自己的实现。

但是有一个问题。 JS胶水代码提供的接口尚未设计为标准甚至公共接口。 例如,要像调用普通API一样进行调用,JS胶水代码将使用_system3(which, varargs)调用。



第一个参数是一个始终与名称中的数字匹配的整数(在本例中为3)。

第二个参数varargs列出参数。 之所以称为varargs ,是因为我们可以有不同数量的参数。 但是WebAssembly不允许将可变数量的参数传递给函数。 因此,它们是通过线性存储器传输的,这是不安全的,并且比通过寄存器要慢。

对于浏览器中的Emscripten,这是正常的。 但是现在运行时将其视为事实上的标准,实现了自己的JS胶水版本。 他们模拟POSIX模拟层的内部细节。

这意味着它们重新实现了代码(例如,将参数作为堆值传递),这在给定Emscripten约束的情况下是有意义的,但在这些运行时环境中没有此类约束。



如果数十年来我们一直在构建WebAssembly生态系统,则它需要坚实的基础,而不是cru脚。 这意味着我们的实际标准不能是仿真仿真。

但是在这种情况下适用什么原则?

WebAssembly系统界面应遵循哪些原则?


WebAssembly的两个基本原则:

  • 便携性
  • 安全性

我们超越了浏览器,但保留了这些关键原则。

但是,POSIX方法和Unix访问控制系统无法提供期望的结果。 让我们看看问题出在哪里。

可携性


POSIX提供源代码的可移植性。 您可以为不同的计算机使用不同版本的libc编译相同的源代码。



但是WebAssembly必须超越此范围。 我们需要编译一次才能在很多不同的系统上运行。 我们需要可移植的二进制文件。



这简化了代码分配。

例如,如果本机节点模块是用WebAssembly编写的,则用户在安装具有本机模块的应用程序时无需运行node-gyp,并且开发人员无需配置和分发数十个二进制文件。

安全性


当代码要求操作系统进行输入或输出时,操作系统通常应使用基于所有权和组的访问控制系统来评估此操作的安全性。

例如,一个程序要求打开文件。 用户具有他有权访问的一组特定文件。

当用户启动程序时,该程序代表该用户启动。 如果用户有权访问文件(他是文件所有者,或者是有权访问文件的组的成员),则程序具有相同的访问权。



这可以保护用户彼此之间的相互保护,这在过去很有意义,当时很多人在一台计算机上工作,而管理员则控制该软件。 然后,主要威胁是其他用户正在查看您的文件。

一切都变了。 当前,系统通常是单用户,但使用可靠性未知的第三方代码。 现在,主要威胁来自您自己运行的代码。

例如,对于应用程序中的库,已经启动了一个新的维护程序(在开放源代码中通常如此)。 他可能是一个真诚的活动家……或一个入侵者。 而且,如果他具有访问您的系统的权限(例如,打开任何文件并通过网络发送文件的能力),那么此代码可能会造成很大的破坏。


可疑应用程序 :我为用户Bob工作。 我可以打开他的比特币钱包吗?
核心 :对于鲍勃? 当然可以!
可疑应用程序 :太好了! 网络连接如何?

这就是使用第三方库很危险的原因。 WebAssembly通过沙盒以不同的方式提供安全性。 在这里,代码无法直接与操作系统对话。 但是那怎么访问系统资源呢? 代码可以使用的主机(浏览器或wasm运行时)沙箱功能。

这意味着主机基于软件限制了程序的功能,不允许您仅代表用户执行操作,从而导致具有完全用户权限的任何系统调用。

本身具有沙箱并不能保证系统安全-主机仍可以将全部功能转移到沙箱,在这种情况下,主机不提供任何保护。 但是沙盒至少为主机提供了一个理论上的机会来构建更安全的系统。


WA :请注意,这里有一些与OS交互的安全玩具(safe_write,safe_read)。
可疑的应用程序 :该死的...我的网络访问哪里?

在任何系统界面中,您都必须遵守这两个原则。 可移植性使软件开发和分发更加容易,并且绝对需要保护主机和用户的工具。

这样的系统界面应该是什么样的?


鉴于这两个关键原则,WebAssembly系统界面应该是什么?

我们将在标准化过程中找到这一点。 但是,我们有个建议:

  • 创建一组标准接口
  • 让我们从标准化wasi-core核心模块开始。




wasi-core将会是什么? 这些是所有程序所需的基础。 该模块将涵盖大多数POSIX功能,包括文件,网络连接,时钟和随机数。

许多基本功能将需要非常相似的方法。 例如,为POSIX文件提供了打开,关闭,读取和写入系统调用的方法,其他所有操作都是上面的附加组件。

但是wasi-core并未涵盖所有POSIX功能。 例如,流程的概念显然不适合WebAssembly。 另外,很明显,每个WebAssembly引擎都必须支持流程操作,例如fork 。 但是我们也希望使fork标准化成为可能。



像Rust这样的语言将直接在其标准库中使用wasi-core。 例如,当编译为WebAssembly时,通过调用__wasi_path_open来实现从Rust open

对于C和C ++,我们创建了wasi-sysroot ,它根据wasi-core函数实现了libc。



我们希望像Clang这样的编译器能够与WASI API进行交互,并且像Rust编译器和Emscripten这样的完整工具链都将WASI用作其系统实现的一部分。

定制代码如何调用这些WASI函数?

执行代码的运行时通过wasi-core函数,将对象放置在沙箱中。



这提供了可移植性,因为每个主机都可以有专门针对其平台的wasi-core实现:从WebAssembly运行时(例如Mozilla Wasmtime和Fastly Lucet)到Node甚至是浏览器。

它还提供了可靠的隔离,因为主机会根据软件选择将wasi-core功能转移到沙箱(即应允许的系统调用)。 这就是安全性。



WASI通过将基于授权的安全概念引入系统来增强和扩展安全性。

通常,如果代码需要打开文件,它会在行中使用路径名调用open 。 然后,操作系统检查代码是否有权执行此操作(基于启动程序的用户的权限)。

对于WASI,在调用函数访问文件时,必须传递文件描述符,该文件描述符必须附加文件本身或包含该文件的目录的权限。

因此,您不能拥有偶然要求您打开/etc/passwd 。 相反,代码只能使用其自己的目录。



由于这些系统调用的功能受到限制,因此可以将各种系统调用安全地解析为隔离的代码。

在每个模块中,依此类推。 默认情况下,该模块无权访问文件描述符。 但是,如果一个模块中的代码具有文件描述符,则可以将其传递给其他模块中调用的函数。 或者创建文件描述符的更多受限版本以传递给其他功能。

因此,运行时传递应用程序可以在顶级代码中使用的文件描述符,然后根据需要在整个系统的其余部分分发文件描述符。



这使WebAssembly更接近最小特​​权原则,在该原则下,模块仅获得完成其工作所需的最少资源集。

与CloudABI和Capsicum中一样,此概念基于基于特权的安全性。 这些系统的问题之一是代码难于移植。 但是我们认为这个问题可以解决。

如果代码已经使用带有相对文件路径的openat ,则编译代码就可以了。

如果代码使用open且openat样式的迁移过于激烈,则WASI将提供增量解决方案。 使用libpreopen,可以创建应用程序具有合法访问权限的文件路径列表。 然后使用open ,但仅对这些路径使用。

接下来是什么?


我们相信wasi-core是一个好的开始。 它保留了WebAssembly的可移植性和安全性,为生态系统提供了坚实的基础。

但是在wasi-core完全标准化之后,还需要解决其他问题,包括:

  • 异步输入输出
  • 文件监控
  • 文件锁

这仅仅是开始,因此,如果您有任何想法,请参与其中

Source: https://habr.com/ru/post/zh-CN446764/


All Articles