La condición de carrera ocurre cuando varios subprocesos leen y escriben la misma variable, es decir, tienen acceso a algunos datos compartidos e intentan cambiarlos al mismo tiempo. En tal escenario, los subprocesos están «compitiendo» entre sí para acceder/cambiar los datos.
Esta es una vulnerabilidad de seguridad importante [ CWE-362 ], y al manipular el tiempo de las acciones pueden aparecer resultados anómalos. Esta vulnerabilidad surge durante una ventana TOCTOU (hora de verificación, hora de uso).
Flujo de acceso a archivos durante su ventana TOCTOU
Entonces, ¿qué pasa si bloqueamos el archivo durante esta ventana de TOCTOU?
- Concepto erróneo general:
una cura trivial para esta vulnerabilidad podría ser bloquear el archivo en sí durante esta ventana de verificación y uso, porque entonces ningún otro proceso puede usar el archivo durante la ventana de tiempo.Parece fácil, entonces ¿por qué no es esto práctico? ¿Por qué no podemos usar este enfoque para resolver el problema de la condición de carrera?
La respuesta es simplemente que tal vulnerabilidad no podría prevenirse simplemente bloqueando el archivo. - Problemas al bloquear el archivo:
un archivo se bloquea para otros procesos solo si ya está en estado abierto. Este proceso se denomina proceso de verificación y apertura y durante este tiempo es imposible bloquear un archivo. Cualquier bloqueo creado puede ser ignorado por el atacante o el proceso malicioso.Lo que realmente sucede es que la llamada a Open() no bloquea un ataque a un archivo bloqueado. Cuando el archivo está disponible para un proceso de verificación y apertura, el archivo en realidad está abierto para cualquier acceso/cambio. Por lo tanto, es imposible bloquear un archivo en este momento. Esto hace que cualquier tipo de bloqueo sea prácticamente inexistente para los procesos maliciosos.
Internamente está usando sleep_time que se duplica en cada intento. Más comúnmente, esto se conoce como spinlock o la forma ocupada de espera. Además, siempre existe la posibilidad de que el archivo se bloquee indefinidamente, es decir, existe el peligro de quedarse atascado en un interbloqueo.
- ¿Qué pasaría incluso si de alguna manera pudiéramos bloquear el archivo?
Intentemos bloquear el archivo y ver cuáles podrían ser los posibles inconvenientes. El mecanismo de bloqueo más común que está disponible es el bloqueo de archivos atómicos. Se realiza utilizando un archivo de bloqueo para crear un archivo único en el mismo sistema de archivos. Hacemos uso de link() para hacer un enlace al archivo de bloqueo para cualquier tipo de acceso al archivo.- Si link() devuelve 0, el bloqueo es exitoso.
La solución más común disponible es almacenar el PID de la aplicación en el archivo de bloqueo, que se compara con el PID activo en ese momento. Por otra parte, una falla con esta solución es que ƒPID puede haber sido reutilizado.
- Soluciones reales:
una mejor solución es, en lugar de crear bloqueos en el archivo como un todo, bloquear las partes del archivo en diferentes procesos.Ejemplo:
cuando un proceso quiere escribir en un archivo, primero le pide al kernel que bloquee ese archivo o una parte de él. Mientras el proceso mantenga el bloqueo, ningún otro proceso puede solicitar bloquear la misma parte del archivo. Por lo tanto, podría ver que el problema con la concurrencia se resuelve de esta manera.De la misma manera, un proceso solicita el bloqueo antes de leer el contenido de un archivo, lo que garantiza que no se realizarán cambios mientras se mantenga el bloqueo.
La diferenciación de estos diferentes tipos de cerraduras la realiza el propio sistema. El sistema tiene la capacidad de distinguir entre los bloqueos necesarios para la lectura de archivos y los necesarios para la escritura de archivos. Este tipo de sistema de bloqueo se logra mediante la llamada al sistema flock(). La llamada Flock() puede tener diferentes valores:
- LOCK_SH (bloqueo para lectura)
- LOCK_EX (para escribir)
- LOCK_UN (liberación de la cerradura)
Usando estas llamadas separadas, podemos decir qué tipo de bloqueos son necesarios.
Un punto a tener en cuenta aquí es que muchos procesos pueden beneficiarse de un bloqueo de lectura simultáneamente, ya que nadie intentará cambiar el contenido del archivo. Sin embargo, solo un proceso puede beneficiarse de un bloqueo para escribir en un momento dado que lo está utilizando actualmente. Por lo tanto, no se puede permitir ningún otro bloqueo al mismo tiempo, incluso para lectura.
Este tipo de sistema cuidadosamente diseñado funciona bien con aplicaciones que pueden pedirle al núcleo que reserve su acceso (su bloqueo) antes de leer o escribir en un archivo importante del sistema. Por lo tanto, esta forma de bloquear selectivamente el archivo es mucho más práctica que nuestro enfoque inicial. Entonces, mientras intenta implementar su propio sistema de archivos para directorios, puede aprovechar esta técnica de codificación segura para evitar un CWE-362 potencial (vulnerabilidad de condición de carrera).