En artículos anteriores de la serie, discutimos la seguridad de la memoria y la seguridad de los hilos en Rust. En este último artículo, veremos las implicaciones de una aplicación Rust real usando el proyecto Quantum CSS .El motor CSS aplica reglas CSS a la página. Este es un proceso descendente que desciende el árbol DOM, después de calcular el CSS principal, los estilos secundarios se pueden calcular de forma independiente: ideal para la computación paralela. Para 2017, Mozilla hizo dos intentos de paralelizar el sistema de estilo usando C ++. Ambos fallaron.
El desarrollo cuántico de CSS ha comenzado a aumentar la productividad. Mejorar la seguridad es solo un buen efecto secundario.
Existe una cierta conexión entre la protección de la memoria y los errores de seguridad de la información. Por lo tanto, esperábamos que el uso de Rust redujera la superficie de ataque en Firefox. Este artículo analizará las vulnerabilidades potenciales que se han identificado en el motor CSS desde el lanzamiento inicial de Firefox en 2002. Luego mira lo que podría y no pudo haberse evitado con Rust.
Para siempre, se han detectado 69 errores de seguridad en el componente CSS de Firefox. Si tuviéramos una máquina del tiempo y pudiéramos escribir Rust desde el principio, entonces 51 (73.9%) errores serían imposibles. Aunque Rust facilita la escritura de un buen código, tampoco ofrece protección absoluta.
Herrumbre
Rust es un lenguaje de programación de sistema moderno que es seguro para los tipos y la memoria. Como efecto secundario de estas garantías de seguridad, los programas Rust también son seguros para subprocesos en tiempo de compilación. Por lo tanto, Rust es particularmente adecuado para:
- procesamiento seguro de datos entrantes poco confiables;
- concurrencia para mejorar el rendimiento;
- integración de componentes individuales en la base de código existente.
Sin embargo, Rust no corrige explícitamente algunas clases de error, especialmente los errores de corrección. De hecho, cuando nuestros ingenieros reescribieron Quantum CSS, repitieron accidentalmente un error de seguridad crítico, que anteriormente se solucionó en código C ++, eliminaron accidentalmente la
corrección de errores 641731 , que permite la filtración de la historia global a través de SVG. El error se volvió a registrar como
error 1420001 . Una fuga en el historial se considera una vulnerabilidad de seguridad crítica. La solución inicial fue una verificación adicional para ver si el documento SVG es una imagen. Desafortunadamente, esta verificación se perdió al reescribir el código.
Aunque las pruebas automatizadas deberían encontrar violaciones de la regla
:visited
esta manera, en la práctica no encontraron este error. Para acelerar las pruebas automáticas, deshabilitamos temporalmente el mecanismo que probó esta característica; las pruebas no son particularmente útiles si no se realizan. El riesgo de volver a implementar errores lógicos se puede reducir con una buena cobertura de prueba. Pero todavía existe el peligro de nuevos errores lógicos.
A medida que un desarrollador se familiariza con Rust, su código se vuelve aún más seguro. Aunque Rust no previene todas las vulnerabilidades posibles, corrige toda una clase de los errores más graves.
Errores de seguridad CSS cuánticos
En general, de forma predeterminada, Rust evita errores relacionados con la memoria, límites, variables nulas / no inicializadas y desbordamientos de enteros. El error no estándar mencionado anteriormente sigue siendo posible: se produce un bloqueo debido a una asignación de memoria fallida.
Errores de seguridad por categoría
- Memoria: 32
- Fronteras: 12
- Implementación: 12
- Nulo: 7
- Desbordamiento de pila: 3
- Desbordamiento de enteros: 2
- Otro: 1
En nuestro análisis, todos los errores están relacionados con la seguridad, pero solo 43 recibieron una calificación oficial (es asignada por los ingenieros de seguridad de Mozilla con base en suposiciones calificadas sobre "explotabilidad"). Los errores comunes pueden indicar funciones faltantes o algún tipo de mal funcionamiento, lo que no necesariamente conduce a la fuga de datos o al cambio de comportamiento. Los errores de seguridad oficiales varían desde baja importancia (si hay una fuerte restricción en la superficie de ataque) hasta vulnerabilidad crítica (puede permitir que un atacante ejecute código arbitrario en la plataforma del usuario).
Las vulnerabilidades de memoria a menudo se clasifican como problemas de seguridad graves. De los 34 problemas críticos / graves, 32 estaban relacionados con la memoria.
Distribución de gravedad de errores de seguridad.
- Total: 70
- Errores de seguridad: 43
- Crítico / grave: 34
- Óxido fijo: 32
Comparación de óxido y C ++
Error 955913 : desbordamiento del búfer de almacenamiento dinámico en la función
GetCustomPropertyNameAt
. El código utilizó la variable incorrecta para la indexación, lo que condujo a la interpretación de la memoria después del final de la matriz. Esto puede provocar un bloqueo al acceder a un puntero incorrecto o al copiar memoria en una cadena que se pasa a otro componente.
El orden de todas las
propiedades CSS (incluidas las personalizadas, es decir, personalizadas) se almacena en la matriz
mOrder
. Cada elemento está representado por un valor de propiedad CSS o, en el caso de propiedades personalizadas, un valor que comienza con
eCSSProperty_COUNT
(número total de propiedades CSS no personalizadas). Para obtener el nombre de las propiedades personalizadas, primero debe obtener el valor de
mOrder
y luego acceder al nombre en el índice correspondiente de la matriz
mVariableOrder
, que almacena los nombres de las propiedades personalizadas en orden.
Código vulnerable de C ++:
void GetCustomPropertyNameAt(uint32_t aIndex, nsAString& aResult) const { MOZ_ASSERT(mOrder[aIndex] >= eCSSProperty_COUNT); aResult.Truncate(); aResult.AppendLiteral("var-"); aResult.Append(mVariableOrder[aIndex]);
El problema ocurre en la línea 6 cuando se usa
aIndex
para acceder al elemento de la matriz
mVariableOrder
. El hecho es que
aIndex
debe usarse con la matriz
mOrder
, no con la
mVariableOrder
. El elemento correspondiente para la propiedad personalizada representada por
aIndex
en
mOrder
es en realidad
mOrder[aIndex] - eCSSProperty_COUNT
.
Código C ++ corregido:
void Get CustomPropertyNameAt(uint32_t aIndex, nsAString& aResult) const { MOZ_ASSERT(mOrder[aIndex] >= eCSSProperty_COUNT); uint32_t variableIndex = mOrder[aIndex] - eCSSProperty_COUNT; aResult.Truncate(); aResult.AppendLiteral("var-"); aResult.Append(mVariableOrder[variableIndex]); }
Código de óxido correspondiente
Aunque Rust es algo similar a C ++, utiliza otras abstracciones y estructuras de datos. El código de óxido será muy diferente de C ++ (ver más abajo para más detalles). Primero, veamos qué sucede si el código vulnerable se traduce lo más literalmente posible:
fn GetCustomPropertyNameAt(&self, aIndex: usize) -> String { assert!(self.mOrder[aIndex] >= self.eCSSProperty_COUNT); let mut result = "var-".to_string(); result += &self.mVariableOrder[aIndex]; result }
El compilador de Rust aceptará este código porque la longitud de los vectores no se puede determinar antes de la ejecución. A diferencia de las matrices, cuya longitud debe conocerse, el
tipo Vec en Rust tiene un tamaño dinámico. Sin embargo, la verificación de límites está integrada en la implementación del vector de biblioteca estándar. Si aparece un índice no válido, el programa termina inmediatamente de manera controlada, evitando cualquier acceso no autorizado.
El
código real de Quantum CSS utiliza estructuras de datos muy diferentes, por lo que no hay un equivalente exacto. Por ejemplo, utilizamos las poderosas estructuras de datos integradas de Rust para unificar la disposición y los nombres de propiedad. Esto elimina la necesidad de mantener dos matrices independientes. Las estructuras de datos de óxido también mejoran la encapsulación de datos y reducen la probabilidad de tales errores lógicos. Dado que el código debe interactuar con el código C ++ en otras partes del navegador, la nueva función
GetCustomPropertyNameAt
se parece al código idiomático de Rust. Pero aún ofrece todas las garantías de seguridad, al tiempo que proporciona una abstracción más comprensible de los datos subyacentes.
tl; dr
Debido a que las vulnerabilidades a menudo se asocian con infracciones de seguridad de la memoria, el código Rust debería reducir significativamente la cantidad de
CVE críticos. Pero incluso Rust no es perfecto. Los desarrolladores aún necesitan rastrear los errores de corrección y los ataques de fuga de datos. El soporte para bibliotecas seguras aún requiere revisiones de código, pruebas y fuzzing.
Los compiladores no pueden detectar todos los errores de programador. Sin embargo, Rust nos quita la carga de seguridad de la memoria, lo que nos permite centrarnos en la corrección lógica del código.