Le 27 mars, à Mozilla, nous avons annoncé la standardisation de WASI, l'interface système WebAssembly (interface système WebAssembly).
Pourquoi: les développeurs ont commencé à utiliser WebAssembly en dehors du navigateur, car WASM fournit un moyen rapide, évolutif et sécurisé pour exécuter le même code sur toutes les machines. Mais nous n'avons pas encore de bases solides pour un tel développement. En dehors du navigateur, vous avez besoin d'un moyen de communiquer avec le système, c'est-à-dire l'interface système. Mais la plateforme WebAssembly ne l'a pas encore.
Quoi: WebAssembly est un assembleur pour une machine conceptuelle plutôt que physique. Il fonctionne sur diverses architectures, par conséquent, une interface système est nécessaire pour qu'un système d'exploitation conceptuel fonctionne sur différents systèmes d'exploitation.
Voici ce qu'est WASI: c'est une interface système pour la plate-forme WebAssembly.
Nous nous efforçons de créer une interface système qui deviendra un véritable compagnon pour WebAssembly avec une portabilité et une sécurité maximales.
Qui: Dans le cadre de l'équipe de développement WebAssembly, nous avons organisé un sous-groupe qui normalisera
WASI . Nous avons déjà rassemblé des partenaires intéressés et recherchons de nouveaux.
Voici quelques raisons pour lesquelles nous, nos partenaires et supporters considérons cela comme important:
Sean White, directeur R&D de Mozilla:"WebAssembly modifie déjà la façon dont les gens proposent de nouveaux types de contenu attrayant. Il aide les développeurs et les créateurs de contenu. Jusqu'à présent, tout a fonctionné via les navigateurs, mais avec WASI, plus d'utilisateurs et plus d'appareils à différents endroits bénéficieront de WebAssembly. »
Tyler McMullen, CTO rapidement:«Nous considérons WebAssembly comme une plate-forme pour exécuter rapidement et en toute sécurité du code sur un cloud périphérique. Malgré les différents environnements (périphérie et navigateurs), grâce à WASI, vous n'avez pas à porter le code sur chaque plate-forme. "
Miles Borins, CTO du Node Steering Committee:«WebAssembly peut résoudre l'un des plus gros problèmes de Node: comment atteindre une vitesse quasi-native et réutiliser du code écrit dans d'autres langages tels que C et C ++, tout en maintenant la portabilité et la sécurité. La normalisation WASI est la première étape vers cela. »
Lori Voss, co-fondatrice de npm:«Npm est extrêmement enthousiasmé par le WebAssembly potentiel pour l'écosystème npm, car il facilite beaucoup l'exécution du code natif dans les applications JavaScript côté serveur. Nous attendons avec impatience les résultats de ce processus. »
C'est donc un grand événement!
Il existe actuellement trois implémentations WASI:
Démonstration WASI en action:
Ensuite, nous parlerons de la proposition de Mozilla sur le fonctionnement de cette interface système.
Qu'est-ce qu'une interface système?
Beaucoup disent que des langages comme C fournissent un accès direct aux ressources système. Mais ce n'est pas tout à fait vrai. Sur la plupart des systèmes, ces langues n'ont pas d'accès direct à des éléments tels que l'ouverture ou la création de fichiers. Pourquoi pas?
Parce que ces ressources système - fichiers, mémoire et connexions réseau - sont trop importantes pour la stabilité et la sécurité.
Si un programme ruine accidentellement les ressources d'un autre, il peut provoquer un plantage. Pire, si un programme (ou un utilisateur) envahit spécifiquement les ressources d'autres personnes, il peut voler des données sensibles.

Par conséquent, vous avez besoin d'un moyen de contrôler quels programmes et utilisateurs peuvent accéder aux ressources. Pendant longtemps, les développeurs de systèmes ont trouvé un moyen de fournir un tel contrôle: des anneaux de protection.
Avec des anneaux de protection, le système d'exploitation établit essentiellement une barrière de protection autour des ressources système. Ceci est le noyau. Elle seule peut effectuer des opérations telles que la création d'un fichier, l'ouverture d'un fichier ou l'ouverture d'une connexion réseau.
Les programmes utilisateur s'exécutent en dehors du noyau dans ce qu'on appelle l'espace utilisateur. Si le programme veut ouvrir le fichier, il devrait demander le noyau.

C'est là que le concept d'un appel système se pose. Lorsqu'un programme doit demander au noyau une opération, il envoie un appel système. Le noyau vérifie l'utilisateur qui contacte et voit s'il a l'autorisation d'accéder à ce fichier.
Sur la plupart des appareils, le seul moyen d'accéder aux ressources système est via les appels système.

Le système d'exploitation permet d'accéder aux appels système. Mais si chaque système d'exploitation a ses propres appels système, n'a-t-il pas besoin d'écrire différentes versions du code? Heureusement non. Le problème est résolu en utilisant l'abstraction.
La plupart des langues ont une bibliothèque standard. Lors du codage, le programmeur n'a pas besoin de savoir pour quel système il écrit. Il utilise simplement l'interface. Ensuite, lors de la compilation, votre chaîne d'outils choisit quelle implémentation d'interface utiliser pour quel système. Cette implémentation utilise des fonctions de l'API du système d'exploitation, elle lui est donc spécifique.
C'est là que le concept d'interface système apparaît. Par exemple, si vous compilez
printf
pour une machine Windows, il utilisera l'API Windows. S'il est compilé pour Mac ou Linux, il utilise POSIX.

Cependant, cela pose un problème pour WebAssembly. Ici, nous ne savons pas pour quel OS optimiser le programme même pendant la compilation. Ainsi, vous ne pouvez pas utiliser l'interface système d'un système d'exploitation unique dans l'implémentation de la bibliothèque standard sur WebAssembly.

J'ai déjà dit que WebAssembly est un
assembleur pour une machine conceptuelle , pas une vraie machine. De même, WebAssembly a besoin d'une interface système pour un système d'exploitation conceptuel plutôt que réel.
Mais il existe déjà des runtimes qui peuvent exécuter WebAssembly en dehors du navigateur, même sans cette interface système. Comment font-ils? Voyons voir.
Comment WebAssembly fonctionne-t-il maintenant en dehors du navigateur?
Le premier outil pour générer du code WebAssembly était Emscripten. Il émule sur le Web une interface système OS spécifique - POSIX. Cela signifie que le programmeur peut utiliser les fonctions de la bibliothèque C standard (libc).
Pour cela, Emscripten utilise sa propre implémentation libc. Il est divisé en deux parties: la première est compilée dans un module WebAssembly, et l'autre est implémentée en code JS-glue. Cette colle JS envoie des appels au navigateur qui parle au système d'exploitation.

La plupart des premiers codes WebAssembly sont compilés avec Emscripten. Par conséquent, lorsque les gens ont commencé à vouloir exécuter WebAssembly sans navigateur, ils ont commencé à exécuter du code Emscripten.
Donc, dans ces runtimes, vous devez créer vos propres implémentations pour toutes les fonctions qui étaient dans le code JS-glue.
Mais il y a un problème. L'interface fournie par le code de colle JS n'a pas été conçue comme une interface standard ou même publique. Par exemple, pour appeler comme
read
dans l'API normale, le code de collage JS utilise l'appel
_system3(which, varargs)
.

Le premier paramètre
which
est un entier qui correspond toujours au nombre dans le nom (dans notre cas 3).
Le deuxième paramètre,
varargs
répertorie les arguments. On l'appelle
varargs
car nous pouvons avoir un nombre d'arguments différent. Mais WebAssembly ne permet pas de passer un nombre variable d'arguments à une fonction. Par conséquent, ils sont transmis via une mémoire linéaire, ce qui est dangereux et plus lent que via des registres.
Pour Emscripten dans le navigateur, c'est normal. Mais maintenant les runtimes voient cela comme une norme de facto, implémentant leurs propres versions de colle JS. Ils émulent les détails internes de la couche d'émulation POSIX.
Cela signifie qu'ils réimplémentent le code (par exemple, passent des arguments en tant que valeurs de segment de mémoire), ce qui était logique compte tenu des contraintes Emscripten, mais il n'y a pas de telles contraintes dans ces environnements d'exécution.

Si nous construisons l'écosystème WebAssembly depuis des décennies, il a besoin d'une base solide, pas de béquilles. Cela signifie que notre standard réel ne peut pas être l'émulation d'émulation.
Mais quels principes s'appliquent dans ce cas?
À quels principes l'interface du système WebAssembly doit-elle adhérer?
Deux principes fondamentaux de WebAssembly:
Nous allons au-delà du navigateur, mais conservons ces principes clés.
Cependant, l'approche POSIX et le système de contrôle d'accès Unix ne nous donnent pas le résultat souhaité. Voyons quel est le problème.
La portabilité
POSIX fournit la portabilité du code source. Vous pouvez compiler le même code source avec différentes versions de libc pour différents ordinateurs.

Mais WebAssembly doit aller au-delà. Nous devons compiler une fois pour exécuter sur tout un tas de systèmes différents. Nous avons besoin de binaires portables.

Cela simplifie la distribution du code.
Par exemple, si les modules Node natifs sont écrits dans WebAssembly, les utilisateurs n'ont pas besoin d'exécuter node-gyp lors de l'installation d'applications avec des modules natifs, et les développeurs n'ont pas besoin de configurer et de distribuer des dizaines de fichiers binaires.
La sécurité
Lorsque le code demande au système d'exploitation de faire des entrées ou des sorties, le système d'exploitation doit évaluer la sécurité de cette opération, en utilisant généralement un système de contrôle d'accès basé sur la propriété et les groupes.
Par exemple, un programme demande d'ouvrir un fichier. L'utilisateur dispose d'un ensemble spécifique de fichiers auxquels il a accès.
Lorsqu'un utilisateur démarre un programme, le programme démarre au nom de cet utilisateur. Si l'utilisateur a accès au fichier - qu'il soit son propriétaire ou qu'il fasse partie d'un groupe qui a accès au fichier - alors le programme a le même accès.

Cela protège les utilisateurs les uns des autres, ce qui était logique dans le passé, lorsque beaucoup de personnes travaillaient sur un ordinateur et que les administrateurs contrôlaient le logiciel. Ensuite, la principale menace était que d'autres utilisateurs consultent vos fichiers.
Tout a changé. Actuellement, les systèmes sont généralement à utilisateur unique, mais utilisent un code tiers de fiabilité inconnue. Maintenant, la principale menace vient du code que vous exécutez vous-même.
Par exemple, pour la bibliothèque de votre application, un nouveau mainteneur a été démarré (comme c'est souvent le cas en open source). Il peut être un activiste sincère ... ou un intrus. Et s'il a accès à votre système - par exemple, la possibilité d'ouvrir n'importe quel fichier et de l'envoyer sur le réseau - alors ce code peut causer de gros dégâts.
Application suspecte : je travaille pour l'utilisateur Bob. Puis-je ouvrir son portefeuille Bitcoin?
Noyau : pour Bob? Bien sûr!
Application suspecte : génial! Et la connectivité réseau?C'est pourquoi l'utilisation de bibliothèques tierces est dangereuse. WebAssembly offre une sécurité différente - via le bac à sable. Ici, le code ne peut pas parler directement au système d'exploitation. Mais alors comment accéder aux ressources système? Les fonctions sandbox de l'hôte (le navigateur ou le wasm runtime) que le code peut utiliser.
Cela signifie que l'hôte limite par programmation les fonctionnalités du programme, ne vous permettant pas simplement d'agir au nom de l'utilisateur, provoquant des appels système avec des droits d'utilisateur complets.
Le fait d'avoir un bac à sable en soi ne rend pas le système sûr - l'hôte peut toujours transférer toutes les fonctionnalités au bac à sable, auquel cas il ne fournit aucune protection. Mais le bac à sable fournit au moins une opportunité théorique pour les hôtes de construire un système plus sécurisé.
WA : S'il vous plaît, voici quelques jouets sûrs pour interagir avec le système d'exploitation (safe_write, safe_read).
Application suspecte : Oh putain ... où est mon accès au réseau?Dans toute interface système, vous devez respecter ces deux principes. La portabilité facilite le développement et la distribution de logiciels, et des outils pour protéger l'hôte et les utilisateurs sont absolument nécessaires.
À quoi devrait ressembler une telle interface système?
Compte tenu de ces deux principes clés, quelle devrait être l'interface du système WebAssembly?
Nous le découvrirons dans le processus de normalisation. Cependant, nous avons une suggestion pour commencer:
- Création d'un ensemble modulaire d'interfaces standard
- Commençons par standardiser le module de base wasi-core.

Qu'y aura-t-il dans wasi-core? Ce sont les bases nécessaires à tous les programmes. Le module couvrira la plupart des fonctionnalités POSIX, y compris les fichiers, les connexions réseau, les horloges et les nombres aléatoires.
Une grande partie des fonctionnalités de base nécessitera une approche très similaire. Par exemple, une approche orientée fichier POSIX est fournie avec des appels système ouverts, fermés, lus et écrits, et tout le reste est composé de modules complémentaires par le haut.
Mais wasi-core ne couvre pas toutes les fonctionnalités POSIX. Par exemple, le concept de processus ne rentre pas clairement dans WebAssembly. De plus, il est clair que chaque moteur WebAssembly doit prendre en charge les opérations de processus telles que
fork
. Mais nous voulons également rendre possible la standardisation des
fork
.

Des langages comme Rust utiliseront wasi-core directement dans leurs bibliothèques standard. Par exemple,
open
from Rust est implémenté lors de la compilation dans WebAssembly en appelant
__wasi_path_open
.
Pour C et C ++, nous avons créé
wasi-sysroot , qui implémente libc en termes de fonctions wasi-core.

Nous nous attendons à ce que les compilateurs comme Clang puissent interagir avec l'API WASI, et des chaînes d'outils complètes comme le compilateur Rust et Emscripten utiliseront WASI dans le cadre de leurs implémentations système.
Comment le code personnalisé appelle-t-il ces fonctions WASI?
Le runtime dans lequel le code est exécuté passe la fonction wasi-core, plaçant l'objet dans le bac à sable.

Cela offre une portabilité, car chaque hôte peut avoir sa propre implémentation wasi-core spécifiquement pour sa plate-forme: des runtimes WebAssembly tels que Mozilla Wasmtime et Fastly Lucet, à Node ou même à un navigateur.
Il fournit également une isolation fiable, car l'hôte sélectionne sur une base logicielle les fonctions de base à transférer vers le bac à sable, c'est-à-dire les appels système qu'il doit autoriser. C'est ça la sécurité.

WASI améliore et étend la sécurité en introduisant un concept de sécurité basé sur l'autorisation dans le système.
Habituellement, si le code doit ouvrir le fichier, il appelle
open
avec le nom du chemin dans la ligne. Ensuite, le système d'exploitation vérifie si le code a droit à une telle action (sur la base des droits de l'utilisateur qui a lancé le programme).
Dans le cas de WASI, lorsque vous appelez une fonction pour accéder à un fichier, vous devez passer un descripteur de fichier auquel des autorisations sont attachées pour le fichier lui-même ou pour le répertoire contenant le fichier.
Ainsi, vous ne pouvez pas avoir de code qui vous demande accidentellement d'ouvrir
/etc/passwd
. Au lieu de cela, le code ne peut fonctionner qu'avec ses propres répertoires.

Cela permet de résoudre en toute sécurité divers appels système vers le code isolé car les capacités de ces appels système sont limitées.
Et donc dans chaque module. Par défaut, le module n'a pas accès aux descripteurs de fichiers. Mais si le code d'un module possède un descripteur de fichier, il peut le transmettre à des fonctions appelées dans d'autres modules. Ou créez des versions plus limitées du descripteur de fichier pour passer à d'autres fonctions.
Ainsi, le runtime transmet les descripteurs de fichiers que l'application peut utiliser dans le code de niveau supérieur, puis les descripteurs de fichiers sont distribués dans le reste du système selon les besoins.

Cela rapproche WebAssembly du principe du moindre privilège, où le module n'a accès qu'à l'ensemble minimal de ressources nécessaires pour faire son travail.
Ce concept est basé sur la sécurité basée sur les privilèges, comme dans CloudABI et Capsicum. L'un des problèmes de ces systèmes est la portabilité difficile du code. Mais nous pensons que ce problème peut être résolu.
Si le code utilise déjà
openat
avec des chemins de fichier relatifs, la compilation du code fonctionnera simplement.
Si le code utilise la migration
open
et de style openat est trop drastique, WASI fournira une solution incrémentielle. À l'aide de
libpreopen, vous créez une liste de chemins de fichiers auxquels l'application a un accès légal. Utilisez ensuite
open
, mais uniquement avec ces chemins.
Et ensuite?
Nous pensons que wasi-core est un bon début. Il conserve la portabilité et la sécurité de WebAssembly, fournissant une base solide pour l'écosystème.
Mais après la normalisation complète de wasi-core, d'autres problèmes doivent être résolus, notamment:
- entrée-sortie asynchrone
- surveillance des fichiers
- verrou de fichier
Ce n'est qu'un début, alors si vous avez des idées,
impliquez-vous !