Skip to content

Commit 9dd9cb0

Browse files
FichteFollclaude
andcommitted
Serialize X11 Display access in the Linux Ctrl-release poller
libX11 is not thread-safe (XInitThreads is not called), yet a single Display connection was used unsynchronized from two threads: the poller thread (XQueryKeymap, and XCloseDisplay on exit) and the main thread via is_ctrl_down (XQueryKeymap). This raced the query against the close and against itself, driving _XReply into a freed/corrupt buffer and crashing plugin_host with SIGSEGV. Guard every use of the shared Display with a threading.Lock so a query can never overlap a close or another query. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent fc3f521 commit 9dd9cb0

1 file changed

Lines changed: 16 additions & 9 deletions

File tree

‎plugin/ctrl_release/linux.py‎

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ def __init__(self, on_release, interval_ms):
3232
self._interval_ms = interval_ms
3333
self._state = self._open_x11_state()
3434
self._stop_event = threading.Event()
35+
# libX11 is not thread-safe; serialize all access to the shared Display
36+
# between the poller thread and the main thread (is_ctrl_down).
37+
self._display_lock = threading.Lock()
3538

3639
def start(self) -> None:
3740
if self.is_alive():
@@ -52,9 +55,10 @@ def run(self) -> None:
5255
self._close_state()
5356

5457
def _close_state(self) -> None:
55-
if self._state is not None:
56-
self._state.xclose_display(self._state.display)
57-
self._state = None
58+
with self._display_lock:
59+
if self._state is not None:
60+
self._state.xclose_display(self._state.display)
61+
self._state = None
5862

5963
def _fire_release(self) -> None:
6064
if self._stop_event.is_set():
@@ -63,14 +67,17 @@ def _fire_release(self) -> None:
6367
sublime.set_timeout(self._on_release)
6468

6569
def _ctrl_down(self) -> bool:
66-
state = self._state
67-
if state is None:
68-
return False
6970
keymap = (ctypes.c_ubyte * 32)()
70-
state.xquery_keymap(state.display, keymap)
71-
return _keycode_is_down(keymap, state.keycode_left) or _keycode_is_down(
71+
with self._display_lock:
72+
state = self._state
73+
if state is None:
74+
return False
75+
state.xquery_keymap(state.display, keymap)
76+
keycode_left = state.keycode_left
77+
keycode_right = state.keycode_right
78+
return _keycode_is_down(keymap, keycode_left) or _keycode_is_down(
7279
keymap,
73-
state.keycode_right,
80+
keycode_right,
7481
)
7582

7683
def is_ctrl_down(self) -> bool:

0 commit comments

Comments
 (0)