Resolución incorrecta de enlaces en el protocolo de arrastrar y soltar de kitty permite a un cliente crear archivos fuera del directorio de almacenamiento temporal
Descripción
Resolución incorrecta de enlaces antes del acceso a ficheros (CWE-59) en subdir_data_for_drag() (kitty/dnd.c), en la ruta de preparación del origen de arrastre del protocolo de arrastrar y soltar, en kitty desde 0.47.0 y anterior a 0.49.0, que permite a un programa que escribe en el terminal crear ficheros y directorios en rutas situadas fuera del directorio de preparación, porque la función resuelve un descendiente del árbol de elementos preparados construyendo una cadena de ruta y abriéndola con safe_open(path, O_DIRECTORY | O_RDONLY, 0) en lugar de recorrer el árbol componente a componente, de modo que un cliente que declara dos entradas con el mismo nombre, la primera un enlace simbólico cuyo destino es una ruta absoluta arbitraria y la segunda un directorio, hace que mkdirat() falle con EEXIST, error que el código ignora, y que la resolución de ruta posterior siga el enlace simbólico y devuelva un descriptor de directorio situado fuera del directorio de preparación, que se almacena en parent->fd_plus_one y se pasa como argumento dirfd a add_payload(), empleándose en todas las operaciones de creación posteriores sobre ese elemento y sus descendientes. Los nombres de entrada se sanean frente a separadores de ruta y componentes de punto en populate_dir_entries(), pero los destinos de los enlaces simbólicos no se validan. Esto da lugar a la creación de ficheros y directorios en cualquier ruta en la que pueda escribir el usuario que ejecuta kitty, siempre que el destino del enlace simbólico sea un directorio existente.
La primitiva es únicamente de creación. Los ficheros se crean con O_CREAT | O_WRONLY | O_EXCL y modo 0644, por lo que la existencia previa de un fichero en la ruta de destino hace fallar la operación en lugar de sobrescribirlo, y la primitiva no aporta ninguna capacidad de lectura. Los directorios se crean con mkdirat() y modo 0755, tolerándose un resultado EEXIST, por lo que el atacante puede crear directorios intermedios que no existían previamente. La restricción es, por tanto, que el destino del enlace simbólico sea un directorio existente, y no que lo sea el directorio de destino final.
Las operaciones de creación en sí están acotadas. Un comentario en el código, en la rama de enlaces simbólicos, recoge que se aceptan destinos arbitrarios porque los ficheros sólo se crean con O_EXCL, y porque mkdirat() y symlinkat() fallan con EEXIST cuando ya existe un enlace simbólico en la ruta. Ese razonamiento es válido para las llamadas de creación; lo que no cubre es la resolución del directorio respecto al cual se realizan, que es donde reside el defecto.
En un sistema de ficheros de preparación que no distingue mayúsculas y minúsculas, uniqify_dir_entries_for_case_insensitive_fs() renombra la segunda de dos entradas homónimas como case-conflict-<n>-<nombre>. Ambas entradas subsisten, pero dejan de compartir nombre, por lo que no se produce la colisión de la que depende el mecanismo. La distinción de mayúsculas y minúsculas se detecta para cada directorio de preparación y la ruta sensible a mayúsculas es el comportamiento por defecto.
Tipo de vulnerabilidad (CWE)
- CWE-59: Resolución incorrecta de enlaces antes del acceso a ficheros ('Link Following')
Versiones afectadas
kitty desde 0.47.0 y anterior a 0.49.0. El protocolo de arrastrar y soltar se introdujo en kitty/dnd.c y se publicó por primera vez en v0.47.0 (19-05-2026); las versiones anteriores no contienen el código afectado. Están afectadas todas las versiones desde v0.47.0 hasta v0.48.2, ambas incluidas. Estado por defecto: no afectado.
Puntuación (CVSS 4.0)
Media (4.6)
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:N/SI:L/SA:N
La interacción del usuario es Activa porque a la ruta de preparación sólo se llega una vez que el usuario ha iniciado un arrastre desde la ventana del terminal, lo que constituye una acción deliberada y no la simple visualización de la salida del atacante. El impacto recae sobre el sistema subsiguiente y no sobre kitty, dado que los ficheros se crean en el sistema de ficheros del usuario, fuera del terminal. La integridad se puntúa como Baja y no Alta porque la primitiva no puede alterar un fichero que ya exista. La creación de un fichero en una ruta elegida por el atacante puede derivar en ejecución de código cuando el fichero creado es uno que el entorno lee posteriormente, por ejemplo una entrada de arranque automático de escritorio en un directorio que no existía previamente, pero ese resultado depende de la configuración de la víctima y no se puntúa.
Solución
Actualizar a kitty 0.49.0 o posterior.
Parche
Commit 554908ed, que sustituye la construcción de rutas por un recorrido componente a componente desde un directorio creado por la propia kitty, abriendo cada componente con O_NOFOLLOW y acotando el descenso con MAX_DRAG_DIR_DEPTH.
El mismo commit añade O_NOFOLLOW en la apertura del directorio de preparación de nivel superior en toplevel_data_for_drag(), cuyo nombre genera kitty, e incorpora ELOOP y ENOTDIR a los errores que se comunican al cliente, de modo que un arrastre que lo intente se deniega con un error en lugar de contenerse en silencio.
El commit 3c3133d3, titulado "Defense in depth use O_NOFOLLOW when opening regular files", añade O_NOFOLLOW en un único punto de llamada, en drop_send_file_data(). Ese es el camino de soltado, en el que kitty abre un fichero local en sólo lectura para enviar su contenido a un cliente, y no el camino de creación del origen de arrastre que describe este aviso. En el registro se cita como referencia relacionada y no como parche.
Créditos
- Darío Rivas Quero - descubridor
- Cristian Fernández Cornejo - analista
- Xoán M. Otero Jorge - analista
- Secur0 CNA - coordinador
- Kovid Goyal - desarrollador de la corrección Origen del descubrimiento: Interno
Registro oficial: CVE-2026-80430
Avisos relacionados
- CVE-2026-80431 - Severidad media
- CVE-2026-80432 - Severidad media