WASI: una interfaz de sistemas para WebAssembly

Los programas que utilizamos en la computadora hacen uso de instrucciones para realizar operaciones tales como como escribir a un archivo, leer datos que provienen de la red, crear nuevos procesos, etc. Estas instrucciones son puestas a disposición y gobernadas por el sistema operativo con la finalidad de evitar que un programa tenga acceso indebido a ciertos recursos que no le corresponden o ejecute operaciones inseguras.

Un módulo WebAssembly no puede darse el lujo de dialogar directamente con el sistema operativo ya que por su definición no está enterado del entorno en el que está siendo ejecutado. Dicho entorno bien podría ser un sistema Linux tanto como un navegador web o incluso otra aplicación que sólo lo usa como plugin.

Para que un módulo WebAssembly pueda andar por el mundo manteniendo su promesa de portabilidad necesita de una interfaz estable y estandarizada que le permita interactuar con el sistema que lo hospeda. Esta es la motivación para crearWebAssembly System Interface o WASI.

WASI se define formalmente como una interfaz de sistema portable y segura para WASM. Permite a los módulos comunicarse con un sistema exterior que puede proveer capacidades tales como acceso a archivos, a sockets de red, a implementaciones de números aleatorios, a relojes, etc.

Tal y como WASM crea una ejecución para un entorno virtual, WASI crea una interfaz para un sistema operativo virtual. Es de esta forma que WASI intenta mantener la portabilidad y seguridad, ya que no depende del sistema operativo ni puede ejecutar funciones que no le son otorgadas.

Esto también permite otras ventajas, por ejemplo, al proveer funciones al módulo, se puede lograr que los archivos a los que accedemos residan en un contenedor aislado o en un sistema de archivos distribuídos como IPFS sin alterar la forma en que el módulo WASM funciona.

¿Cómo logra se logra esto? Pasando las funciones que el entorno provee en las funciones que el módulo WASM importa. En términos de ingeniería del software a esto se le denomina inyección de dependencias.

WASI es la manera en que un módulo WASM puede operar con su sistema anfitrión

Por fin el “Hola Mundo”

En la publicación anterior mencioné que WebAssembly sólo soporta datos numéricos en estos momentos (i32, i64, f32 y f64) y que el uso de otro tipo de datos resulta tedioso ya que deben ser representados en números y manipulados como tal. Gracias a los entornos de ejecución es posible utilizar estos otros tipos de datos, como las cadenas de caracteres, de forma transparente. Además, con la implementación de WASI, estos mismos entornos pueden proveer funciones para imprimir esta cadena de caracteres en pantalla.

A continuación realizaremos el “Hola Mundo” en Rust compilado a WebAssembly WASI. Para ejecutar el módulo utilizaremos Wasmtime, que es un proyecto de la Bytecode alliance, que implementa WASI.

// hola_mundo.rs
fn main() {
  println!("Hola Mundo");
}
# instalando wasmtime
; curl https://wasmtime.dev/install.sh -sSf | bash
# instalando el target de compilación
; rustup target add wasm32-wasi
# compilando y ejecutando
; rustc hola_mundo.rs --target wasm32-wasi
; wasmtime hola_mundo.wasm
Hola Mundo

La simpleza de la ejecución no hace evidente que wasmtime intervino para que el módulo WASM pudiera imprimir la cadena de números en memoria que representan al “Hola Mundo” en la pantalla inyectándole dichas capacidades en las funciones apropiadas.

Aparte de wasmtime también podemos usar otros entornos de ejecución que implementan WASI como wasm3 y wasmer, obteniendo el mismo resultado.

; wasm3 hola_mundo.wasm
Hola Mundo

; wasmer hola_mundo.wasm
Hola Mundo

El sistema de archivos y su seguridad

El estándar de WASI va un paso más allá y por defecto no permite interacción alguna con el sistema de archivos, que en un sistema Unix es básicamente todo. La única excepción es stdin, stdout y stderr que son las entradas y salidas estándar de un sistema.

Para poder observar este comportamiento crearemos un módulo que intenta leer el contenido de un archivo de texto.

// archivos.rs

use std::error::Error;
use std::fs::read_to_string;

fn main() -> Result<(), Box<dyn Error>> {
    let buf = read_to_string("archivo.txt")?;

    println!("Leyendo: {}", buf);

    Ok(())
}
; rustc archivos.rs --target wasm32-wasi

Si intentamos ejecutar este módulo con wastime, obtenemos un error que nos informa que no existe el acceso a dicho archivo.

; wasmtime archivos.wasm
Error: Custom { kind: Uncategorized, error: "failed to find a pre-opened file descriptor through which \"archivo.txt\" could be opened" }

En WASI necesitamos ser explícitos al otorgar los permisos sobre los archivos y directorios. Para wasmtime tenemos que pasar específicamente el directorio al que el módulo tiene accesso mediante el argumento --dir

; wasmtime --dir . archivos.wasm
Leyendo: Hola archivos

Obteniendo de esta manera el resultado esperado.

Ahora imaginemos un código foráneo que tiene intenciones oscuras de leer la información de las cuentas de usuario de un sistema UNIX

// archivos_malefico.rs

use std::error::Error;
use std::fs::read_to_string;

fn main() -> Result<(), Box<dyn Error>> {
    let buf = read_to_string("../../../../../etc/passwd")?;

    println!("Leyendo: {}", buf);

    Ok(())
}

Incluso al intentar ejecutarlo con permisos de súper usuario obtenemos:

; sudo wasmtime --dir . archivos.wasm
Password:
Error: Os { code: 63, kind: PermissionDenied, message: "Operation not permitted" }

Cumpliento efectivamente con el estándar de seguridad definido por WASI.
Nota: si desean pueden comparar resultados compilando y ejecutando el código de arriba de forma normal mediante rustc archivos_malefico.rs.

La evolución de WASI

El estándar WASI está en sus inicios y por el momento mantiene la atención en WASI Core, que es una API que provee funciones para ser capaz de operar con archivos, sockets, números aleatorios y relojes del sistema.

Las propuestas para el estándar son abiertamente discutidas en Github. Personalmente encuentro entre las más interesantes la posibilidad de crear hilos, la posibilidad de hacer uso de las funciones criptográficas del anfitrión y una interfaz para usar redes neuronales.

Conclusión

WASI es un estándar que le permite a un módulo WASM mantener su promesa de portabilidad y seguridad al ofrecerle una interfaz estable con la cual puede interactuar con el sistema anfitrión. Esta interacción se logra mediante funciones que proveen acceso a ciertos recursos del sistema.

Este modelo, que opera a través de la máquina virtual de WebAssembly, es totalmente independiente de su entorno y no es capaz de ejecutar funciones que no le han sido otorgadas.

El entorno de ejecución que implementa WASI es el que se encarga de otorgar a un módulo WASM las funciones que requiere para imprimir a pantalla, abrir archivos, abrir sockets de red, etc. Entre los entornos de ejecución se encuentran wasmtime, wasm3 y wasmer.

WASI se encuentra en estos momentos en su versión inicial y lleva su evolución mediante propuestas discutidas abiertamente en Github.

Referencias

https://wasi.dev/

https://wasmtime.dev/

https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-intro.md

https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/

WebAssembly: the definitive guide

Write a comment