Суб-миллисекундные VM-песочницы на основе copy-on-write
Как fork памяти с copy-on-write позволяет поднимать VM-песочницы меньше чем за миллисекунду и меняет serverless и изоляцию безопасности.

Запуск Docker-контейнера занимает около 500 миллисекунд. Firecracker microVM — около 125 миллисекунд. V8-изолят — около 5 миллисекунд. Но новое поколение легковесных песочниц, использующих copy-on-write для форка памяти, способно поднять изолированную среду выполнения меньше чем за 1 миллисекунду — часто в диапазоне 50–200 микросекунд. Этого достаточно, чтобы создавать свежую песочницу для каждого отдельного вызова функции.
Это не просто инкрементальное улучшение. Это качественный сдвиг в том, что могут песочницы. Когда создание песочницы стоит 500 мс, их создают изредка и переиспользуют. Когда оно стоит 50 мкс, их создают для каждого недоверенного входа, каждого вызова плагина, каждого пользовательского запроса. Модель безопасности смещается от «изоляции арендаторов» к «изоляции отдельных операций».
Что на самом деле означает copy-on-write
Copy-on-write (CoW) — это техника операционной системы, при которой создаётся «копия» области памяти без фактического копирования данных. И оригинал, и копия ссылаются на одни и те же физические страницы памяти, помеченные как только для чтения. Они неотличимы — оба видят одни и те же данные. Реальное копирование происходит только тогда, когда одна из сторон пытается записать в страницу. В этот момент ядро перехватывает запись, копирует только эту одну страницу и выполняет запись уже в копии.
Системный вызов fork() в Unix использует этот механизм с 1990-х годов. Когда вы делаете fork процесса, потомок получает полную копию памяти родителя — но благодаря CoW фактически ничего не копируется. Если потомок сразу вызывает exec() (что типично), он полностью заменяет свою память, и страницы CoW просто освобождаются. Fork обходился почти бесплатно.
// Classic fork() — copy-on-write in action
pid_t pid = fork();
// At this point:
// - Parent and child share ALL physical memory pages
// - Both have identical virtual address spaces
// - Pages are marked read-only in both
// - Zero data has been copied
if (pid == 0) {
// Child process
// Reading shared_data is free — same physical page
int value = shared_data[0];
// Writing triggers CoW — only THIS page gets copied
shared_data[0] = 42;
// Now child has its own copy of this one page
// Parent's page is unchanged
}
От fork к песочнице
Ключевая идея CoW-песочниц такова: вместо того чтобы загружать свежую VM или контейнер с нуля, заранее поднимается «шаблонное» окружение (с загруженными рантаймом, библиотеками и начальным состоянием), а затем оно форкается через CoW, создавая мгновенные копии. Каждая копия стартует ровно в том состоянии, в котором остановился шаблон — полностью инициализированной и готовой к выполнению, — но работает в собственном изолированном адресном пространстве.
Разница в производительности огромна. Традиционный запуск VM включает: загрузку ядра, инициализацию оборудования, монтирование файловых систем, запуск init-системы, загрузку кода приложения, инициализацию рантайма. Даже при агрессивной оптимизации (Firecracker существенно её упрощает) вы всё равно тратите сотни миллисекунд на инициализацию.
CoW-форк всё это пропускает. Шаблон уже выполнил инициализацию. Форк создаёт предынициализированную копию за микросекунды. «Стоимость запуска» — это лишь служебная работа ядра по созданию нового адресного пространства и дублированию записей таблицы страниц — несколько тысяч операций независимо от того, сколько памяти использует шаблон.
Где это меняет правила игры
Serverless-функции
Холодные старты — бич serverless-вычислений. AWS Lambda тратит 100–500 мс на холодный старт, а для рантаймов на основе JVM — ещё больше. Это неприемлемо для latency-чувствительных нагрузок, из-за чего пользователям приходится держать инстансы «прогретыми» (что сводит на нет смысл serverless) или мириться с непредсказуемой задержкой.
С CoW-песочницами холодный старт падает до субмиллисекундного уровня. Каждый вызов может быть «холодным стартом», потому что такой старт практически бесплатен. Не нужны пулы прогретых инстансов, нет траты памяти на простаивающие экземпляры, нет устаревшего состояния между вызовами. Каждое выполнение функции получает чистое изолированное окружение без платы за инициализацию.
Системы плагинов и расширений
Безопасный запуск недоверенных плагинов — одна из самых сложных задач в разработке ПО. Браузеры решили её для JavaScript с помощью изолятов V8. Но для произвольного кода — скомпилированных расширений, скриптовых языков, бинарных плагинов — варианты изоляции ограничивались контейнерами (слишком медленными для изоляции на каждый запрос) или WebAssembly (ограниченная экосистема и поддержка языков).
CoW-песочницы предлагают промежуточный путь: выполнять произвольный код в изолированной копии окружения хоста с настройкой и уничтожением за субмиллисекунды. Плагин видит полноценное окружение ОС (файловую систему, сеть, библиотеки), но его изменения изолированы — когда песочница завершается, все изменения исчезают. Это идеально подходит для пользовательского кода в CI-системах, ноутбуках и инструментах сборки.
Изоляция для безопасности
При обработке недоверенных данных — разборе загруженного PDF, рендеринге пользовательского HTML, выполнении запроса к базе — запуск операции в изолированной песочнице ограничивает радиус поражения любого эксплойта. Если в парсере PDF найдётся переполнение буфера, злоумышленник получит контроль над одноразовой песочницей, которая вот-вот будет уничтожена, а не над сервером приложения.
Такой подход — изоляция процессов для каждой недоверенной операции — раньше был непрактичен в традиционных песочницах, потому что накладные расходы превышали время обработки. Если разбор PDF занимает 10 мс, тратить 500 мс на создание контейнера не имеет смысла. Но тратить 100 мкс на создание CoW-песочницы — пренебрежимо дёшево.
Детали реализации
Построение практичной CoW-системы песочниц требует решения нескольких задач помимо простого вызова fork().
- Учёт памяти. CoW делает использование памяти неоднозначным. Если шаблон занимает 1 ГБ, и вы форкаете 100 копий, каждая из которых меняет 10 МБ, физическое потребление составит ~2 ГБ (1 ГБ общей памяти + 100 × 10 МБ уникальной), а не 100 ГБ. Ядро учитывает общие и приватные страницы, но для точного потребления по каждой песочнице нужно разбирать
/proc/[pid]/smaps. - Изоляция файловой системы. CoW решает задачу с памятью, но записи в файловую систему требуют отдельной изоляции. Оверлейные файловые системы (overlayfs) обеспечивают семантику CoW для файлов: песочница видит файловую систему шаблона, но записи идут в отдельный слой. При завершении песочницы оверлей удаляется.
- Изоляция сети. Каждой песочнице нужно собственное сетевое пространство имён, чтобы исключить взаимное влияние. Пространства имён Linux обеспечивают это, но их создание имеет измеримые накладные расходы. Некоторые системы переиспользуют пул заранее созданных namespace.
- Ограничения ресурсов. Песочница, которая выделяет неограниченную память или потребляет безлимитный CPU, — это вектор отказа в обслуживании. cgroups предоставляют лимиты (память, CPU, I/O), но создание и уничтожение cgroup добавляет накладных расходов. Опять же, пулинг помогает.
- Детерминированная очистка. Когда песочница завершается, все её ресурсы — память, файловые дескрипторы, сетевые соединения, объекты IPC — должны надёжно освобождаться. Пространства имён PID помогают: достаточно убить init-процесс пространства имён, и все потомки будут убиты.
CoW против песочниц на WebAssembly
WebAssembly (Wasm) — другая крупная технология легковесной песочницы. Стоит сравнить их, потому что они принципиально по-разному балансируют компромиссы.
Песочницы Wasm выполняют код в безопасной по памяти виртуальной машине с линейной моделью памяти. Песочница не может обратиться ни к чему за пределами своей линейной памяти — ни к файловой системе, ни к сети, ни к системным вызовам (если только они явно не предоставлены через WASI). Это крайне безопасно, но ограничивающе: существующий код приходится перекомпилировать в Wasm, и не все языки компилируются в него эффективно.
CoW-песочницы выполняют нативный код в изолированном окружении ОС. Песочница имеет доступ к полноценному интерфейсу ОС (возможно, ограниченному фильтрами seccomp), может запускать любой бинарник и использовать обычные системные библиотеки. Это менее ограничительно, но и менее безопасно: граница изоляции — это модель процессов ОС, у которой больше поверхность атаки, чем у минимальной виртуальной машины Wasm.
Выбирайте Wasm, если: вы контролируете изолируемый код, ваша нагрузка чисто компилируется в Wasm и нужна максимально сильная изоляция. Выбирайте CoW-песочницы, если: нужно запускать произвольные существующие бинарники, нагрузке требуются возможности уровня ОС (файловая система, сеть, дочерние процессы), и вы ставите совместимость выше минимальной поверхности атаки.
Подвох: fork в многопоточных программах
Есть хорошо известная ловушка с fork(): он копирует только вызывающий поток. Если у родителя 20 потоков, потомок получит один. Мьютексы, захваченные остальными 19 потоками, в памяти потомка всё ещё помечены как занятые, но потоков, которые их держат, уже нет. Потомок зайдёт в deadlock при первой попытке захватить один из этих мьютексов.
CoW-системы песочниц обходят это, гарантируя, что процесс-шаблон однопоточный в момент форка. Обычно это означает: инициализировать всё в шаблоне (загрузить библиотеки, настроить рантайм, подготовить начальное состояние), затем остановить все потоки, кроме главного, сделать fork и позволить каждому потомку запускать потоки по мере необходимости. Стоимость инициализации платится один раз, а fork обходит опасность с потоками.
Некоторые новые подходы используют userfaultfd или собственные обработчики страничных ошибок, чтобы реализовать семантику, похожую на CoW, вообще не полагаясь на fork(). Они избегают проблемы с многопоточностью, но добавляют сложности и требуют более тесной координации на уровне ядра.
На что обратить внимание
Субмиллисекундная изоляция пока на ранней стадии, но строительные блоки надёжны (fork, пространства имён, cgroups, overlayfs — всё зрелые технологии). Системы, построенные на них — для serverless, CI/CD и безопасного выполнения кода, — доказывают, что изоляция на уровне каждой операции практична в масштабе. По мере взросления этих инструментов представление о том, что песочницы дороги, устареет так же, как устарело мнение, что сборка мусора слишком медленна для приложений реального времени. Накладные расходы исчезают, а преимущества безопасности подхода «песочить всё» становятся всё труднее игнорировать.


