Хабрахабр

[Перевод] Стандарт WASI: запуск WebAssembly за пределами веба

27 марта мы в Mozilla объявили о начале стандартизации WASI, системного интерфейса WebAssembly (WebAssembly system interface).

Но у нас пока нет прочного фундамента для такой разработки. Зачем: разработчики начали применять WebAssembly за пределам браузера, потому что WASM обеспечивает быстрый, масштабируемый, безопасный способ запуска одинакового кода на всех машинах. А у платформы WebAssembly его пока нет. Вне браузера нужен некий способ общения с системой, то есть системный интерфейс.

Он работает на различных архитектурах, поэтому и системный интерфейс нужен для концептуальной ОС, чтобы работать на разных операционных системах. Что: WebAssembly — это ассемблер для концептуальной, а не физической машины.

Вот что такое WASI: это системный интерфейс для платформы WebAssembly.
Мы стремимся создать системный интерфейс, который станет настоящим компаньоном для WebAssembly с максимальной переносимостью и безопасностью.

Мы уже собрали заинтересованных партнёров и ищем новых. Кто: в рамках группы разработки WebAssembly мы организовали подгруппу, которая займётся стандартизацией WASI.

Вот некоторые причины, по которым мы, наши партнёры и сторонники считаем это важным:

Шон Уайт, директор Mozilla по R&D:

До сих пор всё работало через браузеры, но с WASI преимущества WebAssembly получат больше пользователей и больше устройств в разных местах». «WebAssembly уже меняет способы доставки людям новых видов привлекательного контента, Он помогает разработчикам и создателям контента.

Тайлер МакМаллен, технический директор Fastly:

Несмотря на разную среду (edge и браузеры), благодаря WASI не придётся портировать код на каждую платформу». «Мы рассматриваем WebAssembly как платформу для быстрого и безопасного выполнения кода на граничном облаке (edge cloud).

Майлз Боринс, технический директор руководящего комитета Node:

Стандартизация WASI является первым шагом к этому». «WebAssembly может решить одну из самых больших проблем Node: как добиться близкой к нативной скорости и повторно использовать код, написанный на других языках, таких как C и C++, сохраняя портативность и безопасность.

Лори Восс, соучредитель npm:

Мы с нетерпением ожидаем результатов этого процесса». «npm чрезвычайно взволнован потенциальными возможностями WebAssembly для экосистемы npm, поскольку значительно упрощается получение нативного кода для запуска в серверных приложениях JavaScript.

Так то это большое событие!

В настоящее время есть три реализации WASI:

Демонстрация WASI в действии:

Дальше мы расскажем о предложении Mozilla, как должен работать этот системный интерфейс.
Многие говорят, что языки вроде C дают прямой доступ к системным ресурсам. Но это не совсем так. В большинстве систем эти языки не имеют прямого доступа к таким вещам, как открытие или создание файлов. Почему нет?

Потому что эти системные ресурсы — файлы, память и сетевые подключения — слишком важны для стабильности и безопасности.

Хуже того, если программа (или пользователь) специально вторгается в чужие ресурсы, то может украсть конфиденциальные данные. Если одна программа случайно испортит ресурсы другой, то может вызвать сбой.

Системные разработчики довольно давно придумали способ обеспечить такой контроль: кольца защиты. Поэтому нужен способ контролировать, какие программы и пользователи могут получить доступ к ресурсам.

Это ядро. С кольцами защиты ОС по сути устанавливает защитный барьер вокруг системных ресурсов. Только оно может выполнять такие операции, как создание файла, открытие файла или открытие сетевого подключения.

Если программа хочет открыть файл, она должна попросить ядро. Программы пользователя работают вне ядра в так называемом пользовательском пространстве.

Когда программе нужно попросить ядро о какой-то операции, она отправляет системный вызов. Именно здесь возникает концепция системного вызова. Ядро проверяет обратившегося пользователя и видит, есть ли у него права на доступ к этому файлу.

На большинстве устройств единственный способ доступа к ресурсам системы — через системные вызовы.

Но если у каждой ОС собственные системные вызовы, разве для них не нужно писать разные версии кода? Операционная система предоставляет доступ к системным вызовам. Проблема решается с помощью абстракции. К счастью, нет.

При кодировании программисту не нужно знать, для какой системы он пишет. В большинстве языков есть стандартная библиотека. Затем, при компиляции, ваша цепочка инструментов выбирает, какую реализацию интерфейса использовать для какой системы. Он просто использует интерфейс. Эта реализация использует функции из API операционной системы, поэтому она специфична для неё.

Например, если компилировать printf для машины Windows, он будет использовать Windows API. Вот где появляется понятие системного интерфейса. Если компилировать для Mac или Linux, он использует POSIX.

Здесь мы не знаем, для какой ОС оптимизировать программу даже во время компиляции. Однако это создаёт проблему для WebAssembly. Таким образом, вы не можете использовать системный интерфейс какой-то одной ОС внутри реализации стандартной библиотеки на WebAssembly.

Точно так же WebAssembly нуждается в системном интерфейсе для концептуальной, а не реальной ОС. Я уже говорила, что WebAssembly — это ассемблер для концептуальной машины, а не реальная машина.

Как они это делают? Но уже есть среды выполнения, которые могут запускать WebAssembly вне браузера, даже без этого системного интерфейса. Давайте посмотрим.

Первым инструментом для генерации кода WebAssembly был Emscripten. Он эмулирует в вебе определённый системный интерфейс ОС — POSIX. Это означает, что программист может использовать функции из стандартной библиотеки C (libc).

Она разделена на две части: первая скомпилирована в модуль WebAssembly, а другая реализована в коде «JS-клея». Для этого Emscripten пользуется собственной реализацией libc. Этот JS-клей отправляет вызовы браузеру, который разговаривает с ОС.

Поэтому, когда люди начали хотеть запускать WebAssembly без браузера, то начали запускать код Emscripten. Основная часть раннего кода WebAssembly скомпилирована с Emscripten.

Так что в этих рантаймах следовало создать собственные реализации для всех функций, которые были в коде JS-клея.

Интерфейс, предоставляемый кодом клея JS, не был разработан как стандартный или даже общедоступный интерфейс. Но тут есть проблема. Например, для вызова вроде read в нормальном API код JS-клея использует вызов _system3(which, varargs).

Первый параметр which — целое число, которое всегда совпадает с числом в имени (в нашем случае 3).

Он называется varargs, потому что у нас может быть разное число аргументов. Второй параметр varargs перечисляет аргументы. Поэтому они передаются через линейную память, что нетипобезопасно и медленнее, чем через регистры. Но WebAssembly не позволяет передать функции переменное количество аргументов.

Но теперь среды выполнения рассматривают это как стандарт де-факто, реализуя собственные версии JS-клея. Для Emscripten в браузере это нормально. Они эмулируют внутренние детали эмуляционного слоя POSIX.

Это означает, что они заново реализуют код (например, передают аргументы как значения кучи), что имело смысл с учётом ограничений Emscripten, но в этих средах выполнения нет таких ограничений.

Это означает, что наш фактический стандарт не может быть эмуляцией эмуляции. Если мы строим экосистему WebAssembly на десятилетия вперёд, для неё нужен прочный фундамент, а не костыли.

Но какие принципы применить а таком случае?

Два основополагающих принципа WebAssembly:

  • портируемость
  • безопасность

Мы выходим за пределы браузера, но сохраняем эти ключевые принципы.

Давайте посмотрим, в чём проблема. Однако подход POSIX и система управления доступом Unix не даёт нам искомый результат.

Портируемость

POSIX обеспечивает переносимость исходного кода. Вы можете скомпилировать один и тот же исходный код с разными версиями libc для разных компьютеров.

Нам нужно компилировать однажды для запуска на целой куче разных систем. Но WebAssembly должен выйти за эти рамки. Нам нужны портируемые бинарники.

Это упрощает распространение кода.

Например, если нативные модули Node написаны в WebAssembly, то пользователям не нужно запускать node-gyp при установке приложений с нативными модулями, а разработчикам не нужно настраивать и распространять десятки бинарных файлов.

Безопасность

Когда код просит операционную систему сделать ввод или вывод, ОС должна оценить безопасность этой операции, обычно с помощью системы управления доступом, основанной на владении и группах.

У пользователя есть определённый набор файлов, к которым он имеет доступ. Например, программа просит открыть файл.

Если пользователь имеет доступ к файлу — либо он его владелец, либо входит в группу, имеющую доступ к файлу, — то у программы такой же доступ. Когда пользователь запускает программу, программа запускается от имени этого пользователя.

Тогда главной угрозой были другие пользователи, заглядывающие в ваши файлы. Это защищает пользователей друг от друга, что имело смысл в старые времена, когда на одном компьютере работало много людей, а администраторы контролировали программное обеспечение.

В настоящее время системы, как правило, являются однопользовательскими, но используют сторонний код неизвестной надёжности. Всё изменилось. Сейчас основная угроза исходит от кода, который вы сами запускаете.

Он может быть искренним активистом… или злоумышленником. Например, для библиотеки в вашем приложении завёлся новый мейнтейнер (как часто бывает в open source). И если у него появляется доступ к вашей системе — например, возможность открыть любой файл и отправить его по сети — тогда этот код может нанести большой ущерб.

Можно мне открыть его кошелёк Bitcoin?
Ядро: Для Боба?
Подозрительное приложение: Я работаю для пользователя Боб. А что насчёт сетевого подключения? Конечно!
Подозрительное приложение: Отлично!

В WebAssembly безопасность обеспечивается иначе — через песочницу. Вот почему опасно использование сторонних библиотек. Но как тогда обращаться к системным ресурсам? Здесь код не может напрямую разговаривать с ОС. Хост (браузер или среда выполнения wasm) помещает в песочницу функции, которые код может использовать.

Это означает, что хост на программной основе ограничивает функциональность программы, не позволяя просто действовать от имени юзера, вызывая любые системные вызовы с полными правами пользователя.

Но песочница даёт хотя бы теоретическую возможность хостам построить более безопасную систему. Наличие песочницы само по себе не делает систему безопасной — хост всё ещё может передать в песочницу полную функциональность, и в этом случае она не обеспечивает никакой защиты.


WA: Пожалуйста, вот тебе несколько безопасных игрушек для взаимодействия с ОС (safe_write, safe_read).
Подозрительное приложение: Ох блин… а где ж мой доступ к сети?

Переносимость облегчает разработку и распространение программного обеспечения, а инструменты для защиты хоста и пользователей абсолютно необходимы. В любом системном интерфейсе нужно придерживаться этих двух принципов.

Учитывая эти два ключевых принципа, каким должен быть системный интерфейс WebAssembly?

Впрочем, у нас есть предложение для начала: Это мы выясним в процессе стандартизации.

  • Создание модульного набора стандартных интерфейсов
  • Начнём со стандартизации основного модуля wasi-core

Это основы, необходимые всем программам. Что будет в wasi-core? Модуль покроет большую часть функциональности POSIX, включая файлы, сетевые подключения, часы и случайные числа.

Например, предусмотрен файл-ориентированный подход POSIX с системными вызовами open, close, read и write, а всё остальное — дополнения сверху. Многое из базовой функциональности потребует очень похожего подхода.

Например, концепция процесса чётко не укладывается в WebAssembly. Но wasi-core не охватит всю функциональность POSIX. Но мы также хотим сделать возможным стандартизацию fork. Кроме того, совершенно очевидно, что каждый движок WebAssembly должен поддерживать операции процесса, такие как fork.

Например, open из Rust реализуется при компиляции в WebAssembly вызовом __wasi_path_open. Языки вроде Rust будут использовать wasi-core непосредственно в своих стандартных библиотеках.

Для C и C++ мы создали wasi-sysroot, который реализует libc в терминах функций wasi-core.

Мы ожидаем, что компиляторы, такие как 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 с относительными путями файлов, компиляция кода будет просто работать.

С помощью libpreopen вы создаёте список путей к файлам, к которым приложение имеет законный доступ. Если код использует open и миграция в стиле openat слишком резкая, WASI предоставит инкрементное решение. Затем используете open, но только с этими путями.

Мы считаем, что wasi-core — хорошее начало. Он сохраняет портируемость и безопасность WebAssembly, обеспечивая прочную основу для экосистемы.

Но после полной стандартизации wasi-core нужно решить другие вопросы, в том числе:

  • асинхронный ввод-вывод
  • наблюдение за файлами
  • блокировка файлов

Это только начало, так что если у вас есть идеи, подключайтесь к работе!

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»