Cómo un hilo o proceso puede sobrescribir un archivo de base de datos SQLite

Fuentes: File overwrite by a rogue thread or process

SQLite presume de una elevada resistencia a la corrupción: ante un fallo de aplicación, del sistema operativo o un corte de alimentación, la transacción parcialmente escrita se revierte automáticamente al volver a abrir el archivo, sin intervención del usuario. Sin embargo, esa resistencia no es inmunidad: el presente documento repasa las vías por las que una base de datos SQLite puede acabar dañada.

Una de las causas más insidiosas es la reutilización de descriptores de archivo. Si un proceso abre un descriptor, lo cierra y SQLite lo reutiliza para abrir la base de datos, otro hilo puede seguir escribiendo en el descriptor original y sobrescribir así fragmentos del fichero, volviéndolo ilegible. Un caso célebre ocurrió hacia el 30 de agosto de 2013 en el repositorio canónico de Fossil, donde stunnel cerró por error el descriptor 2 (stderr) y una aserción posterior lo reescribió dentro del archivo de base de datos. Desde la versión 3.8.1 (2013-10-17), SQLite rechaza descriptores de número bajo para evitarlo. Otros episodios similares han sido documentados por ingenieros de Facebook en 2014 y en Fossil en julio de 2019.

También puede producirse corrupción cuando un sistema de respaldos automáticos realiza una copia mientras hay una transacción en curso, generando un archivo mezclado con contenido antiguo y nuevo. SQLite ofrece tres rutas seguras para respaldar una base de datos en caliente: la utilidad sqlite3_rsync (disponible desde 3.47.0, octubre de 2024), el comando VACUUM INTO y la API backup. La condición mínima es copiar la base de datos junto con los ficheros auxiliares -journal o -wal que puedan existir, necesarios para la recuperación tras caída.

Otro foco de problemas es la manipulación de los ficheros de journaling (llamados "hot"), que SQLite necesita para reconstruir el estado tras un fallo. Si esos ficheros se mueven, se borran o se renombran, la recuperación automática falla y la base de datos puede quedar irrecuperable. La documentación también advierte sobre la corrupción por uso inconsistente de nombres 8+3 en sistemas que truncan nombres largos.

Por último, SQLite utiliza bloqueos a nivel de sistema de archivos para coordinar el acceso entre procesos y evitar escrituras concurrentes incompatibles. El mecanismo por defecto en Unix —bloqueo consultivo POSIX— presenta debilidades conocidas: cualquier hilo con un descriptor que retenga un bloqueo puede anularlo desde otro descriptor, y una llamada close() del mismo proceso cancela todos los bloqueos consultivos sobre el archivo. Esto puede provocar que varios hilos o procesos escriban a la vez y dañen la base de datos. El problema solo surge cuando un hilo intenta saltarse la biblioteca SQLite y acceder al archivo directamente con open/read/close.