1 puntos por GN⁺ 3 시간 전 | 1 comentarios | Compartir por WhatsApp
  • Desde Linux 6.9, una herramienta que bloquea la unidad al suspender una laptop venía fallando silenciosamente, por lo que las claves de cifrado de disco completo de LUKS quedaban en memoria
  • La causa fue una interacción inesperada entre el refactor de acceso a dispositivos de bloque incluido en Linux 6.9 en mayo de 2024 y el código de cifrado; la corrección propuesta es un parche de una sola línea
  • En un apagado completo el problema no se manifestaba, pero en suspend-to-RAM las claves permanecían, dejando a un atacante que obtuviera una laptop encendida en condiciones de extraer las claves desde la RAM
  • El hallazgo comenzó al revisar entradas de /proc/keys mientras se ordenaba el port a NixOS de cryptsetup-suspend de Debian, y se confirmó con un volcado de memoria de QEMU que la volume key que debía haberse borrado seguía presente
  • Se propusieron pruebas de integración para NixOS y un parche de advertencia para cryptsetup; las funciones de seguridad como borrar claves justo antes de suspender pueden parecer normales, pero sin verificación real de memoria es fácil pasar por alto fallas

Problema de claves LUKS que permanecen durante la suspensión desde Linux 6.9

  • Desde Linux 6.9, es decir, desde mayo de 2024, una herramienta que bloquea la unidad al suspender una laptop venía fallando silenciosamente
  • El cifrado de disco completo con LUKS se usa para proteger los datos si una laptop se pierde, es confiscada o es robada, pero en este caso las claves de cifrado quedaban en memoria durante la suspensión
  • En un apagado completo seguía funcionando, pero el impacto aumenta porque muchas veces se deja la laptop en suspend-to-RAM en lugar de apagarla por completo
  • Si alguien lograba obtener una laptop mientras seguía encendida, las claves que quedaban en memoria podían quedar expuestas
  • Se mencionó VeraCrypt como software para el mismo propósito en Windows, pero luego en los comentarios se corrigió que “canonical software” no significaba el software más usado, sino una recomendación representativa dentro del área de seguridad informática

Causa y parche de una sola línea

  • La causa fue el commit de refactorización del kernel Linux md: port block device access to file
    • El cambio en sí era una refactorización razonable y útil, pero provocó una interacción a distancia con el código de cifrado
  • La corrección propuesta es un parche de una sola línea
  • El autor del parche señaló que, sin verificación formal, no puede afirmar que este parche sea correcto ni que no existan otras interacciones a distancia
  • También se presentaron trabajos de seguimiento para evitar que vuelva a ocurrir

Proceso de descubrimiento

  • El punto de partida fue el trabajo de ordenar el port a NixOS de cryptsetup-suspend de Debian
  • Tanto el original de Debian como el port de NixOS tenían una condición de carrera incómoda pero no dañina que a veces impedía que la laptop entrara en suspensión
  • Para resolverlo, se intentó revivir el parche de kernel no fusionado de Pali Rohár para borrado de claves de dm-crypt en suspensión/hibernación
  • En ese proceso, al revisar el código fuente de cryptsetup y del kernel, se confirmó que según la documentación el keyring se adjunta al hilo llamador y se elimina cuando el hilo termina
  • Sin embargo, aparecían entradas en /proc/keys, algo que antes no se conocía, y eso aumentó las sospechas
  • Finalmente se levantó una máquina virtual en QEMU, se volcó la memoria y se confirmó que la LUKS volume key que debía haberse borrado seguía intacta

Proyecto secure suspend-to-RAM de NixOS

  • El proyecto secure-suspend, publicado por separado, ofrece secure suspend-to-RAM experimental en NixOS
  • Con el cifrado de disco completo habitual, cuando una laptop está suspendida las claves permanecen en memoria, por lo que puede ser vulnerable a cold boot attacks o a métodos de filtración de RAM
  • Este proyecto revive un antiguo parche de kernel de Pali Rohár para borrar las claves de cifrado LUKS al suspender
  • Se inspira en cryptsetup-suspend de Debian, pero usa un parche de kernel para evitar la condición de carrera que a veces impide que la laptop se suspenda y agrega medidas preventivas adicionales
  • Soporta completamente un root filesystem cifrado y también ofrece pruebas de integración
  • El parche de kernel y las herramientas de espacio de usuario pueden adaptarse también a otras distribuciones Linux
  • El proyecto está publicado como secure-suspend

Por qué es difícil verificar la seguridad de la suspensión

  • Que se vea la pantalla de bloqueo después de suspender no significa que el dispositivo de almacenamiento haya quedado realmente bloqueado
  • Si justo después de reanudar desde la suspensión se puede acceder al disco de inmediato, eso indica que el almacenamiento nunca estuvo bloqueado desde el principio
    • Por ejemplo, se puede verificar el acceso al disco con un script que siga ejecutándose detrás de la pantalla de bloqueo
    • Si después de suspender es posible conectarse por SSH con clave pública sin desbloquear primero el almacenamiento cifrado, es fácil confirmar que el almacenamiento no estaba bloqueado
  • También hay comentarios que señalan que las configuraciones predeterminadas de Ubuntu o Debian ni siquiera intentaban ofrecer este tipo de protección
  • Hay que verificar por separado si el intento de bloquear el almacenamiento realmente funcionó correctamente
    • Las marcas de tiempo de los logs pueden haberse creado antes de la suspensión, pero registrarse después del wake
    • A la inversa, un log generado justo después del wake, antes de que se ajuste la hora del sistema, también puede parecer tener la hora del momento de suspensión
  • Que el bloqueo del almacenamiento haya ocurrido justo antes de suspender o justo después de reanudar puede verse igual para el usuario, pero desde el punto de vista de seguridad es una diferencia decisiva
  • La prueba de integración de NixOS arranca el sistema en una máquina virtual y vuelca la memoria para comprobar si las claves se borraron realmente durante la suspensión

1 comentarios

 
GN⁺ 3 시간 전
Opiniones de Hacker News
  • Es cierto que es un bug interesante, pero el título se siente un poco como clickbait.
    Según entiendo, cryptsetup luksSuspend no es tanto una función con soporte oficial, sino más bien una extensión creada por Debian, así que me parece que esta regresión solo afectó a Debian.
    No estoy seguro de que se pueda culpar al kernel por una función que no tiene soporte o no se prueba ampliamente.
    Aun así, es impresionante, y es bueno que ahora haya pruebas para evitar que esta regresión vuelva a aparecer. También coincido con la opinión del OP de que NixOSTests es realmente excelente.
    Pero, con solo ver el título, parece un problema extendido y no algo de una distribución específica.

    • Intenté que el título fuera técnicamente correcto, no atraer clics.
      Correcto. No afecta a quienes usan la configuración predeterminada, porque en primer lugar no esperarían que la clave del volumen estuviera segura durante suspend.
      La solución de Debian fue portada a varias, probablemente la mayoría, de las demás distribuciones, y supongo que también había bastantes personas manteniendo ports propios.
      La página de manual de thread-keyring(7) promete que “el thread keyring se destruye cuando termina el hilo que lo referencia”.
      El proyecto cryptsetup dependía de esta propiedad en el mecanismo para subir la clave desde el espacio de usuario al espacio del kernel, pero el kernel 6.9 introdujo una regresión que rompió esa propiedad.
    • Me confunde que digan que esto es exclusivo de Debian. luksSuspend es una función upstream y se agregó en 2009 en la versión v1.1.0.
      En el pasado la usé ocasionalmente también en Arch y openSUSE, y claramente existe en distribuciones que no son Debian.
      Quizá estén pensando en la integración automática con system suspend, pero eso se desvía del punto central. luksSuspend está documentado como algo que elimina la clave de la memoria del sistema, y por ese parche de refactorización en Linux 6.9 ese comportamiento dejó de funcionar.
      Aunque, en la práctica, también podría considerarse un bug de cryptsetup, porque dependía de un comportamiento de ciclo de vida muy específico de las claves del keyring del kernel, y se podría argumentar que debió borrarlas de forma más explícita desde el espacio de usuario.
      [1]: https://gitlab.com/cryptsetup/cryptsetup/-/commit/3cea5dcc7b...
      [2]: https://gitlab.com/cryptsetup/cryptsetup/-/blob/main/docs/v1...
      [3]: https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/93...
    • El subcomando está en el repositorio oficial de cryptsetup y la descripción también parece correcta: https://gitlab.com/cryptsetup/cryptsetup/-/blob/main/man/cry...
    • He usado esta función en Arch, y también se puede usar con LUKS normal. Pero, hasta donde sé, no se usa por defecto al suspender.
      Probablemente se refieran a algún dispositivo que, después de luksSuspend, ejecuta RAM suspend de una forma realmente útil; eso al principio apuntaba a Debian y luego también apareció en Arch, pero en ninguno de los dos casos era el valor predeterminado.
    • Me da curiosidad saber en qué versión Debian distribuyó por primera vez 6.9.
  • No veo bien otra forma de hacerlo. Si se suspende, es decir, se usa RAM suspend, todo queda almacenado en la RAM y está cifrado, pero recuerdo que la clave maestra permanece en la memoria del kernel.
    En cambio, si se hiberna, es decir, se hace suspend a disco, todo el contenido de la RAM, incluida la clave maestra, se escribe al disco y se cifra, y la RAM se borra.
    Al despertar de nuevo, hay que volver a ingresar la frase de contraseña para descifrar la clave maestra y volver a cargar el contenido del disco en memoria.

    • Exacto. En la mayoría de las distribuciones Linux predeterminadas, si simplemente suspendes la laptop, todo queda en memoria, incluida la clave maestra.
      Pero Debian primero creó un complemento opcional llamado cryptsetup-suspend, que ejecuta el comando luksSuspend, diseñado para borrar la clave de la memoria, y luego vuelve a pedir la frase de contraseña al reanudar.
      Hasta el kernel 6.8 funcionaba como se describe, pero desde el kernel 6.9 dejó de funcionar silenciosamente.
    • Las CPU Intel/AMD de más o menos los últimos 5 años soportan cifrado completo de memoria transparente para el sistema operativo.
      Si se activa esta función, los ataques de arranque en frío pasan a ser cosa del pasado. Normalmente viene desactivada por defecto porque reduce la velocidad de la RAM alrededor de un 0,5%.
  • Como después de Sleep no vuelvo a ingresar la contraseña de arranque, es evidente que la clave de cifrado sigue en memoria.

    • Es evidente que tu distribución no usa cryptsetup-luksSuspend.
  • Esto no es un problema que me preocupe demasiado.
    La única razón por la que uso cifrado de disco es para no preocuparme de que alguien revise documentos fiscales o información de tarjetas de crédito cuando venda la laptop.
    Claro que también borro la laptop, pero si los datos están cifrados a nivel de unidad, veo muy pequeño el riesgo de que alguien recupere datos con herramientas forenses o algo similar.

    • Como compromiso razonable, basta con borrar solo el encabezado LUKS.
      LUKS usa un algoritmo antiforense que requiere tener la clave de volumen completa para abrir el disco. Combina los bloques de clave con un algoritmo de difusión y los aplica con XOR para generar la clave maestra real, así que, en teoría, con borrar un solo sector de la clave de volumen debería quedar todo irrecuperable.
      Es decir, si falta incluso un bloque de la clave, no se puede adivinar fácilmente el resto.
    • Si asumimos que la clave de cifrado es fuerte, borrar es, en teoría, un trabajo redundante.
  • No soy ni de cerca especialista en seguridad, pero al ver que últimamente se descubren con regularidad bugs de seguridad críticos “causados por pasar por alto una línea de comprobación en C que atraviesa archivos durante una refactorización”, la premisa misma de una enorme base de código C open source segura me parece sospechosa.
    No es un problema exclusivo de C, pero creo que en C en particular es más difícil imponer y rastrear invariantes de manera consistente, y más aún cuando se cambia el código.
    Tampoco sé si la programación funcional, que codifica invariantes en tipos, sea una solución realmente escalable. ¿Verificación de modelos? ¿Fuzzing con LLM? ¿Menos primitivas con límites más claros? ¿seLinux fue “verificado” de esa manera?

    • Las desventajas de C son visibles y en general tampoco lo recomendaría para proyectos nuevos, pero no creo que este bug en particular sea un buen ejemplo de algo que el borrow checker de Rust o el sistema de tipos de otro lenguaje habrían detectado. Tampoco creo que un analizador estático lo hubiera detectado.
      En esencia es algo así:
      original: DoTheThing()
      new: DoTheThingSlightlyDifferentButKeepMyCredentialsAlive()
      fix: DoTheThingSlightlyDifferentButDoInFactNOTKeepMyCredentialsAlive()
      Según mi experiencia, una parte considerable de los bugs difíciles proviene de violaciones de invariantes de sistema de alto nivel, y esto no parece ser algo que se pueda automatizar.
      Incluso con algo como Lean se puede demostrar que un programa cumple cierta propiedad, pero primero había que haber pensado en esa propiedad. La prueba no descubre los invariantes por ti.
      Si se hubiera pensado en la propiedad de seguridad relevante, no habría sido difícil escribir una prueba de regresión. Creo que la parte realmente difícil no es expresar la implementación de forma segura, sino darse cuenta de que existe una propiedad que la implementación debe preservar.
    • La premisa de una base de código pública segura en sí está bien.
      El problema es que una mayor auditabilidad no significa automáticamente que se audite más.
      Hace falta que personas con suficiente capacidad le dediquen suficiente tiempo.
    • Si se hubiera traducido a Rust, habría sido “se pasó por alto una línea de comprobación en Rust”.
      Es un bug causado por el cruce de áreas de interés y por falta de conocimiento entre dominios. Probablemente habría sido igual en Lisp o en ensamblador.
    • La lección aquí es que, si una funcionalidad no tiene al menos casos de prueba relacionados, entonces no es una funcionalidad real.
    • La razón por la que la premisa de una “enorme base de código C open source segura” parece sospechosa es que la revisión de código a veces no es muy distinta de una versión idealizada del problema de la parada, con acceso a una versión formalizada de la especificación.
      En otras palabras, no hay una definición rigurosa de qué es un problema de seguridad.
  • ¿Será que alguna agencia federal necesitaba desesperadamente una forma de obtener las claves? ¿Esto es un bugdoor? ¿Se rastreó el commit?
    Últimamente he visto mucho este patrón, así que empiezo a ponerme un poco suspicaz. También puede ser que la gente esté más sensible a esto y por eso lo publique más.

    • Esto es una regresión. La aplicación en espacio de usuario también habría fallado silenciosamente, y fue el resultado de una cadena de descuidos.
      Que la clave de cifrado esté en memoria no significa que pueda extraerse sin más. Es más bien que se dejó innecesariamente y por tiempo indefinido en un lugar donde no debería estar.
  • Estas regresiones son fáciles de pasar por alto porque todo sigue “funcionando”. Los bugs de seguridad muchas veces no se delatan solos.

    • Exacto. Por eso las pruebas de integración son más importantes para funciones como esta.
      También fue divertido escribirlas, y permitieron correr git-bisect para encontrar la refactorización específica del kernel que introdujo este bug: https://github.com/NixOS/nixpkgs/pull/532499
  • En mi laptop Fedora tengo Linux configurado para hibernar al disco 15 minutos después de suspender. Si cortas la energía de la memoria, este bug específico de Debian no es un problema.
    La extensión de las herramientas de Linux de Debian suena bien en teoría, pero si de verdad te preocupan los ataques de arranque en frío, no solo las claves LUKS, sino todas las claves y documentos importantes deberían borrarse de la memoria.
    Así que, al final, la única forma correcta de prevenir el arranque en frío es hibernar.

    • Estoy de acuerdo. O también se podría resucitar FridgeLock: https://www.sec.in.tum.de/i20/publications/fridgelock-preven...
    • Pero al reanudar, ¿de dónde sacas la clave para descifrar la memoria?
      Hasta donde sé, no es práctico sin usar TPM. Y si usas TPM, básicamente estás poniendo tu destino en manos del TPM.
  • Basta imaginar cómo se vería este hilo de HN si esta vulnerabilidad hubiera estado en un sistema operativo comercial.
    El comentario principal seguramente diría que Applosoft ya no se preocupa por la calidad del software, o que “esto es lo que pasa cuando dejas entrar basura de vibe coding en el sistema operativo”.
    Los comentarios debajo serían teorías conspirativas sobre el complejo industrial de la vigilancia y la NSA; en otros lados sonarían como locuras, pero en HN no.

  • No entiendo por qué algo tan importante no se prueba en cada build.