WASI Standard: Launch WebAssembly Beyond the Web

Original author: Lin Clark
  • Transfer
On March 27, we at Mozilla announced the start of standardization of WASI, the WebAssembly system interface (WebAssembly system interface).

Why: developers started using WebAssembly outside the browser, because WASM provides a fast, scalable, secure way to run the same code on all machines. But we do not yet have a solid foundation for such a development. Outside the browser, you need some way to communicate with the system, that is, the system interface. But the WebAssembly platform does not have it yet.

What: WebAssembly is an assembler for a conceptual rather than a physical machine. It works on various architectures, therefore, a system interface is needed for a conceptual OS to work on different operating systems.

Here's what WASI is: it is a system interface for the WebAssembly platform.

We strive to create a system interface that will become a true companion for WebAssembly with maximum portability and security.

Who: As part of the WebAssembly development team, we organized a subgroup that will standardize on WASI . We have already gathered interested partners and are looking for new ones.

Here are some reasons why we, our partners and supporters consider this important:

Sean White, Mozilla R&D Director:
“WebAssembly is already changing the way people deliver new types of engaging content. It helps content developers and creators. Until now, everything has worked through browsers, but with WASI, more users and more devices in different places will benefit from WebAssembly. ”

Tyler McMullen, CTO Fastly:
“We see WebAssembly as a platform for quickly and safely executing code on an edge cloud. Despite the different environments (edge ​​and browsers), thanks to WASI, you don’t have to port the code to each platform. ”

Miles Borins, CTO of the Node Steering Committee:
“WebAssembly can solve one of Node's biggest problems: how to achieve near native speed and reuse code written in other languages ​​such as C and C ++, while maintaining portability and security. WASI standardization is the first step towards this. ”

Lori Voss, co-founder of npm:
“Npm is extremely excited about the potential WebAssembly for the npm ecosystem, as it makes it much easier to get native code to run in server-side JavaScript applications. We look forward to the results of this process. ”

So this is a big event!

There are currently three WASI implementations:


WASI demonstration in action:


Next, we’ll talk about Mozilla’s proposal on how this system interface should work.

What is a system interface?


Many say languages ​​like C provide direct access to system resources. But it is not so. On most systems, these languages ​​do not have direct access to things such as opening or creating files. Why not?

Because these system resources — files, memory, and network connections — are too important for stability and security.

If one program accidentally ruins the resources of another, it may cause a crash. Worse, if a program (or a user) specifically invades other people's resources, it can steal sensitive data.



Therefore, you need a way to control which programs and users can access resources. For a long time, system developers came up with a way to provide such control: protection rings.

With protection rings, the OS essentially sets up a protective barrier around system resources. This is the core. Only it can perform operations such as creating a file, opening a file, or opening a network connection.

User programs run outside the kernel in what is called user space. If the program wants to open the file, it should ask for the kernel. This is where the concept of a system call arises. When a program needs to ask the kernel for some operation, it sends a system call. The kernel checks the contacting user and sees if he has permission to access this file. On most devices, the only way to access system resources is through system calls.









The operating system provides access to system calls. But if each OS has its own system calls, do not they need to write different versions of the code? Fortunately not. The problem is solved using abstraction.

Most languages ​​have a standard library. When coding, the programmer does not need to know for which system he writes. It just uses the interface. Then, when compiling, your tool chain chooses which interface implementation to use for which system. This implementation uses functions from the API of the operating system, so it is specific to it.

This is where the concept of a system interface appears. For example, if you compile printffor a Windows machine, it will use the Windows API. If compiled for Mac or Linux, it uses POSIX.



However, this poses a problem for WebAssembly. Here we do not know for which OS to optimize the program even during compilation. Thus, you cannot use the system interface of any one OS inside the implementation of the standard library on WebAssembly. I have already said that WebAssembly is an assembler for a conceptual machine , not a real machine. Similarly, WebAssembly needs a system interface for a conceptual rather than a real OS. But there are already runtimes that can run WebAssembly outside the browser, even without this system interface. How do they do it? Let's get a look.







How does WebAssembly now work outside the browser?


The first tool for generating WebAssembly code was Emscripten. It emulates on the web a specific OS system interface - POSIX. This means that the programmer can use the functions from the standard C library (libc).

For this, Emscripten uses its own libc implementation. It is divided into two parts: the first is compiled into a WebAssembly module, and the other is implemented in JS-glue code. This JS glue sends calls to the browser that is talking to the OS.



Most of the early WebAssembly code is compiled with Emscripten. Therefore, when people began to want to run WebAssembly without a browser, they started to run Emscripten code.

So in these runtimes you should create your own implementations for all the functions that were in the JS-glue code.

But there is a problem. The interface provided by the JS glue code has not been designed as a standard or even public interface. For example, to call something like readin the normal API, the JS-glue code uses the call _system3(which, varargs).



The first parameter which is an integer that always matches the number in the name (in our case 3).

The second parameter varargslists the arguments. It is called varargsbecause we can have a different number of arguments. But WebAssembly does not allow passing a variable number of arguments to a function. Therefore, they are transmitted through linear memory, which is unsafe and slower than through registers.

For Emscripten in the browser, this is normal. But now runtimes see this as a de facto standard, implementing their own versions of JS glue. They emulate the internal details of the POSIX emulation layer.

This means that they re-implement the code (for example, pass arguments as heap values), which made sense given the Emscripten constraints, but there are no such constraints in these runtime environments. If we have been building the WebAssembly ecosystem for decades to come, it needs a solid foundation, not crutches. This means that our actual standard cannot be emulation emulation. But what principles apply in this case?







What principles should the WebAssembly system interface adhere to?


Two fundamental principles of WebAssembly:

  • portability
  • security

We go beyond the browser, but retain these key principles.

However, the POSIX approach and the Unix access control system do not give us the desired result. Let's see what the problem is.

Portability


POSIX provides portability of source code. You can compile the same source code with different versions of libc for different computers. But WebAssembly must go beyond that. We need to compile once to run on a whole bunch of different systems. We need portable binaries. This simplifies code distribution. For example, if native Node modules are written in WebAssembly, then users do not need to run node-gyp when installing applications with native modules, and developers do not need to configure and distribute dozens of binary files.











Security


When the code asks the operating system to make input or output, the OS should evaluate the security of this operation, usually using an access control system based on ownership and groups.

For example, a program asks to open a file. The user has a specific set of files to which he has access.

When a user starts a program, the program starts on behalf of that user. If the user has access to the file — either he is its owner, or is part of a group that has access to the file — then the program has the same access. This protects users from each other, which made sense in the old days, when a lot of people worked on one computer and administrators controlled the software. Then the main threat was other users looking at your files.





Everything has changed. Currently, systems are usually single-user, but use third-party code of unknown reliability. Now the main threat comes from the code that you run yourself.

For example, for the library in your application a new maintainer has been started (as is often the case in open source). He may be a sincere activist ... or an intruder. And if he has access to your system - for example, the ability to open any file and send it over the network - then this code can cause great damage. Suspicious application : I work for user Bob. May I open his Bitcoin wallet? Core : For Bob? Of course! Suspicious app : Great! What about network connectivity?






This is why using third-party libraries is dangerous. WebAssembly provides security in a different way - through the sandbox. Here, the code cannot talk directly to the OS. But then how to access system resources? The host (the browser or the wasm runtime) sandboxes functions that the code can use.

This means that the host programmatically limits the functionality of the program, not allowing you to simply act on behalf of the user, causing any system calls with full user rights.

Having a sandbox in itself does not make the system safe - the host can still transfer the full functionality to the sandbox, in which case it does not provide any protection. But the sandbox provides at least a theoretical opportunity for hosts to build a more secure system. WA


: Please, here are some safe toys for interacting with the OS (safe_write, safe_read).
Suspicious application : Oh damn ... where is my access to the network?


In any system interface, you must adhere to these two principles. Portability makes software development and distribution easier, and tools to protect the host and users are absolutely necessary.

What should such a system interface look like?


Given these two key principles, what should be the WebAssembly system interface?

This we will find out in the standardization process. However, we have a suggestion for a start:

  • Creating a modular set of standard interfaces
  • Let's start by standardizing the wasi-core core module.




What will be in wasi-core? These are the basics needed by all programs. The module will cover most of POSIX functionality, including files, network connections, clocks and random numbers.

Much of the basic functionality will require a very similar approach. For example, a POSIX file-oriented approach is provided with open, close, read, and write system calls, and everything else is add-ons from above.

But wasi-core does not cover all the POSIX functionality. For example, the concept of a process does not clearly fit into WebAssembly. In addition, it is clear that every WebAssembly engine must support process operations, such as fork. But we also want to make standardization possible fork. Languages ​​like Rust will use wasi-core directly in their standard libraries. For instance,



openfrom Rust is implemented when compiled into a WebAssembly call __wasi_path_open.

For C and C ++, we created wasi-sysroot , which implements libc in terms of wasi-core functions. We expect compilers like Clang to be able to interact with the WASI API, and complete tool chains like the Rust compiler and Emscripten will use WASI as part of their system implementations. How does custom code invoke these WASI functions? The runtime in which the code is executed passes the wasi-core function, placing the object in the sandbox. This provides portability, because each host can have its own wasi-core implementation specifically for its platform: from WebAssembly runtimes such as Mozilla Wasmtime and Fastly Lucet, to Node or even a browser.













It also provides reliable isolation, because the host selects on a software basis which wasi-core functions to transfer to the sandbox, that is, which system calls it should allow. This is security. WASI enhances and extends security by introducing an authorization-based security concept into the system. Usually, if the code needs to open the file, it calls with the path name in the line. Then, the OS checks whether the code has the right to such an action (based on the rights of the user who launched the program). In the case of WASI, when calling a function to access a file, you must pass a file descriptor to which permissions are attached for the file itself or for the directory containing the file. Thus, you cannot have code that accidentally asks you to open





open



/etc/passwd. Instead, code can only work with its own directories. This allows various system calls to be resolved safely to the isolated code because the capabilities of these system calls are limited. And so in each module. By default, the module does not have access to file descriptors. But if the code in one module has a file descriptor, it can pass it to functions called in other modules. Or create more limited versions of the file descriptor to pass to other functions. Thus, the runtime passes file descriptors that the application can use in top-level code, and then file descriptors are distributed throughout the rest of the system as needed.











This brings WebAssembly closer to the principle of least privilege, where the module only gets access to the minimum set of resources needed to do its job.

This concept is based on privilege-based security, as in CloudABI and Capsicum. One of the problems with these systems is the difficult portability of the code. But we believe that this problem can be solved.

If the code already uses openatwith relative file paths, compiling the code will just work.

If the code uses openand openat-style migration is too drastic, WASI will provide an incremental solution. Using libpreopen, you create a list of file paths to which the application has legal access. Then use open, but only with these paths.

What's next?


We believe wasi-core is a good start. It retains the portability and security of WebAssembly, providing a solid foundation for the ecosystem.

But after the full standardization of wasi-core, other issues need to be resolved, including:

  • asynchronous input-output
  • file monitoring
  • file lock

This is only the beginning, so if you have any ideas, get involved !

Also popular now: