El setenta por ciento de los CVEs son bugs de memory safety. Buffer overflows, use-after-free, double frees. El tipo de vulnerabilidades que permiten a un atacante pivotar desde un JPEG malformado hasta acceso root.

Hemos sabido cómo detener la mayoría de estos en hardware desde 1975. La computadora Cambridge CAP usó direccionamiento basado en capabilities. La Hydra de Carnegie Mellon también. La idea era simple: cada acceso a memoria porta su propio permiso. El hardware lo hace cumplir. Sin permiso, sin acceso.

Y luego el hardware de capabilities pasó las siguientes cuatro décadas perdiendo contra los modelos de memoria plana. Es solo ahora, con la placa Morello de ARM y el conjunto de instrucciones CHERI, que las arquitecturas de capabilities están protagonizando un regreso creíble.

La pregunta no es si las capabilities funcionan. Lo hacen. La pregunta es por qué le tomó cuarenta años a la industria preocuparse.

Un capability pointer es un ticket infalsificable, no solo una dirección

Un puntero normal es un entero. Una dirección. No tiene longitud, ni permisos, ni procedencia. Si puedes adivinar una dirección, puedes acceder a ella. Eso no es una característica. Es un bug de diseño que C heredó del PDP-11.

Un capability pointer, por el contrario, es una tupla impuesta por hardware. Contiene la dirección virtual, la base y longitud de la región autorizada, permisos de lectura/escritura/ejecución, y una tag de validez de 1 bit. La tag se almacena fuera de la memoria normal, en metadatos de hardware dedicados, lo que hace que los capabilities sean infalsificables. No puedes construir un capability válido a partir de un entero. Solo puedes recibirlo de algo que ya tenía autoridad.

En CHERI, esto se ve como un puntero de 128 bits en una máquina de 64 bits. Los 64 bits extra transportan bounds y permisos. El bit de tag vive en una tabla shadow o en bits ECC sobrantes, dependiendo de la implementación.

Así es como se ve reducir un capability en la práctica en un sistema 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;
}

Si un atacante corrompe un capability pointer, el bit de tag se borra. El hardware lanza una excepción en la siguiente desreferenciación. La cadena de explotación muere en el primer salto.

Ese es un modelo de seguridad radicalmente mejor que ASLR y stack canaries, que son solo topes de velocidad.

El iAPX 432 envenenó el pozo para una generación

Entonces, ¿por qué pasamos cuarenta años fingiendo que los punteros eran solo enteros?

Intel lanzó el iAPX 432 en 1981. Tenía capabilities de hardware, segmentos de memoria orientados a objetos e incluso garbage collection asistida por hardware. También era aproximadamente cinco a diez veces más lento que el 8086, enormemente complejo e incompatible con cada compiler existente.

IBM tuvo más suerte con el System/38, que usó capabilities para su single-level store. Funcionó. Fue estable. También era un sistema propietario de gama media sin camino hacia el ecosistema abierto emergente. Cuando llegó la revolución UNIX, trajo espacios de dirección planos, punteros de C y la suposición de que un puntero cabe en un registry. Esa suposición se convirtió en la ABI de toda la industria.

Para la década de 1990, el hardware de capabilities era una curiosidad de investigación. El imperativo comercial era la compatibilidad hacia atrás y el rendimiento bruto. La memory safety era una preocupación a nivel de lenguaje, si es que lo era. C y C++ simplemente la declararon problema del programador.

Eso funcionó lo suficientemente bien hasta que Internet hizo que cada buffer overflow fuera explotable de forma remota.

El modo híbrido de CHERI rompe el punto muerto de compatibilidad

El hardware de capabilities moderno, específicamente CHERI, aprendió la lección del iAPX 432. No fuerza una migration de todo o nada.

CHERI admite un ABI híbrido donde los punteros de enteros legacy y los capability pointers coexisten en el mismo proceso. Puedes compilar solo tu parser de red o tu biblioteca sandboxed con capabilities, mientras el resto de tu aplicación usa punteros normales. El sistema operativo (CheriBSD, o el port de CHERI para Linux) gestiona la transición.

Esto importa porque la barrera real nunca fue el área de silicio. Era la inercia del software. Hay miles de millones de líneas de C y C++ que asumen sizeof(void *) == 8. Una arquitectura de capabilities que requiere reescribir todas ellas está muerta al llegar. Una que puede adoptarse incrementalmente tiene una oportunidad.

El mecanismo de hardware es elegante. Las cargas y almacenamientos aware de capabilities usan instrucciones dedicadas que verifican la tag y los bounds en paralelo con la traducción de direcciones. Los punteros de enteros evitan las verificaciones por completo. Solo pagas por seguridad donde la usas.

El costo real es la presión de TLB y el bloat de punteros, no el conteo de ciclos

Las capabilities no son gratis. El overhead cae en tres cubetas.

Primero, tamaño del puntero. En un ABI purecap, cada puntero es de 128 bits. Eso duplica la presión de caché para estructuras de datos pointer-heavy. Las listas enlazadas, los árboles y las vtables se vuelven todos más gordos.

Segundo, granularidad de bounds. CHERI impone bounds con granularidad de byte, lo que significa que el subsistema de memoria debe verificar bounds en cada desreferenciación. La verificación en sí es rápida, pero capabilities de grano fino pueden aumentar la presión de TLB y caché si creas muchas regiones pequeñas protegidas.

Tercero, y más importante, cambios de software. Los compilers deben generar prólogos aware de capabilities. Los ABIs cambian. Los allocators de memoria deben devolver capabilities acotados en lugar de punteros raw. Los debuggers necesitan entender registries de 128 bits.

El overhead medido en runtime en hardware CHERI está típicamente en el rango de un solo dígito por ciento para la mayoría de las cargas de trabajo, y hasta diez a quince por ciento para benchmarks pointer-heavy. Eso es menos que el overhead de muchas mitigaciones modernas como memory tagging o sandboxing, y proporciona garantías más fuertes.

El trade-off no es rendimiento. El trade-off es agitación del ecosistema.

Puedes ejecutar esto hoy en una placa de desarrollo

Si quieres probar hardware de capabilities, no necesitas una máquina del tiempo ni una beca de investigación.

La placa Morello de ARM implementa extensiones CHERI en un núcleo Neoverse N1. CheriBSD corre en ella out of the box. Si no tienes el hardware, QEMU-CHERI emula la arquitectura de capabilities completa.

La toolchain es LLVM estándar. Compilas con cheri-clang y la flag -march=morello+cheri. Existen ports de FreeBSD y Linux. El ensamblador, linker y debugger integrados de LLVM entienden todos los capabilities.

Comienza con una sola función. Envuelve un parser o una rutina de deserialización en un capability reducido. Ejecuta tu fuzzer contra ella. Cuando una escritura fuera de bounds hubiera corrompido el heap, obtendrás una excepción de hardware limpia en su lugar.

El hardware de capabilities no es una corrección teórica para memory safety. Es una tecnología funcional y desplegada que la industria ignoró durante cuarenta años porque los incentivos estaban mal. La memoria plana fue más rápida de construir, más fácil de portar y lo suficientemente buena hasta que la explotación remota se convirtió en el threat model por defecto.

El silicio funciona. Los compilers funcionan. Los sistemas operativos funcionan. Lo único que queda es decidir qué partes de tu codebase vale la pena proteger primero.