Skip to content
cve-2026-95834 ·
Medium · September 25, 2026

Use after free in the kitty drag and drop protocol when a drag source item is aborted mid-transfer

S0
Secur0 CNA
cve-2026-95834

Description

Use After Free (CWE-416) in drag_remote_file_data() (kitty/dnd.c), in the drag source path of the drag and drop protocol, in kitty from 0.47.0 before 0.49.0, which allows a program writing to the terminal to cause the terminal to read from and write to freed heap memory, because the function holds a DragRemoteItem pointer into an array it does not own, calls toplevel_data_for_drag() or subdir_data_for_drag(), and then continues to use that pointer.

Those helpers, and add_payload() and populate_dir_entries() which they call, report errors through the abrt() macro, which expands to cancel_drag() followed by a plain return, and cancel_drag() calls drag_free_offer(), which frees the array the pointer refers to. The helpers return void, so the caller receives no indication that the teardown happened, and proceeds to call all_children_complete() on the freed pointer, which dereferences it, and then to write through it. In the top level branch, clearing the owning structure's own pointers during teardown does not help, because the stale pointer is a local variable in the caller's stack frame.

Not every abort reaches the dereference. The caller guards it with a flag set only when the request carries no payload and announces no further data, and that is the same condition selecting the finalisation block of add_payload(), so it is the error paths in that block that matter. Two of those paths are determined entirely by the client's own bytes, because a client that declares two entries under one name makes the second create fail with EEXIST: the file create, which uses O_CREAT with O_EXCL, and the symlink create, which uses symlinkat(). The aborts in toplevel_data_for_drag() itself are reachable the same way. Aborts carrying a payload cannot reach the dereference at all.

Both branches of the caller are affected. subdir_data_for_drag() sets the caller's pointer to NULL on entry and assigns it only after its own last error path, so its own aborts leave the caller with NULL and are stopped by a null check, but it then calls add_payload() with that pointer set, and an abort there leaves the caller holding a freed child node inside the item tree.

Vulnerability type (CWE)

  • CWE-416: Use After Free

Affected versions

kitty from 0.47.0 before 0.49.0. The drag and drop protocol was introduced in kitty/dnd.c, first released in v0.47.0; releases before that do not contain the affected code. Every release from v0.47.0 to v0.48.2 inclusive is affected. Default status: unaffected.

Score (CVSS 4.0)

Medium (4.6)

CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N

The impact metrics are Low rather than High because the memory written after the free is not memory whose contents the attacker controls, and the fields the freed pointer is read through sit beyond the region the allocator reuses for its own bookkeeping. User interaction is Active because the sequence requires a drag to be under way over the window, which the user has to start. Attack requirements are None: nothing about the attack itself is conditional, and the limited consequence is carried by the impact metrics rather than counted a second time in the requirements metric.

The severity of this issue rests on the defect rather than on a consequence that follows from it. drag_free_remote_item() zeroes each item before the array holding it is freed, so the field the freed pointer is read through returns zero rather than attacker data, and the field written to lies beyond the region the allocator reuses for its own bookkeeping. Behaviour after a free is nonetheless undefined and depends on the allocator, which is why this is scored as a defect worth fixing rather than dismissed. It is a use after free in a path a client reaches on demand, and Kovid Goyal has fixed it in 0.49.0. Users should upgrade.

Solution

Upgrade to kitty 0.49.0 or later.

Patch

Commit b54bfbb4, which changes populate_dir_entries(), add_payload(), toplevel_data_for_drag() and subdir_data_for_drag() to return a status the caller checks, so that drag_remote_file_data() returns immediately when the offer has been torn down rather than continuing to use its local pointer. The abrt() macro is redefined to return that status across exactly those four functions and restored to its original form afterwards, so no other error path changes behaviour.

Credits

  • Gabriel Machado Tavares - finder
  • Cristian Fernández Cornejo - analyst
  • Xoán M. Otero Jorge - analyst
  • Secur0 CNA - coordinator
  • Kovid Goyal - remediation developer Discovery source: External

Official record: CVE-2026-95834

Related advisories

  • CVE-2026-95832 - Critical severity
  • CVE-2026-95835 - Medium severity