This is an automated email from the git hooks/post-receive script.
git pushed a commit to reference refs/pull/36/head
in repository terminology.
View the commit online.
commit 9f97638f5c63fb85f76c53bf51ffd67bfa09cacc
Author: [email protected] <[email protected]>
AuthorDate: Sat Mar 21 17:12:51 2026 -0600
fix: defer tymux widget switch to avoid use-after-free
_termio_tymux_switch was called from within the PTY's read handler
chain (_handle_read → termpty_handle_buf → _handle_esc →
_smart_pty_command). Calling termpty_free on the PTY mid-parse left
dangling pointers up the call stack, causing a crash on the next
write to the freed Termpty.
Fix: store the session name in sd->tymux_pending and schedule the
actual switch via ecore_job_add. The job runs on the next main loop
iteration, after the PTY read handler has returned cleanly.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
src/bin/termio.c | 37 ++++++++++++++++++++++++++++---------
src/bin/termiointernals.h | 1 +
2 files changed, 29 insertions(+), 9 deletions(-)
diff --git a/src/bin/termio.c b/src/bin/termio.c
index 16568308..024f33b6 100644
--- a/src/bin/termio.c
+++ b/src/bin/termio.c
@@ -3537,6 +3537,7 @@ _smart_del(Evas_Object *obj)
if (sd->pty) termpty_free(sd->pty);
#ifdef HAVE_TYMUX
eina_stringshare_del(sd->tymux_name);
+ eina_stringshare_del(sd->tymux_pending);
#endif
eina_stringshare_del(sd->link.string);
if (sd->glayer) evas_object_del(sd->glayer);
@@ -3813,14 +3814,21 @@ _tymux_name_valid(const char *name)
return EINA_TRUE;
}
+/* Deferred tymux switch — runs on the next Ecore main loop iteration
+ * so we don't free the PTY while we're still inside its read handler. */
static void
-_termio_tymux_switch(Evas_Object *obj, Termio *sd, const char *tymux_name)
+_termio_tymux_switch_job(void *data)
{
+ Evas_Object *obj = data;
+ Termio *sd = evas_object_smart_data_get(obj);
Termpty *spty = NULL;
TermTymux *tymux = NULL;
+ const char *tymux_name;
- EINA_SAFETY_ON_NULL_RETURN(tymux_name);
- if (!_tymux_name_valid(tymux_name)) return;
+ if (!sd) return;
+ tymux_name = sd->tymux_pending;
+ if (!tymux_name) return;
+ sd->tymux_pending = NULL;
/* Clean up any previous tymux or plain PTY state before attaching. */
if (sd->tymux)
@@ -3835,8 +3843,6 @@ _termio_tymux_switch(Evas_Object *obj, Termio *sd, const char *tymux_name)
}
else if (sd->pty)
{
- /* Plain PTY — free it. The shell and 'tymux attach' helper both
- * receive SIGHUP when the PTY master is closed. */
termpty_free(sd->pty);
sd->pty = NULL;
}
@@ -3848,16 +3854,16 @@ _termio_tymux_switch(Evas_Object *obj, Termio *sd, const char *tymux_name)
if (!tymux)
{
ERR("tymux switch: could not attach to '%s'", tymux_name);
+ eina_stringshare_del(tymux_name);
return;
}
- /* Install the shadow PTY that renders the tymux session. */
sd->pty = spty;
sd->pty->obj = obj;
/* Replace tymux tracking fields. */
eina_stringshare_del(sd->tymux_name);
- sd->tymux_name = eina_stringshare_add(tymux_name);
+ sd->tymux_name = tymux_name; /* take ownership of the stringshare */
sd->tymux = tymux;
/* Wire callbacks — same pattern as the tymux branch in termio_add(). */
@@ -3870,13 +3876,26 @@ _termio_tymux_switch(Evas_Object *obj, Termio *sd, const char *tymux_name)
sd->tymux->cb_exit.func = _smart_pty_exited;
sd->tymux->cb_exit.data = ""
- /* Light up the tymux indicator in the containing Term widget. */
if (sd->term)
term_tymux_indicator_set(sd->term, EINA_TRUE);
- /* Force an immediate redraw so the session content appears. */
_smart_apply(obj);
}
+
+static void
+_termio_tymux_switch(Evas_Object *obj, Termio *sd, const char *tymux_name)
+{
+ EINA_SAFETY_ON_NULL_RETURN(tymux_name);
+ if (!_tymux_name_valid(tymux_name)) return;
+
+ /* Store the tymux name and schedule the actual switch for the next
+ * main loop iteration. We cannot free the PTY here because we are
+ * called from within its read handler (_handle_read → termpty_handle_buf
+ * → _handle_esc → _smart_pty_command). Freeing it now would leave
+ * dangling pointers up the call stack. */
+ eina_stringshare_replace(&sd->tymux_pending, tymux_name);
+ ecore_job_add(_termio_tymux_switch_job, obj);
+}
#endif /* HAVE_TYMUX */
static void
diff --git a/src/bin/termiointernals.h b/src/bin/termiointernals.h
index 61417573..1b766b49 100644
--- a/src/bin/termiointernals.h
+++ b/src/bin/termiointernals.h
@@ -81,6 +81,7 @@ struct tag_Termio
/* tymux_name: tymux name (eina_stringshare). Kept for display
* purposes (e.g. window title) even after the tymux fd is open. */
const char *tymux_name; /* eina_stringshare */
+ const char *tymux_pending; /* eina_stringshare; deferred switch target */
#endif
Ecore_Animator *anim;
Ecore_Timer *delayed_size_timer;
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.