Commit 4370fe9f for guacamole.apache.org

commit 4370fe9fd29cd5492068e34087d53f5764d23163
Author: Bradley Bennett <bbennett@keepersecurity.com>
Date:   Fri Mar 6 15:58:47 2026 -0500

    GUACAMOLE-2238: Multiple guacamole-server code paths do not correctly retry on EINTR.

diff --git a/src/common/io.c b/src/common/io.c
index 2e2a5ad7..a81a131c 100644
--- a/src/common/io.c
+++ b/src/common/io.c
@@ -20,6 +20,9 @@
 #include "config.h"
 #include "common/io.h"

+#include <guacamole/error.h>
+
+#include <errno.h>
 #include <unistd.h>

 int guac_common_write(int fd, void* buffer, int length) {
@@ -29,7 +32,8 @@ int guac_common_write(int fd, void* buffer, int length) {
     while (length > 0) {

         /* Attempt write */
-        int bytes_written = write(fd, bytes, length);
+        int bytes_written;
+        GUAC_RETRY_EINTR(bytes_written, write(fd, bytes, length));
         if (bytes_written < 0)
             return bytes_written;

@@ -51,7 +55,8 @@ int guac_common_read(int fd, void* buffer, int length) {
     while (length > 0) {

         /* Attempt read */
-        int bytes_read = read(fd, bytes, length);
+        int bytes_read;
+        GUAC_RETRY_EINTR(bytes_read, read(fd, bytes, length));
         if (bytes_read < 0)
             return bytes_read;

diff --git a/src/guacd/conf-file.c b/src/guacd/conf-file.c
index ed74dcbc..874a39ba 100644
--- a/src/guacd/conf-file.c
+++ b/src/guacd/conf-file.c
@@ -24,6 +24,7 @@
 #include "conf-parse.h"

 #include <guacamole/client.h>
+#include <guacamole/error.h>
 #include <guacamole/mem.h>
 #include <guacamole/string.h>

@@ -134,7 +135,11 @@ int guacd_conf_parse_file(guacd_config* conf, int fd) {
     int parsed = 0;

     /* Attempt to fill remaining space in buffer */
-    while ((chars_read = read(fd, buffer + length, sizeof(buffer) -  length)) > 0) {
+    while (1) {
+        GUAC_RETRY_EINTR(chars_read, read(fd, buffer + length, sizeof(buffer) - length));
+
+        if (chars_read <= 0)
+            break;

         length += chars_read;

diff --git a/src/guacd/connection.c b/src/guacd/connection.c
index 2c8c00d6..c07d6643 100644
--- a/src/guacd/connection.c
+++ b/src/guacd/connection.c
@@ -71,7 +71,8 @@ static int __write_all(int fd, char* buffer, int length) {
     int remaining_length = length;
     while (remaining_length > 0) {

-        int written = write(fd, buffer, remaining_length);
+        int written;
+        GUAC_RETRY_EINTR(written, write(fd, buffer, remaining_length));
         if (written < 0)
             return -1;

@@ -140,7 +141,12 @@ void* guacd_connection_io_thread(void* data) {
     pthread_create(&write_thread, NULL, guacd_connection_write_thread, params);

     /* Transfer data from file descriptor to socket */
-    while ((length = read(params->fd, buffer, sizeof(buffer))) > 0) {
+    while (1) {
+        GUAC_RETRY_EINTR(length, read(params->fd, buffer, sizeof(buffer)));
+
+        if (length <= 0)
+            break;
+
         if (guac_socket_write(params->socket, buffer, length))
             break;
         guac_socket_flush(params->socket);
@@ -329,7 +335,8 @@ static int guacd_route_connection(guacd_proc_map* map, guac_socket* socket) {
             guacd_proc_map_add(map, proc);

             /* Wait for child to finish */
-            waitpid(proc->pid, NULL, 0);
+            pid_t wait_result;
+            GUAC_RETRY_EINTR(wait_result, waitpid(proc->pid, NULL, 0));

             /* Remove client */
             if (guacd_proc_map_remove(map, proc->client->connection_id) == NULL)
diff --git a/src/guacd/daemon.c b/src/guacd/daemon.c
index 251d348f..b374eb9b 100644
--- a/src/guacd/daemon.c
+++ b/src/guacd/daemon.c
@@ -506,7 +506,8 @@ int main(int argc, char* argv[]) {
                 "Child processes may pile up in the process table.");
     }

-    /* Clean up and exit if SIGINT or SIGTERM signals are caught */
+    /* Clean up and exit if SIGINT or SIGTERM signals are caught; don't set
+       SA_RESTART as we rely on accept() to return EINTR.*/
     struct sigaction signal_stop_action = { .sa_handler = signal_stop_handler };
     sigaction(SIGINT, &signal_stop_action, NULL);
     sigaction(SIGTERM, &signal_stop_action, NULL);
diff --git a/src/guacd/move-fd.c b/src/guacd/move-fd.c
index 74c98e5f..3d155797 100644
--- a/src/guacd/move-fd.c
+++ b/src/guacd/move-fd.c
@@ -31,6 +31,8 @@
 #include <sys/wait.h>
 #include <unistd.h>

+#include <guacamole/error.h>
+
 int guacd_send_fd(int sock, int fd) {

     struct msghdr message = {0};
@@ -58,7 +60,10 @@ int guacd_send_fd(int sock, int fd) {
     memcpy(CMSG_DATA(control), &fd, sizeof(fd));

     /* Send file descriptor */
-    return (sendmsg(sock, &message, 0) == sizeof(message_data));
+    ssize_t result;
+    GUAC_RETRY_EINTR(result, sendmsg(sock, &message, 0));
+
+    return (result == sizeof(message_data));

 }

@@ -82,7 +87,10 @@ int guacd_recv_fd(int sock) {
     message.msg_controllen = sizeof(buffer);

     /* Receive file descriptor */
-    if (recvmsg(sock, &message, 0) == sizeof(message_data)) {
+    ssize_t result;
+    GUAC_RETRY_EINTR(result, recvmsg(sock, &message, 0));
+
+    if (result == sizeof(message_data)) {

         /* Validate payload */
         if (message_data[0] != 'G') {
diff --git a/src/guacd/proc.c b/src/guacd/proc.c
index 2c387128..4040a3be 100644
--- a/src/guacd/proc.c
+++ b/src/guacd/proc.c
@@ -357,7 +357,11 @@ static void guacd_exec_proc(guacd_proc* proc, const char* protocol) {
     guacd_proc_self = proc;

     /* Clean up and exit if SIGINT or SIGTERM signals are caught */
-    struct sigaction signal_stop_action = { .sa_handler = signal_stop_handler };
+    struct sigaction signal_stop_action = {
+        .sa_handler = signal_stop_handler,
+        /* Restart system calls interrupted by signal delivery */
+        .sa_flags = SA_RESTART
+    };
     sigaction(SIGINT, &signal_stop_action, NULL);
     sigaction(SIGTERM, &signal_stop_action, NULL);

@@ -393,7 +397,12 @@ cleanup_client:

     /* Verify whether children were all properly reaped */
     pid_t child_pid;
-    while ((child_pid = waitpid(0, NULL, WNOHANG)) > 0) {
+    while (1) {
+        GUAC_RETRY_EINTR(child_pid, waitpid(0, NULL, WNOHANG));
+
+        if (child_pid <= 0)
+            break;
+
         guacd_log(GUAC_LOG_DEBUG, "Automatically reaped unreaped "
                 "(zombie) child process with PID %i.", child_pid);
     }
@@ -503,7 +512,12 @@ static void guacd_proc_kill(guacd_proc* proc) {

     /* Wait for all processes within process group to terminate */
     pid_t child_pid;
-    while ((child_pid = waitpid(-proc->pid, NULL, 0)) > 0 || errno == EINTR) {
+    while (1) {
+        GUAC_RETRY_EINTR(child_pid, waitpid(-proc->pid, NULL, 0));
+
+        if (child_pid <= 0)
+            break;
+
         guacd_log(GUAC_LOG_DEBUG, "Child process %i of connection \"%s\" has terminated",
             child_pid, proc->client->connection_id);
     }
diff --git a/src/libguac/guacamole/error.h b/src/libguac/guacamole/error.h
index 5fdf558a..0384569f 100644
--- a/src/libguac/guacamole/error.h
+++ b/src/libguac/guacamole/error.h
@@ -21,43 +21,61 @@
 #define _GUAC_ERROR_H

 /**
- * Provides functions and structures required for handling return values and
- * errors.
+ * Provides functions, structures, and macros required for handling return
+ * values and errors.
  *
  * @file error.h
  */

 #include "error-types.h"

-/**
- * Returns a human-readable explanation of the status code given.
- */
-const char* guac_status_string(guac_status status);
+#include <errno.h>

 /**
- * Returns the status code associated with the error which occurred during the
- * last function call. This value will only be set by functions documented to
- * use it (most libguac functions), and is undefined if no error occurred.
+ * Executes the given expression and retries while the given retry condition
+ * evaluates to non-zero.
  *
- * The storage of this value is thread-local. Assignment of a status code to
- * guac_error in one thread will not affect its value in another thread.
+ * This can be used to retry operations that need custom retry logic.
+ *
+ * @param retval
+ *     The variable that should receive the result of each evaluation of the
+ *     expression.
+ *
+ * @param expression
+ *     The expression to execute.
+ *
+ * @param retry_condition
+ *     The condition that determines whether the expression should be retried.
  */
-#define guac_error (*__guac_error())
-
-guac_status* __guac_error();
+#define GUAC_RETRY_UNTIL(retval, expression, retry_condition) \
+    do {                                                      \
+        do {                                                  \
+            (retval) = (expression);                          \
+        } while (retry_condition);                            \
+    } while (0)

 /**
- * Returns a message describing the error which occurred during the last
- * function call. If an error occurred, but no message is associated with it,
- * NULL is returned. This value is undefined if no error occurred.
+ * Executes the given expression and retries if it returns a negative value
+ * and errno is set to EINTR.
  *
- * The storage of this value is thread-local. Assignment of a message to
- * guac_error_message in one thread will not affect its value in another
- * thread.
+ * This is necessary for system calls and similar functions that may be
+ * interrupted by signal delivery before completion. In such cases, the call
+ * can fail with EINTR even though no real error has occurred and the
+ * operation should simply be retried.
+ *
+ * This can be used for blocking system calls and similar operations that
+ * should continue waiting or processing after signal delivery.
+ *
+ * @param retval
+ *     The variable that should receive the result of each evaluation of the
+ *     expression.
+ *
+ * @param expression
+ *     The expression to execute.
  */
-#define guac_error_message (*__guac_error_message())
-
-const char** __guac_error_message();
+#define GUAC_RETRY_EINTR(retval, expression) \
+    GUAC_RETRY_UNTIL((retval), (expression), \
+            (retval) < 0 && errno == EINTR)

 /**
  * Returns a human-readable explanation of the status code given.
diff --git a/src/libguac/socket-fd.c b/src/libguac/socket-fd.c
index 5c1b463f..b8bb04ef 100644
--- a/src/libguac/socket-fd.c
+++ b/src/libguac/socket-fd.c
@@ -18,12 +18,13 @@
  */

 #include "config.h"
+#include "wait-fd.h"

 #include "guacamole/mem.h"
 #include "guacamole/error.h"
 #include "guacamole/socket.h"
-#include "wait-fd.h"

+#include <errno.h>
 #include <pthread.h>
 #include <stddef.h>
 #include <stdio.h>
@@ -106,7 +107,7 @@ ssize_t guac_socket_fd_write(guac_socket* socket,
         retval = send(data->fd, buffer, count, 0);
 #else
         /* Use write() for all other platforms */
-        retval = write(data->fd, buffer, count);
+        GUAC_RETRY_EINTR(retval, write(data->fd, buffer, count));
 #endif

         /* Record errors in guac_error */
@@ -154,7 +155,7 @@ static ssize_t guac_socket_fd_read_handler(guac_socket* socket,
     retval = recv(data->fd, buf, count, 0);
 #else
     /* Use read() for all other platforms */
-    retval = read(data->fd, buf, count);
+    GUAC_RETRY_EINTR(retval, read(data->fd, buf, count));
 #endif

     /* Record errors in guac_error */
diff --git a/src/libguac/socket-wsa.c b/src/libguac/socket-wsa.c
index 18e79475..ec17d827 100644
--- a/src/libguac/socket-wsa.c
+++ b/src/libguac/socket-wsa.c
@@ -93,7 +93,9 @@ ssize_t guac_socket_wsa_write(guac_socket* socket,
     /* Write until completely written */
     while (count > 0) {

-        int retval = send(data->sock, buffer, count, 0);
+        int retval;
+        GUAC_RETRY_UNTIL(retval, send(data->sock, buffer, count, 0),
+                retval < 0 && WSAGetLastError() == WSAEINTR);

         /* Record errors in guac_error */
         if (retval < 0) {
@@ -134,7 +136,9 @@ static ssize_t guac_socket_wsa_read_handler(guac_socket* socket,
     guac_socket_wsa_data* data = (guac_socket_wsa_data*) socket->data;

     /* Read from socket */
-    int retval = recv(data->sock, buf, count, 0);
+    int retval;
+    GUAC_RETRY_UNTIL(retval, recv(data->sock, buf, count, 0),
+            retval < 0 && WSAGetLastError() == WSAEINTR);

     /* Record errors in guac_error */
     if (retval < 0) {
@@ -327,19 +331,26 @@ static int guac_socket_wsa_select_handler(guac_socket* socket,
     struct timeval timeout;
     int retval;

-    /* Initialize fd_set with single underlying socket handle */
-    FD_ZERO(&sockets);
-    FD_SET(data->sock, &sockets);
-
     /* No timeout if usec_timeout is negative */
-    if (usec_timeout < 0)
-        retval = select(0, &sockets, NULL, NULL, NULL);
+    if (usec_timeout < 0) {
+        do {
+            /* On retry, fd_set contents are undefined. */
+            FD_ZERO(&sockets);
+            FD_SET(data->sock, &sockets);
+            retval = select(0, &sockets, NULL, NULL, NULL);
+        } while (retval < 0 && WSAGetLastError() == WSAEINTR);
+    }

     /* Handle timeout if specified */
     else {
-        timeout.tv_sec  = usec_timeout / 1000000;
-        timeout.tv_usec = usec_timeout % 1000000;
-        retval = select(0, &sockets, NULL, NULL, &timeout);
+        do {
+            timeout.tv_sec  = usec_timeout / 1000000;
+            timeout.tv_usec = usec_timeout % 1000000;
+            /* On retry, fd_set contents are undefined. */
+            FD_ZERO(&sockets);
+            FD_SET(data->sock, &sockets);
+            retval = select(0, &sockets, NULL, NULL, &timeout);
+        } while (retval < 0 && WSAGetLastError() == WSAEINTR);
     }

     /* Properly set guac_error */
diff --git a/src/libguac/socket.c b/src/libguac/socket.c
index b7a4b77e..2c2cd874 100644
--- a/src/libguac/socket.c
+++ b/src/libguac/socket.c
@@ -18,13 +18,13 @@
  */

 #include "config.h"
-
 #include "guacamole/mem.h"
 #include "guacamole/error.h"
 #include "guacamole/protocol.h"
 #include "guacamole/socket.h"
 #include "guacamole/timestamp.h"

+#include <errno.h>
 #include <inttypes.h>
 #include <pthread.h>
 #include <stddef.h>
@@ -47,11 +47,6 @@ static void* __guac_socket_keep_alive_thread(void* data) {

     int old_cancelstate;

-    /* Calculate sleep interval */
-    struct timespec interval;
-    interval.tv_sec  =  GUAC_SOCKET_KEEP_ALIVE_INTERVAL / 1000;
-    interval.tv_nsec = (GUAC_SOCKET_KEEP_ALIVE_INTERVAL % 1000) * 1000000L;
-
     /* Socket keep-alive loop */
     guac_socket* socket = (guac_socket*) data;
     while (socket->state == GUAC_SOCKET_OPEN) {
@@ -68,10 +63,17 @@ static void* __guac_socket_keep_alive_thread(void* data) {

         }

+        /* Calculate sleep interval every loop as nanosleep updates it
+           with the remaining time interval */
+        struct timespec interval;
+        interval.tv_sec  =  GUAC_SOCKET_KEEP_ALIVE_INTERVAL / 1000;
+        interval.tv_nsec = (GUAC_SOCKET_KEEP_ALIVE_INTERVAL % 1000) * 1000000L;
+
         /* Sleep until next keep-alive check, but allow thread cancellation
          * during that sleep */
+        int sleep_result;
         pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_cancelstate);
-        nanosleep(&interval, NULL);
+        GUAC_RETRY_EINTR(sleep_result, nanosleep(&interval, &interval));
         pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancelstate);

     }
diff --git a/src/libguac/tcp.c b/src/libguac/tcp.c
index 79122658..ba797658 100644
--- a/src/libguac/tcp.c
+++ b/src/libguac/tcp.c
@@ -97,20 +97,33 @@ int guac_tcp_connect(const char* hostname, const char* port, const int timeout)
             continue;
         }

-        /* Structure that stores our timeout setting. */
         struct timeval tv;
         tv.tv_sec = timeout;
         tv.tv_usec = 0;

         /* Connect and wait for timeout */
         if ((retval = connect(fd, current_address->ai_addr, current_address->ai_addrlen)) < 0) {
-            if (errno == EINPROGRESS) {
-                /* Set up timeout. */
+            /* If connect() is in progress (EINPROGRESS) or interrupted by a signal
+             * (EINTR), wait for the socket to become writable and check SO_ERROR. */
+            if (errno == EINPROGRESS || errno == EINTR) {
+
+                /* Prevent overflowing fd_set. */
+                if (fd >= FD_SETSIZE) {
+                    guac_error = GUAC_STATUS_INVALID_ARGUMENT;
+                    guac_error_message = "File descriptor exceeds FD_SETSIZE.";
+                    close(fd);
+                    fd = -1;
+                    continue;
+                }
+
                 fd_set fdset;
                 FD_ZERO(&fdset);
                 FD_SET(fd, &fdset);
-
-                retval = select(fd + 1, NULL, &fdset, NULL, &tv);
+
+                /* Linux (kernel/glibc verified): select() does not modify
+                   fd_set on -1, and updates struct timeval to reflect elapsed
+                   time on EINTR, so neither needs reinitialization on retry. */
+                GUAC_RETRY_EINTR(retval, select(fd + 1, NULL, &fdset, NULL, &tv));

                 if (retval > 0) {
                     int so_error = 0;
@@ -126,14 +139,14 @@ int guac_tcp_connect(const char* hostname, const char* port, const int timeout)
                         fd = -1;
                         continue;
                     }
-
+
                     /* This indicates that the connection is successful, so we
                        break so that the socket can be immediately returned. */
                     if (so_error == 0) {
                         break;
                     }

-                    /* There's a socket error, so we retrieve it and move to
+                    /* There's a socket error, so we retrieve it and move to
                        the next address. */
                     else {
                         guac_error = GUAC_STATUS_REFUSED;
@@ -145,7 +158,7 @@ int guac_tcp_connect(const char* hostname, const char* port, const int timeout)
                 }

             }
-
+
             else {
                 guac_error = GUAC_STATUS_REFUSED;
                 guac_error_message = "Unable to connect via socket.";
diff --git a/src/libguac/timestamp.c b/src/libguac/timestamp.c
index 9020a6e1..ee207f28 100644
--- a/src/libguac/timestamp.c
+++ b/src/libguac/timestamp.c
@@ -19,8 +19,10 @@

 #include "config.h"

+#include "guacamole/error.h"
 #include "guacamole/timestamp.h"

+#include <errno.h>
 #include <sys/time.h>

 #if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_NANOSLEEP)
@@ -66,7 +68,8 @@ void guac_timestamp_msleep(int duration) {
     };

     /* Sleep for specified interval */
-    nanosleep(&sleep_period, NULL);
+    int sleep_result;
+    GUAC_RETRY_EINTR(sleep_result, nanosleep(&sleep_period, &sleep_period));

 }

diff --git a/src/libguac/wait-fd.c b/src/libguac/wait-fd.c
index d6079be3..05359951 100644
--- a/src/libguac/wait-fd.c
+++ b/src/libguac/wait-fd.c
@@ -19,6 +19,9 @@

 #include "config.h"

+#include "guacamole/error.h"
+#include <errno.h>
+
 #ifdef ENABLE_WINSOCK
 #    include <winsock2.h>
 #else
@@ -39,17 +42,29 @@ int guac_wait_for_fd(int fd, int usec_timeout) {
         .revents = 0
     }};

+    int retval;
+
     /* No timeout if usec_timeout is negative */
-    if (usec_timeout < 0)
-        return poll(fds, 1, -1);
+    if (usec_timeout < 0) {
+        GUAC_RETRY_EINTR(retval, poll(fds, 1, -1));
+        return retval;
+    }

     /* Handle timeout if specified, rounding up to poll()'s granularity */
-    return poll(fds, 1, (usec_timeout + 999) / 1000);
+    GUAC_RETRY_EINTR(retval, poll(fds, 1, (usec_timeout + 999) / 1000));
+    return retval;

 }
 #else
 int guac_wait_for_fd(int fd, int usec_timeout) {

+    /* Prevent overflowing fd_set. */
+    if (fd >= FD_SETSIZE) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    int retval;
     fd_set fds;

     /* Initialize fd_set with single underlying file descriptor */
@@ -57,8 +72,10 @@ int guac_wait_for_fd(int fd, int usec_timeout) {
     FD_SET(fd, &fds);

     /* No timeout if usec_timeout is negative */
-    if (usec_timeout < 0)
-        return select(fd + 1, &fds, NULL, NULL, NULL);
+    if (usec_timeout < 0) {
+        GUAC_RETRY_EINTR(retval, select(fd + 1, &fds, NULL, NULL, NULL));
+        return retval;
+    }

     /* Handle timeout if specified */
     struct timeval timeout = {
@@ -66,7 +83,11 @@ int guac_wait_for_fd(int fd, int usec_timeout) {
         .tv_usec = usec_timeout % 1000000
     };

-    return select(fd + 1, &fds, NULL, NULL, &timeout);
+    /* Linux (kernel/glibc verified): select() does not modify
+       fd_set on -1, and updates struct timeval to reflect elapsed
+       time on EINTR, so neither needs reinitialization on retry. */
+    GUAC_RETRY_EINTR(retval, select(fd + 1, &fds, NULL, NULL, &timeout));
+    return retval;

 }
 #endif
diff --git a/src/libguac/wol.c b/src/libguac/wol.c
index 32ddedfd..0b301a3c 100644
--- a/src/libguac/wol.c
+++ b/src/libguac/wol.c
@@ -18,7 +18,6 @@
  */

 #include "config.h"
-
 #include "guacamole/error.h"
 #include "guacamole/tcp.h"
 #include "guacamole/timestamp.h"
@@ -300,8 +299,9 @@ static ssize_t __guac_wol_send_packet(const char* broadcast_addr,
     }

     /* Send the packet and return number of bytes sent. */
-    int bytes = sendto(wol_socket, packet, GUAC_WOL_PACKET_SIZE, 0,
-            (struct sockaddr*) &wol_dest, wol_dest_size);
+    ssize_t bytes;
+    GUAC_RETRY_EINTR(bytes, sendto(wol_socket, packet, GUAC_WOL_PACKET_SIZE, 0,
+            (struct sockaddr*) &wol_dest, wol_dest_size));
     close(wol_socket);
     return bytes;

diff --git a/src/protocols/rdp/fs.c b/src/protocols/rdp/fs.c
index 2cafd28a..8e9125e4 100644
--- a/src/protocols/rdp/fs.c
+++ b/src/protocols/rdp/fs.c
@@ -22,6 +22,7 @@
 #include "upload.h"

 #include <guacamole/client.h>
+#include <guacamole/error.h>
 #include <guacamole/mem.h>
 #include <guacamole/object.h>
 #include <guacamole/pool.h>
@@ -424,7 +425,7 @@ int guac_rdp_fs_read(guac_rdp_fs* fs, int file_id, uint64_t offset,

     /* Attempt read */
     lseek(file->fd, offset, SEEK_SET);
-    bytes_read = read(file->fd, buffer, length);
+    GUAC_RETRY_EINTR(bytes_read, read(file->fd, buffer, length));

     /* Translate errno on error */
     if (bytes_read < 0)
@@ -448,7 +449,7 @@ int guac_rdp_fs_write(guac_rdp_fs* fs, int file_id, uint64_t offset,

     /* Attempt write */
     lseek(file->fd, offset, SEEK_SET);
-    bytes_written = write(file->fd, buffer, length);
+    GUAC_RETRY_EINTR(bytes_written, write(file->fd, buffer, length));

     /* Translate errno on error */
     if (bytes_written < 0)
diff --git a/src/protocols/rdp/print-job.c b/src/protocols/rdp/print-job.c
index 2b6ca1aa..eda0275a 100644
--- a/src/protocols/rdp/print-job.c
+++ b/src/protocols/rdp/print-job.c
@@ -21,6 +21,7 @@
 #include "rdp.h"

 #include <guacamole/client.h>
+#include <guacamole/error.h>
 #include <guacamole/mem.h>
 #include <guacamole/protocol.h>
 #include <guacamole/socket.h>
@@ -399,7 +400,11 @@ static void* guac_rdp_print_job_output_thread(void* data) {
             "process...");

     /* Read continuously while data remains */
-    while ((length = read(job->output_fd, buffer, sizeof(buffer))) > 0) {
+    while (1) {
+        GUAC_RETRY_EINTR(length, read(job->output_fd, buffer, sizeof(buffer)));
+
+        if (length <= 0)
+            break;

         /* Wait for client to be ready for blob */
         if (guac_rdp_print_job_wait_for_ack(job)) {
@@ -627,7 +632,8 @@ int guac_rdp_print_job_write(guac_rdp_print_job* job,
      * on other threads sending outstanding messages (resulting in deadlock if
      * those messages are blocked) */
     int unlock_status = pthread_mutex_unlock(&(rdp_client->message_lock));
-    int write_status = write(job->input_fd, buffer, length);
+    int write_status;
+    GUAC_RETRY_EINTR(write_status, write(job->input_fd, buffer, length));

     /* Restore RDP message lock state */
     if (!unlock_status)
diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c
index 4f99fb97..c988568b 100644
--- a/src/protocols/ssh/ssh.c
+++ b/src/protocols/ssh/ssh.c
@@ -35,6 +35,7 @@
 #include <libssh2.h>
 #include <libssh2_sftp.h>
 #include <guacamole/client.h>
+#include <guacamole/error.h>
 #include <guacamole/mem.h>
 #include <guacamole/recording.h>
 #include <guacamole/socket.h>
@@ -553,8 +554,11 @@ void* ssh_client_thread(void* data) {
                 .revents = 0,
             }};

+            int wait_result;
             /* Wait up to computed timeout */
-            if (poll(fds, 1, timeout) < 0)
+            GUAC_RETRY_EINTR(wait_result, poll(fds, 1, timeout));
+
+            if (wait_result < 0)
                 break;

         }
diff --git a/src/protocols/telnet/telnet.c b/src/protocols/telnet/telnet.c
index e14c2314..f89a0a34 100644
--- a/src/protocols/telnet/telnet.c
+++ b/src/protocols/telnet/telnet.c
@@ -24,6 +24,7 @@
 #include "terminal/terminal.h"

 #include <guacamole/client.h>
+#include <guacamole/error.h>
 #include <guacamole/mem.h>
 #include <guacamole/protocol.h>
 #include <guacamole/recording.h>
@@ -75,7 +76,8 @@ static int __guac_telnet_write_all(int fd, const char* buffer, int size) {
     while (remaining > 0) {

         /* Attempt to write data */
-        int ret_val = write(fd, buffer, remaining);
+        int ret_val;
+        GUAC_RETRY_EINTR(ret_val, write(fd, buffer, remaining));
         if (ret_val <= 0)
             return -1;

@@ -478,8 +480,12 @@ static int __guac_telnet_wait(int socket_fd) {
         .revents = 0,
     }};

+    int wait_result;
+
     /* Wait for one second */
-    return poll(fds, 1, 1000);
+    GUAC_RETRY_EINTR(wait_result, poll(fds, 1, 1000));
+
+    return wait_result;

 }

@@ -607,7 +613,8 @@ void* guac_telnet_client_thread(void* data) {
         if (wait_result == 0)
             continue;

-        int bytes_read = read(telnet_client->socket_fd, buffer, sizeof(buffer));
+        int bytes_read;
+        GUAC_RETRY_EINTR(bytes_read, read(telnet_client->socket_fd, buffer, sizeof(buffer)));
         if (bytes_read <= 0)
             break;

diff --git a/src/terminal/common.c b/src/terminal/common.c
index 3b83d4c8..863dda7e 100644
--- a/src/terminal/common.c
+++ b/src/terminal/common.c
@@ -21,7 +21,9 @@
 #include "terminal/types.h"

 #include <guacamole/assert.h>
+#include <guacamole/error.h>

+#include <errno.h>
 #include <stdbool.h>
 #include <unistd.h>

@@ -97,7 +99,8 @@ int guac_terminal_write_all(int fd, const char* buffer, int size) {
     while (remaining > 0) {

         /* Attempt to write data */
-        int ret_val = write(fd, buffer, remaining);
+        int ret_val;
+        GUAC_RETRY_EINTR(ret_val, write(fd, buffer, remaining));
         if (ret_val <= 0)
             return -1;

diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c
index 307e854c..4b2a18df 100644
--- a/src/terminal/terminal.c
+++ b/src/terminal/terminal.c
@@ -641,7 +641,10 @@ int guac_terminal_render_frame(guac_terminal* terminal) {

 int guac_terminal_read_stdin(guac_terminal* terminal, char* c, int size) {
     int stdin_fd = terminal->stdin_pipe_fd[0];
-    return read(stdin_fd, c, size);
+    int retval;
+
+    GUAC_RETRY_EINTR(retval, read(stdin_fd, c, size));
+    return retval;
 }

 void guac_terminal_notify(guac_terminal* terminal) {