Commit 89cff7b7 for guacamole.apache.org
commit 89cff7b717648948f1d8a1f5087cf0844d75b8da
Author: Stephen Schiffli <sschiffli@keepersecurity.com>
Date: Thu Feb 26 05:40:17 2026 -0800
GUACAMOLE-2234: Fix race condition in non-opaque layer clearing across display worker threads.
diff --git a/src/libguac/display-layer-list.c b/src/libguac/display-layer-list.c
index f80aa3a4..b876d255 100644
--- a/src/libguac/display-layer-list.c
+++ b/src/libguac/display-layer-list.c
@@ -259,6 +259,8 @@ guac_display_layer* guac_display_add_layer(guac_display* display, guac_layer* la
display_layer->layer = layer;
display_layer->opaque = opaque;
+ pthread_mutex_init(&display_layer->path_lock, NULL);
+
/* Init tracking of pending and last frames (NOTE: We need not acquire the
* display-wide last_frame.lock here as this new layer will not actually be
* part of the last frame layer list until the pending frame is flushed) */
@@ -364,6 +366,8 @@ void guac_display_remove_layer(guac_display_layer* display_layer) {
guac_mem_free(display_layer->last_frame.buffer);
guac_mem_free(display_layer->pending_frame_cells);
+ pthread_mutex_destroy(&display_layer->path_lock);
+
guac_mem_free(display_layer);
}
diff --git a/src/libguac/display-priv.h b/src/libguac/display-priv.h
index 255fb897..d6082e1e 100644
--- a/src/libguac/display-priv.h
+++ b/src/libguac/display-priv.h
@@ -516,6 +516,12 @@ struct guac_display_layer {
*/
int opaque;
+ /**
+ * Lock used to ensure sequences of Guacamole protocol path instructions
+ * for this layer are not interleaved when sent from concurrent threads.
+ */
+ pthread_mutex_t path_lock;
+
/* ---------------- LAYER PREVIOUS FRAME STATE ---------------- */
/**
diff --git a/src/libguac/display-worker.c b/src/libguac/display-worker.c
index 813306fd..eb251263 100644
--- a/src/libguac/display-worker.c
+++ b/src/libguac/display-worker.c
@@ -101,15 +101,21 @@ static void guac_display_layer_clear_non_opaque(guac_display_layer* display_laye
guac_socket* socket = client->socket;
/* Clear destination region only if necessary due to the relevant layer
- * being non-opaque */
+ * being non-opaque. The rect+cfill pair must be sent atomically to
+ * prevent interleaving with concurrent workers operating on the same
+ * layer. */
if (!display_layer->opaque) {
+ pthread_mutex_lock(&display_layer->path_lock);
+
guac_protocol_send_rect(socket, layer, dirty->left, dirty->top,
guac_rect_width(dirty), guac_rect_height(dirty));
guac_protocol_send_cfill(socket, GUAC_COMP_ROUT, layer,
0x00, 0x00, 0x00, 0xFF);
+ pthread_mutex_unlock(&display_layer->path_lock);
+
}
}