
¿Qué tienen en común la teoría de categorías y un buffer overflow y por qué casi toda la seguridad de la memoria puede contarse como la historia de una flecha que se pierde por el camino?
Todo programa vive engañado. Cree que su memoria está ordenada: que este puñado de bytes es un número entero, que aquellos dieciséis forman una cadena de texto con un final claro, que más allá hay una dirección sagrada —la de retorno— que nadie debería tocar.
El procesador, en cambio, no comparte ese relato. Para el hardware no existen los enteros, ni las cadenas, ni las fronteras: solo hay bytes, todos iguales, dispuestos en una larga fila. Entre esas dos visiones —la del programa y la de la máquina— se cuela la familia de errores más longeva y más cara de la historia del software: las violaciones de seguridad de memoria.
Las explicaciones habituales de un buffer overflow son mecánicas, y muy buenas: aquí está la pila, aquí el búfer, así se sobrescribe la dirección de retorno y se secuestra el flujo de ejecución. Pero podemos subir un peldaño y hacernos una pregunta más ambiciosa: ¿hay una razón estructural, casi geométrica, por la que esta clase de fallos existe siquiera?
La teoría de categorías —ese lenguaje abstracto cuyo objeto de estudio no son las cosas sino las flechas que las transforman— responde que sí. Y lo hace con una elegancia algo inquietante: casi todo lo que sigue cabe en una sola frase, un functor que olvida demasiado.
No hace falta ser categorista para seguir el argumento. Solo hay que aceptar un cambio de mirada: dejar de pensar en la memoria como una cosa y empezar a pensarla como una traducción entre dos mundos.
Dos mundos: la intención y los bytes
Imaginemos dos universos paralelos que describen la misma memoria. En el primero vive la intención estructurada del programa. Sus habitantes son regiones de memoria, pero no regiones desnudas: cada dirección lleva pegada una etiqueta que dice qué papel cumple —«estos dieciséis bytes son buf», «estos ocho son la dirección de retorno», «esta página es código». Y las flechas entre regiones —los morfismos— son únicamente los accesos que respetan esa estructura: leer o escribir dentro de los límites de un objeto, sin confundir su tipo con el del vecino.
En el segundo universo viven los bytes crudos: la memoria física tal y como la ve el hardware. Aquí los objetos son simples rangos de direcciones sin etiqueta alguna, y las flechas son cualquier cosa que la máquina sea físicamente capaz de hacer. No hay estructura que respetar porque no hay estructura.
DEFINICIÓN DE CATEGORÍA Y MORFISMO
Una categoría es una colección de objetos junto con flechas (morfismos) entre ellos, que pueden componerse de forma asociativa y disponen de identidades. En lo que sigue, los objetos serán regiones de memoria y los morfismos serán los accesos permitidos sobre ellas. Llamaremos Virt a la categoría de la intención estructurada y Phys a la de los bytes crudos.

La memoria que el programa cree tener. A la izquierda, el espacio de direcciones; a la derecha, un marco de pila. La dirección de retorno vive justo encima del búfer: basta escribir más allá de sus límites para alcanzarla.
La asimetría entre ambos mundos lo es todo. Escribir dentro de buf es un morfismo legítimo en Virt. Pero escribir más allá de buf, hasta pisar la dirección de retorno, no es un morfismo en Virt: no existe ninguna flecha que cruce de un objeto a otro, porque sus etiquetas son incompatibles y ningún acceso respetuoso con la estructura puede ignorarlas. En Phys, en cambio, esa misma escritura es una flecha perfectamente válida: son bytes contiguos, y el hardware no tiene ninguna objeción. Guardemos esta tensión; será el corazón de todo.
La traducción es un functor
¿Quién media entre los dos mundos? La MMU (la unidad de gestión de memoria), que mediante las tablas de páginas traduce cada dirección virtual a una dirección física. Esa traducción no es un mapa cualquiera: respeta la estructura. Lleva regiones a regiones y accesos a accesos, y —crucialmente— conserva la composición. En la jerga, es un functor T : Virt → Phys.
DEFINICIÓN DE FUNCTOR
Un functor es una traducción entre categorías que preserva su estructura: envía objetos a objetos, flechas a flechas, identidades a identidades, y respeta la composición. Es decir, traducir el encadenamiento de dos accesos da lo mismo que encadenar las traducciones de cada uno:
Falta un detalle, y es importante: una traducción puede fallar. Si la página no está presente, el intento de acceso no produce una dirección, sino un fallo de página. Para modelar limpiamente ese «a veces no hay resultado», los matemáticos adjuntan a cada conjunto un único valor extra, ⊥, que representa el fallo. La operación que hace eso —MX = X + 1— se llama la mónada Maybe, y convierte a T en una traducción honesta: «esto, o bien una dirección física, o bien un fallo».

DÓNDE LA ABSTRACCIÓN SE GANA EL SUELDO
Que T sea un functor no es decoración. La igualdad
afirma algo concreto y útil: recorrer una cadena de punteros en el mundo virtual y luego traducir produce exactamente el mismo comportamiento físico —fallos incluidos— que traducir salto a salto. Esa conmutatividad es lo que permite razonar sobre la memoria de forma composicional, una pieza cada vez, sin que el todo nos sorprenda.
El olvido tiene consecuencias
Aquí está el giro. Al cruzar de Virt a Phys, el functor T olvida las etiquetas: T(V) es solo una hilera de bytes indistinguibles. De cualquier functor podemos preguntar dos cosas. ¿Es fiel, es decir, inyectivo sobre las flechas? Y ¿es pleno, es decir, toda flecha del destino proviene de alguna flecha del origen? Es esta segunda propiedad la que falla por diseño —y su fallo es, literalmente, la superficie de ataque.
DEFINICIÓN DE PLENITUD (FULLNESS)
Un functor es pleno si toda flecha del mundo destino tiene una preimagen en el origen. Para T, esto significaría que todo acceso físico que el hardware permite corresponde a algún acceso que el programa tenía derecho a realizar. La memoria es segura justo en la medida en que T sea pleno.
Y T no es pleno. Esa es toda la enfermedad, enunciada con precisión:
«Un fallo de seguridad de memoria es, exactamente, una flecha del mundo de los bytes que no tiene preimagen en el mundo de la intención.»
El buffer overflow es el testigo perfecto. La escritura que va desde buf hasta la dirección de retorno es una flecha impecable en Phys —bytes contiguos, hardware conforme—, pero no tiene preimagen en Virt, porque allí esos bytes llevan etiquetas incompatibles y ninguna flecha respetuosa cruza entre ellos. El mundo de los bytes tiene la flecha; el de la intención, no. T olvidó precisamente la frontera que la habría prohibido.

Una carrera escrita en flechas
Si los bugs son flechas indeseadas, las defensas son recortes: cada una restringe el conjunto de flechas físicas permitidas, encerrándolas en una subcategoría cada vez más pequeña. Y lo fascinante es que la propia estructura categórica predice el orden de la carrera armamentística.
La primera muralla es W⊕X: ninguna página de memoria puede ser a la vez escribible y ejecutable (w ∧ x prohibido). Eso fulmina el ataque clásico —inyectar instrucciones como datos y luego ejecutarlas—, porque requeriría una página con ambos permisos. En nuestro lenguaje: W⊕X elimina las flechas que estaban fuera de la imagen de T.
El atacante responde con elegancia. Si no puede fabricar flechas ejecutables nuevas, recompone las que ya existen: encadena fragmentos de código legítimo —los gadgets— pegándolos con retornos que controla desde la pila. Es la programación orientada a retornos, ROP. Categóricamente, ROP construye el efecto malicioso como una composición de flechas que ya estaban en la imagen de T. La muralla cerró las flechas externas; el atacante se repliega a las composiciones inesperadas de flechas internas.
De ahí, la respuesta defensiva es forzosa: si el problema son las composiciones, hay que restringir las composiciones. Eso es la integridad de flujo de control, CFI: podar las concatenaciones legales de saltos y retornos hasta dejar solo las del grafo de control que el programa pretendía. Y la siguiente respuesta del atacante también está escrita de antemano: composiciones que sí son legales como control de flujo, pero que violan invariantes de los datos —confusión de tipos, ataques puramente sobre datos.

El peldaño final intenta cambiar las reglas en lugar de seguir subiendo. Las capacidades al estilo CHERI bajan las etiquetas τ_V del mundo de la intención al propio mundo de los bytes, convirtiéndolas en metadatos infalsificables que el hardware acarrea con cada puntero. Si las etiquetas viajan con los bytes, Phys deja de ser amnésico: T recupera la fidelidad por construcción, y la flecha sin preimagen —la que definía el bug— ya no puede formarse.
LO ÚNICO QUE ESTO PREDICE DE VERDAD
Más allá de reordenar hechos conocidos, el esquema compra una predicción genuina: la escalera. En cuanto uno ve W⊕X como «quitar flechas» y CFI como «quitar composiciones», el siguiente peldaño —restringir composiciones que, siendo legales, violan invariantes de datos— es el movimiento forzado. La categoría te dice hacia dónde va la carrera antes de que leas el siguiente artículo.
El final feliz: demostrar la fidelidad
El mismo lenguaje de flechas que diagnostica la enfermedad describe la cura. Los núcleos de sistema operativo verificados —seL4, CertiKOS— demuestran una propiedad llamada refinamiento: un functor entre una especificación abstracta y una implementación concreta que satisface una condición de simulación (un cuadrado que conmuta).
Su garantía de seguridad es, dicho en nuestros términos, que ese functor de implementación es lo bastante fiel como para que ningún comportamiento concreto se escape de la estructura prevista por la especificación. Es decir: demuestran el análogo de «T refleja toda la estructura que importa», cerrando exactamente la grieta de plenitud y fidelidad de la que se alimenta la explotación.
Nota importante: ¿Cuándo se gana la abstracción el sueldo?
Conviene ser honesto, porque no toda reformulación categórica merece su sitio. Llamar «counidad» a una operación puede sonar profundo sin predecir absolutamente nada; el rigor matemático mal usado es solo decoración cara. La prueba de fuego es simple: ¿la propiedad universal te permite deducir algo que de otro modo tendrías que comprobar a mano?
En el cuadro que acabamos de pintar, dos cosas pasan esa prueba. La primera, que «bug = fallo de plenitud» reúne un zoológico entero de clases de vulnerabilidad bajo un único fenómeno. La segunda, que la escalera «flechas, luego composiciones, luego datos» anticipa el orden real de la carrera. El resto es andamiaje: sirve para ver, no para demostrar. Y verlo bien ya es bastante.
La memoria es un functor que olvida: lleva un mundo de intenciones a un mundo de bytes sin forma. Cada vulnerabilidad —desde el overflow más tosco hasta el ataque más sutil— es el precio de ese olvido: una flecha que la máquina permite y que la intención jamás autorizó.









