Семьдесят процентов CVE — это баги memory safety. Buffer overflows, use-after-free, double frees. Тип уязвимостей, который позволяет атакующему перейти от malformed JPEG до root-доступа.

Мы знаем, как остановить большинство из них на уровне hardware, с 1975 года. Компьютер Cambridge CAP использовал capability-based addressing. Hydra из Carnegie Mellon тоже. Идея была проста: каждый доступ к памяти несёт с собой собственное разрешение. Hardware принуждает его соблюдать. Нет разрешения — нет доступа.

А затем hardware capability провёл следующие четыре десятилетия, проигрывая плоским моделям памяти. И только сейчас, с платой Morello от ARM и набором инструкций CHERI, архитектуры capability совершают правдоподобное возвращение.

Вопрос не в том, работают ли capabilities. Работают. Вопрос в том, почему индустрии понадобилось сорок лет, чтобы заинтересоваться.

Capability pointer — это неподделываемый билет, а не просто адрес

Обычный pointer — это целое число. Адрес. У него нет длины, нет разрешений, нет происхождения. Если вы можете угадать адрес, вы можете получить к нему доступ. Это не фича. Это баг дизайна, который C унаследовал от PDP-11.

Capability pointer, напротив, — это tuple, навязанный hardware. Он содержит виртуальный адрес, базу и длину авторизованной области, разрешения на чтение/запись/выполнение и 1-битный validity tag. Тег хранится вне обычной памяти, в выделенных hardware-метаданных, что делает capabilities неподделываемыми. Нельзя сконструировать валидный capability из целого числа. Можно только получить его от того, кто уже обладал полномочиями.

На CHERI это выглядит как 128-битный pointer на 64-битной машине. Дополнительные 64 бита несут bounds и разрешения. Бит тега живёт в shadow-таблице или резервных ECC-битах, в зависимости от реализации.

Вот как выглядит сужение capability на практике на системе CHERI:

#include <cheriintrin.h>
#include <stdio.h>

int main(void) {
    char buffer[64];

    // In the purecap ABI, &buffer[0] is already a capability
    // carrying 64-byte bounds and read/write permissions.
    char *cap = buffer;

    printf("Original length: %zu\n", cheri_length_get(cap));

    // Narrow authority to the first 16 bytes.
    char *narrow = cheri_bounds_set(cap, 16);
    printf("Narrowed length: %zu\n", cheri_length_get(narrow));

    // This access is safe and hardware-authorized.
    narrow[15] = 'x';

    // This would trap with a CHERI bounds violation:
    // narrow[20] = 'y';

    return 0;
}

Если атакующий портит capability pointer, бит тега стирается. Hardware выбрасывает exception при следующей dereference. Цепочка эксплуатации умирает на первом прыжке.

Это радикально лучшая модель безопасности, чем ASLR и stack canaries, которые являются лишь препятствиями.

iAPX 432 отравил колодец для целого поколения

Так почему же мы сорок лет притворялись, что pointer — это просто целое число?

Intel выпустил iAPX 432 в 1981 году. У него были hardware capabilities, объектно-ориентированные сегменты памяти и даже hardware-assisted garbage collection. Он был также примерно в пять-десять раз медленнее 8086, чрезвычайно сложным и несовместимым с каждым существующим компилятором.

IBM имела больше удачи с System/38, который использовал capabilities для своего single-level store. Это работало. Было стабильно. Но это была также проприетарная система среднего класса без пути в зарождающуюся открытую экосистему. Когда пришла UNIX-революция, она принесла плоские адресные пространства, C-pointers и предположение, что pointer помещается в регистр. Это предположение стало ABI всей индустрии.

К 1990-м годам hardware capability был исследовательской диковинкой. Коммерческий императив — обратная совместимость и сырой performance. Memory safety была заботой на уровне языка, если вообще была. C и C++ просто объявили это проблемой программиста.

Это работало достаточно хорошо, пока интернет не сделал каждый buffer overflow удалённо эксплуатируемым.

Гибридный режим CHERI ломает тупик совместимости

Современный hardware capability, а именно CHERI, усвоил урок iAPX 432. Он не навязывает миграцию «всё или ничего».

CHERI поддерживает hybrid ABI, где legacy integer pointers и capability pointers сосуществуют в одном процессе. Вы можете компилировать только свой сетевой парсер или sandboxed библиотеку с capabilities, в то время как остальная часть приложения использует обычные pointers. Операционная система (CheriBSD или порт CHERI Linux) управляет переходом.

Это важно, потому что реальный барьер никогда не был площадью кремния. Это была инерция программного обеспечения. Существуют миллиарды строк C и C++, которые предполагают sizeof(void *) == 8. Архитектура capability, требующая переписывания всего этого, мертва при рождении. Та, которую можно внедрить постепенно, имеет шанс.

Hardware-механизм элегантен. Capability-aware loads и stores используют выделенные инструкции, которые проверяют tag и bounds параллельно с трансляцией адресов. Integer pointers полностью обходят проверки. Вы платите за безопасность только там, где используете её.

Настоящая стоимость — давление на TLB и раздутие pointers, не счётчик циклов

Capabilities не бесплатны. Overhead распадается на три категории.

Во-первых, размер pointer. В purecap ABI каждый pointer — 128 бит. Это удваивает давление на кэш для pointer-heavy структур данных. Связанные списки, деревья и vtables — всё толстеет.

Во-вторых, гранулярность bounds. CHERI навязывает bounds с байтовой гранулярностью, что означает, что подсистема памяти должна проверять bounds при каждой dereference. Сама проверка быстрая, но мелкозернистые capabilities могут увеличить давление на TLB и кэш, если вы создаёте много маленьких защищённых регионов.

В-третьих, и самое главное, изменения в ПО. Компиляторы должны генерировать capability-aware прологи. ABIs меняются. Memory allocators должны возвращать bounded capabilities вместо raw pointers. Отладчики должны понимать 128-битные регистры.

Измеренный runtime overhead на CHERI hardware обычно находится в пределах однозначного процента для большинства рабочих нагрузок и до десяти-пятнадцати процентов для pointer-heavy бенчмарков. Это меньше, чем overhead многих современных митигаций вроде memory tagging или sandboxing, и обеспечивает более сильные гарантии.

Trade-off — не performance. Trade-off — потрясение экосистемы.

Вы можете запустить это сегодня на dev-плате

Если вы хотите попробовать hardware capability, вам не нужна машина времени или исследовательский грант.

Плата Morello от ARM реализует расширения CHERI на ядре Neoverse N1. CheriBSD работает на ней из коробки. Если у вас нет hardware, QEMU-CHERI эмулирует полную архитектуру capability.

Toolchain — стандартный LLVM. Вы компилируете с cheri-clang и флагом -march=morello+cheri. Существуют порты FreeBSD и Linux. Интегрированный ассемблер, линкер и дебаггер LLVM понимают capabilities.

Начните с одной функции. Оберните парсер или десериализационную рутину в суженный capability. Запустите против неё fuzzer. Когда out-of-bounds запись испортила бы heap, вы получите вместо этого чистое hardware-исключение.

Hardware capability — это не теоретический фикс для memory safety. Это рабочая, поставляемая технология, которую индустрия игнорировала сорок лет, потому что стимулы были неверными. Плоская память строилась быстрее, проще портировалась и была достаточно хороша, пока удалённая эксплуатация не стала threat model по умолчанию.

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