Análisis profundo de la nueva syscall mseal de Linux
(blog.trailofbits.com)- mseal en Linux 6.10 es una syscall de mitigación de exploits que sella regiones de memoria virtual en ejecución, dificultando que un atacante que ya obtuvo permisos de ejecución de código cambie después los permisos o la disposición de las VMA
mseal(start, len, flags)agregaVM_SEALEDa un rango de VMA alineado a páginas, y el kernel rechaza cambios en rutas comomprotect,munmap,mmap(MAP_FIXED),mremapy algunas rutas destructivas demadvise- Mientras que
memfd_createomemfd_secretse parecen más al control de acceso para archivos anónimos basados en RAM y memoria secreta, mseal se enfoca en impedir cambios de permisos, desmapeos y remapeos después de que un atacante remoto logra ejecutar código - Los principales objetivos defensivos son ataques que eluden NX con
mprotect(PROT_EXEC)para ejecutar shellcode, y exploits solo de datos que crean agujeros en la memoria conmunmap/remapeo y luego los rellenan con datos del atacante - Es posible que se integre a partir de glibc 2.41, pero como la stack y el heap no se sellan automáticamente debido a su expansión y contracción en tiempo de ejecución, los desarrolladores deben elegir con cuidado el momento y el alcance de su aplicación
Qué protección ofrece mseal
- El sellado de memoria (memory sealing) es una función que vuelve un rango de VMA en ejecución casi inmutable frente a modificaciones peligrosas posteriores
- Aunque un atacante consiga una primitiva de ejecución de código, en una VMA sellada le resulta difícil cambiar permisos mediante operaciones de memoria virtual o manipular la disposición a su favor
- El equipo de Chrome Security introdujo esta syscall para soportar la estrategia de CFI de V8 en ChromeOS basado en Linux
- Tras varias rondas de discusión y reescritura, llegó al kernel de Linux, y su integración con glibc podría ampliar su uso más allá de los navegadores
Diferencias con la familia memfd
memfd_createymemfd_secretse parecen más a la familia de sellado de archivos (file sealing)- Permiten crear archivos anónimos basados en RAM
memfd_secrethace que solo los procesos con el descriptor de archivo puedan acceder a esa región de memoria- Puede usarse para mapeos de espacio de usuario estilo “secure enclave” que protegen datos sensibles en memoria
mseales una syscall orientada a la mitigación de exploits contra atacantes remotos que buscan ejecutar código, más que contra atacantes locales que intentan filtrar información sensible
Funcionamiento interno del kernel
- La firma de la syscall es
int mseal(unsigned long start, size_t len, unsigned long flags)startylenindican el inicio y la longitud de la VMA válida que se sellarálendebe estar correctamente alineado a páginas- Actualmente
flagsno se usa y debe ser0
- La implementación en Linux 6.12 llama a
do_msealcurrent->mmapunta amm_struct, el espacio completo de direcciones de memoria virtual del proceso que llamammapdentro demm_structcontiene la lista devm_area_struct, regiones de memoria contiguas creadas conmmap- Áreas como la stack o VDSO también pueden representarse como una VMA
do_msealcalcula la dirección final del rango y luego bloquea el área de memoria conmmap_write_lock_killablecheck_mm_sealrecorre cada VMA del rango objetivo y primero verifica la validez de los límites- Si hay memoria no asignada en medio del rango, devuelve
-ENOMEM - Esta primera comprobación evita que, ante un error, solo algunas VMA queden selladas
- Si hay memoria no asignada en medio del rango, devuelve
apply_mm_sealrecorre de nuevo el mismo rango y agrega la banderaVM_SEALEDa las VMA objetivo mediantemseal_fixup
Operaciones de memoria prohibidas después del sellado
- El patchset del kernel agrega comprobaciones de
VM_SEALEDenmm/madvise.c,mm/mmap.c,mm/mprotect.c,mm/mremap.cymm/mseal.c mprotectypkey_mprotectllaman internamente acan_modify_vmadesdemprotect_fixup, y si la VMA está sellada devuelven-EPERM- En una VMA sellada no se permiten las siguientes operaciones
- Cambiar bits de permisos mediante
mprotectopkey_mprotect - Desmapear con
munmap - Reemplazar un mapeo sellado por un mapeo mutable o no sellado mediante
mmap(MAP_FIXED) - Expandir o reducir tamaño mediante
mremap - Mover a un nuevo destino mediante
mremap(MREMAP_MAYMOVE | MREMAP_FIXED) - Usar
madvisecon algunas banderas destructivas
- Cambiar bits de permisos mediante
- En movimientos con
mremap, la comprobación de sellado se aplica tanto a la VMA de origen como a la de destino - Si no está
MREMAP_DONTUNMAP, la VMA de origen se desmapea, y en ese caso también debe pasar la comprobación de sellado demunmap - En Linux 6.10 o superior se puede usar
msealmediante una llamada directa a syscall, y el wrapper de ejemplo usa el número de syscall462yflags=0
Bloquear la ejecución de shellcode reforzando NX
- Un atacante puede preferir ejecutar shellcode incluso si dispone de técnicas de reutilización de código como ROP
- Coloca shellcode en una stack o heap no ejecutable
- Ejecuta una cadena ROP inicial mediante una vulnerabilidad
- Desactiva el bit NX del área donde está el shellcode con
mprotect(PROT_EXEC) - Salta a esa área y ejecuta el shellcode
- El ataque al daemon SMB de MikroTik RouterOS que usa CVE-2018-7445 es un ejemplo de este flujo
- Coloca shellcode basado en sockets en un heap no ejecutable
- Una cadena ROP creada mediante un desbordamiento de stack cambia los permisos de la memoria del heap
- Luego ejecuta el shellcode
- Si se sella esa VMA con
mseal, la comprobacióncan_modify_vmaimpide el cambio de permisos cuando se llama amprotect - En el código de ejemplo, si no se sella la página de shellcode en la stack, puede ejecutarse después de
mprotect(PROT_READ|PROT_WRITE|PROT_EXEC) - Si se sella la misma página con
mseal,mprotectno logra cambiar los permisos reales y la ejecución del shellcode provoca una falla de segmentación
Restricciones al aplicarlo a stack y heap
- Si
msealse introduce a partir de glibc 2.41, el cargador dinámico aplicará sellado a un conjunto predefinido de VMA - En el plan actual, la stack y el heap no se sellan automáticamente
- La stack y el heap pueden expandirse durante la ejecución, por lo que el sellado automático podría romper el comportamiento de la aplicación
- El asignador de heap puede llamar a la syscall
brkpara recuperar espacio - En esta ruta, la reducción puede ocurrir pasando por
arch_unmapydo_vmi_unmap - En estado sellado, esos desmapeos no se permiten, por lo que la asignación dinámica de memoria podría romperse
- El asignador de heap puede llamar a la syscall
- Los desarrolladores deben decidir, según el contexto de la aplicación, en qué momento y a qué regiones es seguro aplicar el sellado
- La macro de ejemplo sella repetidamente las páginas de frames de stack seleccionados que podrían contener datos no confiables
- Si se vuelve a llamar a
msealsobre una VMA ya sellada, se trata como no-op y no produce error - Funciones como la expansión automática de stack o stack splitting requieren atención adicional
- Si un atacante insiste en usar shellcode, también podría crear una nueva región ejecutable con
mmapy copiar el payload desde un área legible, pero el procedimiento se vuelve más engorroso
Mitigación de exploits solo de datos basados en desmapeo
- Bloquear
mprotecttambién impide que una región sellada pase a ser escribible, lo que ayuda a proteger variables de datos que, si se modificaran, podrían fortalecer una primitiva de exploit - Los mantenedores de Chrome consideraron una técnica que pasa punteros dañados a syscalls de desmapeo y remapeo para hacer agujeros en la memoria y rellenarlos de nuevo con datos controlados por el atacante
- Como este método no cambia directamente transferencias de flujo de control como direcciones de retorno de stack o punteros a funciones, puede eludir garantías de CFI forward-edge y backward-edge
- En navegadores que implementan compiladores JIT, esta técnica puede ser especialmente útil
- Turbofan de V8 puede crear regiones que alternan entre RW y RX
- Un atacante puede hacer que se genere código ejecutable mediante JavaScript en hot paths durante la compilación JIT
- Puede rellenar una región desmapeada para sobrescribir datos importantes y producir un cambio que lleve a la ejecución de código
- Es un exploit solo de datos que afecta el flujo de control manipulando datos específicos de la memoria, sin secuestrar directamente el flujo de control ni requerir una filtración previa de punteros
msealpuede bloquear estos escenarios de hole-punching al impedir desmapeos y remapeos
Caso House of Muney
- House of Muney usa una técnica similar basada en desmapeo en exploits de heap en espacio de usuario
- Qualys usó esta técnica en un exploit real contra un bug antiguo de Qmail
- La técnica depende de que, cuando los chunks de asignación grandes superan
M_MAP_THRESHOLD,mallocyfreellamen directamente ammapymunmap, respectivamente - En chunks grandes no hay una caché intermedia de freelist, lo que simplifica el procedimiento de explotación
- Si se altera el metadato de tamaño en la parte superior de un chunk asignado a otro tamaño de página y luego se llama a
free, puede producirse unmunmapsobre la región de memoria adyacente al chunk - El ejemplo de Dulin apunta a las regiones
.gnu.hashy.dynsymmediante unmunmaparbitrario- Luego las rellena de nuevo con un chunk de
mmapmás grande - Permite sobrescribir una entrada PLT aún no resuelta
- Revive un ataque estilo GOT overwrite
- Luego las rellena de nuevo con un chunk de
- El PoC abreviado asigna dos chunks grandes, manipula el campo de tamaño de
top[-1], luego desmapea también la región adyacente confree(top)y la rellena de nuevo con datosXmediante una asignación más grande - Esta técnica puede funcionar incluso con CFI y no requiere una filtración previa de ASLR
- Se espera que el sellado del conjunto de VMA previsto en la integración de
msealcon glibc mitigue automáticamente este ataque al proteger el código binario mapeado y las bibliotecas dinámicas frente a trucos de desmapeo y remapeo - Los desarrolladores que quieran mayor endurecimiento pueden sellar selectivamente asignaciones
mmapque no vayan a expandirse ni desmapearse durante la vida del programa
Alcance futuro de uso
mseales una mitigación relativamente nueva del kernel de Linux, y puede haber más casos de uso aún no cubiertos- Cuando la integración con glibc se complete y madure, podrían seguir mejoras ajustadas a los requisitos de la syscall
- El parámetro
flags, que actualmente no se usa, también podría recibir usos concretos en el futuro - Al aplicarlo en software real, hay que evaluar tanto la vida útil de la memoria que se sellará como su necesidad de modificación
1 comentarios
Opiniones de Hacker News
Estaría bueno que alguien con conocimiento interno resumiera qué objeciones y preocupaciones hubo en las discusiones intensas de la lista de correo del kernel.
Suelo evitar la lista de correo en sí porque a veces se pone demasiado picante, y ya tengo suficiente dolor de cabeza.
El mecanismo en sí parece razonable, pero me sorprende que el kernel no tuviera ya una función así.
Entre los flags propuestos había uno que permitiría ignorar el sello (seal), y Linus reaccionó más o menos en plan: “¿entonces en un lugar no se podrá hacer
munmap, pero en otro se podrá ignorar el sello?”.Después fue más contundente: “una vez sellado, está sellado”, y no vale eso de que “algunos lugares respetan el sello y otros arbitrarios no”.
Más tarde llegó a decir que los sellos no se pueden ignorar ni son negociables, y que si seguían proponiendo flags para ignorarlos los pondría en la ignore list.
En un correo de Theo de Raadt a Jeff Xu, ante la afirmación de que los enfoques de
mimmutable()ymseal()son distintos, pero que ambos apuntan a hacer más seguras las aplicaciones de Linux sellando la memoria frente a atacantes, respondió: “parece que estás creando mseal solo para Chrome”.Consideró que no encajaría bien con las aplicaciones en general, porque es demasiado complejo y porque, según la experiencia con
mimmutable(), las aplicaciones no lo gestionan directamente, sino que queda a cargo deexecve(), la inicialización de libc yld.so.Coincidió con Theo y Linus, y opinó que la idea básica de
mimmutable()es buena, pero que la forma en que se dividió e implementó es horrible.Me da curiosidad cómo se usa esta llamada al sistema.
Chrome la quiere, pero como un atacante podría volver a mapear con otros flags, las páginas selladas no se pueden desmapear.
Entonces, para páginas asignadas en tiempo de ejecución, ¿no queda prácticamente inutilizable salvo que uno tenga pensado conservarlas durante toda la vida del proceso?
Si es así, ¿no sería difícil aplicarla a objetivos muy atractivos como la memoria del sandbox de JS?
Me pregunto si este tipo de trabajo se resuelve ejecutándolo en un proceso separado, sellando la memoria y luego matando el proceso al terminar.
No conozco bien la gestión de memoria y procesos de Chrome, así que no estoy seguro de por qué eso no sería un problema, y también me pregunto si esta es la razón por la que a menudo se explica que esta función no es útil para la mayoría de los programas.
Tengo entendido que Chrome los usa ampliamente, y como de todos modos se necesitan procesos separados por motivos como el aislamiento mediante namespaces, quizá vaya por ese lado.
Discusión posterior a
mseal(), 20 de octubre de 2023: https://lwn.net/Articles/948129/mseal()se acerca, 19 de enero de 2024: https://lwn.net/Articles/958438/Sellado de memoria para GNU C Library, 12 de junio de 2024: https://lwn.net/Articles/978010/
Es una lástima que el sistema operativo tenga que implementar llamadas así, aun cuando la arquitectura x86_64 moderna tiene muchas funciones que ayudan a la programación y la computación seguras.
Creo que la actitud de intentar remendar sistemas antiguos construidos sobre legados obsoletos, formas de pensar viejas y paradigmas que no se ajustan al mundo y al conocimiento actuales está frenando el avance de la computación y, literalmente, poniendo en riesgo a miles de millones de personas.
Por supuesto, no quiero decir que este cambio no sea un paso en la dirección correcta, pero si dejamos de lado el ideal existente de que el sistema operativo debe funcionar como lo hace, y consideramos los sistemas y conocimientos actuales y lo que la gente quiere de los sistemas, podemos imaginar sistemas que liberen a desarrolladores y usuarios de las cargas y riesgos que se les imponen hoy.
Es cierto que existen bugs de arquitectura, pero como el software ni siquiera aprovecha correctamente las funciones actuales, usar los bugs de arquitectura como contraargumento esquiva el punto central.
Si se construye sobre cimientos inestables, siempre habrá una forma más barata de comprometer el sistema.
También me interesa saber por qué consideras que
msealno se justifica, y qué sería mejor en su lugar.¿Se puede sobrescribir o desactivar la llamada al sistema
msealcon el truco deLD_PRELOAD?mseales una llamada al sistema orientada a la mitigación de ataques de ejecución remota de código, más que a frenar a un atacante local que intenta extraer secretos sensibles de la memoria.Si un atacante remoto puede cambiar el entorno local, habría que asumir que ya comprometió el sistema.
LD_PRELOAD.Para que tenga efecto, tendría que ser una función importada, pero las llamadas al sistema crudas no se pueden interceptar de esa manera.
Discusión sobre interceptación de llamadas al sistema en Linux: https://stackoverflow.com/questions/69859/how-could-i-interc...
Crear uno mismo un kernel parcheado que finja que
msealfunciona podría ser la forma más fácil de “desactivar” la función.Sin embargo, como un programa que usa
msealpuede verificar si realmente funciona, un kernel comprometido además necesitaría alguna forma de desactivarmsealen secreto para impedir las comprobaciones de la app después de aplicarlo.Por ejemplo, se puede rastrear el ejecutable con
ptracepara vigilar las llamadas al sistema y hacer que se saltemseal(2).Esta llamada al sistema no está pensada para el modelo de amenaza de “el atacante ya tenía acceso antes de que se inicializara el proceso”.
mseal, pero no la llamada al sistema en sí.Por lo que encontré, las sobrescrituras de llamadas al sistema mediante precarga cambian el wrapper, y si se hace la llamada al sistema directamente, parece que no se podría sobrescribir.
Técnicamente, quizá se podría sobrescribir la propia función
syscall.Meta: el prototipo de
mseal()que aparece en el artículo necesita edición.El primer argumento aparece como
unsigned start addr, pero probablemente lo correcto seaunsigned long start_addr.int mseal(unsigned long start, size_t len, unsigned long flags)En “Memory Sealing ‘Mseal’ System Call Merged for Linux 6.10” (2024) https://news.ycombinator.com/item?id=40474510#40474551 ya se había discutido cómo debería CPython dar soporte a la llamada al sistema
mseal().OpenBSD tiene esta función desde hace tiempo: https://man.openbsd.org/mimmutable.2
Me pregunto por qué una función tan obvia recién ahora llega a Linux.
mimmutablede OpenBSD se introdujo en OpenBSD 7.3, lanzado el 10 de abril de 2023, así que no es “desde hace tiempo”.En cambio, Linux y FreeBSD tienen
memfd_createdesde hace mucho, y OpenBSD no tiene archivos anónimos, por lo que depende deshm_open.