Blame SOURCES/0001-Avoid-recursing-through-_XError-due-to-sequence-adju.patch

936a6c
From 8c92ef59890c6d6e2be456658d3b9c145eda8629 Mon Sep 17 00:00:00 2001
936a6c
From: Keith Packard <keithp@keithp.com>
936a6c
Date: Sat, 7 Nov 2020 22:22:47 -0800
936a6c
Subject: [PATCH libX11] Avoid recursing through _XError due to sequence
936a6c
 adjustment
936a6c
936a6c
This patch is based on research done by Dmitry Osipenko to uncover the
936a6c
cause of a large class of Xlib lockups.
936a6c
936a6c
_XError must unlock and re-lock the display around the call to the
936a6c
user error handler function. When re-locking the display, two
936a6c
functions are called to ensure that the display is ready to generate a request:
936a6c
936a6c
    _XIDHandler(dpy);
936a6c
    _XSeqSyncFunction(dpy);
936a6c
936a6c
The first ensures that there is at least one XID available to use
936a6c
(possibly calling _xcb_generate_id to do so). The second makes sure a
936a6c
reply is received at least every 65535 requests to keep sequence
936a6c
numbers in sync (possibly generating a GetInputFocus request and
936a6c
synchronously awaiting the reply).
936a6c
936a6c
If the second of these does generate a GetInputFocus request and wait
936a6c
for the reply, then a pending error will cause recursion into _XError,
936a6c
which deadlocks the display.
936a6c
936a6c
One seemingly easy fix is to have _XError avoid those calls by
936a6c
invoking InternalLockDisplay instead of LockDisplay. That function
936a6c
does everything that LockDisplay does *except* call those final two
936a6c
functions which may end up receiving an error.
936a6c
936a6c
However, that doesn't protect the system from applications which call
936a6c
some legal Xlib function from within their error handler. Any Xlib
936a6c
function which cannot generate protocol or wait for events is valid,
936a6c
including many which invoke LockDisplay.
936a6c
936a6c
What we need to do is make LockDisplay skip these two function calls
936a6c
precisely when it is called from within the _XError context for the
936a6c
same display.
936a6c
936a6c
This patch accomplishes this by creating a list of threads in the
936a6c
display which are in _XError, and then having LockDisplay check the
936a6c
current thread against those list elements.
936a6c
936a6c
Inspired-by: Dmitry Osipenko <digetx@gmail.com>
936a6c
Signed-off-by: Keith Packard <keithp@keithp.com>
936a6c
Tested-by: Dmitry Osipenko <digetx@gmail.com>
936a6c
Reviewed-by: Dmitry Osipenko <digetx@gmail.com>
936a6c
(cherry picked from commit 30ccef3a48029bf4fc31d4abda2d2778d0ad6277)
936a6c
---
936a6c
 include/X11/Xlibint.h |  2 ++
936a6c
 src/OpenDis.c         |  1 +
936a6c
 src/XlibInt.c         | 10 ++++++++++
936a6c
 src/locking.c         | 12 ++++++++++++
936a6c
 src/locking.h         | 12 ++++++++++++
936a6c
 5 files changed, 37 insertions(+)
936a6c
936a6c
diff --git a/include/X11/Xlibint.h b/include/X11/Xlibint.h
936a6c
index 6b95bcf7..09078e3f 100644
936a6c
--- a/include/X11/Xlibint.h
936a6c
+++ b/include/X11/Xlibint.h
936a6c
@@ -202,6 +202,8 @@ struct _XDisplay
936a6c
 	unsigned long last_request_read_upper32bit;
936a6c
 	unsigned long request_upper32bit;
936a6c
 #endif
936a6c
+
936a6c
+	struct _XErrorThreadInfo *error_threads;
936a6c
 };
936a6c
 
936a6c
 #define XAllocIDs(dpy,ids,n) (*(dpy)->idlist_alloc)(dpy,ids,n)
936a6c
diff --git a/src/OpenDis.c b/src/OpenDis.c
936a6c
index 82723578..85901168 100644
936a6c
--- a/src/OpenDis.c
936a6c
+++ b/src/OpenDis.c
936a6c
@@ -201,6 +201,7 @@ XOpenDisplay (
936a6c
 	X_DPY_SET_LAST_REQUEST_READ(dpy, 0);
936a6c
 	dpy->default_screen = iscreen;  /* Value returned by ConnectDisplay */
936a6c
 	dpy->last_req = (char *)&_dummy_request;
936a6c
+	dpy->error_threads = NULL;
936a6c
 
936a6c
 	/* Initialize the display lock */
936a6c
 	if (InitDisplayLock(dpy) != 0) {
936a6c
diff --git a/src/XlibInt.c b/src/XlibInt.c
936a6c
index 4e45e62b..8771b791 100644
936a6c
--- a/src/XlibInt.c
936a6c
+++ b/src/XlibInt.c
936a6c
@@ -1482,6 +1482,11 @@ int _XError (
936a6c
     if (_XErrorFunction != NULL) {
936a6c
 	int rtn_val;
936a6c
 #ifdef XTHREADS
936a6c
+	struct _XErrorThreadInfo thread_info = {
936a6c
+		.error_thread = xthread_self(),
936a6c
+		.next = dpy->error_threads
936a6c
+	}, **prev;
936a6c
+	dpy->error_threads = &thread_info;
936a6c
 	if (dpy->lock)
936a6c
 	    (*dpy->lock->user_lock_display)(dpy);
936a6c
 	UnlockDisplay(dpy);
936a6c
@@ -1491,6 +1496,11 @@ int _XError (
936a6c
 	LockDisplay(dpy);
936a6c
 	if (dpy->lock)
936a6c
 	    (*dpy->lock->user_unlock_display)(dpy);
936a6c
+
936a6c
+	/* unlink thread_info from the list */
936a6c
+	for (prev = &dpy->error_threads; *prev != &thread_info; prev = &(*prev)->next)
936a6c
+		;
936a6c
+	*prev = thread_info.next;
936a6c
 #endif
936a6c
 	return rtn_val;
936a6c
     } else {
936a6c
diff --git a/src/locking.c b/src/locking.c
936a6c
index 9f4fe067..bcadc857 100644
936a6c
--- a/src/locking.c
936a6c
+++ b/src/locking.c
936a6c
@@ -453,6 +453,9 @@ static void _XLockDisplay(
936a6c
     XTHREADS_FILE_LINE_ARGS
936a6c
     )
936a6c
 {
936a6c
+#ifdef XTHREADS
936a6c
+    struct _XErrorThreadInfo *ti;
936a6c
+#endif
936a6c
 #ifdef XTHREADS_WARN
936a6c
     _XLockDisplayWarn(dpy, file, line);
936a6c
 #else
936a6c
@@ -460,6 +463,15 @@ static void _XLockDisplay(
936a6c
 #endif
936a6c
     if (dpy->lock->locking_level > 0)
936a6c
 	_XDisplayLockWait(dpy);
936a6c
+#ifdef XTHREADS
936a6c
+    /*
936a6c
+     * Skip the two function calls below which may generate requests
936a6c
+     * when LockDisplay is called from within _XError.
936a6c
+     */
936a6c
+    for (ti = dpy->error_threads; ti; ti = ti->next)
936a6c
+	    if (ti->error_thread == xthread_self())
936a6c
+		    return;
936a6c
+#endif
936a6c
     _XIDHandler(dpy);
936a6c
     _XSeqSyncFunction(dpy);
936a6c
 }
936a6c
diff --git a/src/locking.h b/src/locking.h
936a6c
index 5251a60c..59fc866e 100644
936a6c
--- a/src/locking.h
936a6c
+++ b/src/locking.h
936a6c
@@ -149,6 +149,18 @@ typedef struct _LockInfoRec {
936a6c
 	xmutex_t	lock;
936a6c
 } LockInfoRec;
936a6c
 
936a6c
+/* A list of threads currently invoking error handlers on this display
936a6c
+ * LockDisplay operates differently for these threads, avoiding
936a6c
+ * generating any requests or reading any events as that can cause
936a6c
+ * recursion into the error handling code, which will deadlock the
936a6c
+ * thread.
936a6c
+ */
936a6c
+struct _XErrorThreadInfo
936a6c
+{
936a6c
+    struct _XErrorThreadInfo *next;
936a6c
+    xthread_t error_thread;
936a6c
+};
936a6c
+
936a6c
 /* XOpenDis.c */
936a6c
 extern int (*_XInitDisplayLock_fn)(Display *dpy);
936a6c
 extern void (*_XFreeDisplayLock_fn)(Display *dpy);
936a6c
-- 
936a6c
2.43.0
936a6c