Browse Source

qrexec: use sockets instead of pipes to communicate with child process

The main advantage is possible use of single socket for both stdin and
stdout. This is strictly required for using USBIP over qrexec.

For compatibility qrexec still creates three socket pairs (instead of
pipes) for stdin/out/err respectively. When qrexec-agent receives
SIGUSR1, it will close stdout socket and use stdin socket for both
directions.

Some additional work is needed here to actually allow child process to
send that signal - qrexec is running as root, but child as "user" in
most cases.
Marek Marczykowski-Górecki 9 years ago
parent
commit
c1cb78e0e8
1 changed files with 41 additions and 6 deletions
  1. 41 6
      qrexec/qrexec-agent-data.c

+ 41 - 6
qrexec/qrexec-agent-data.c

@@ -29,6 +29,7 @@
 #include <sys/stat.h>
 #include <sys/wait.h>
 #include <sys/select.h>
+#include <sys/socket.h>
 #include <fcntl.h>
 #include <libvchan.h>
 #include "qrexec.h"
@@ -38,6 +39,7 @@
 #define VCHAN_BUFFER_SIZE 65536
 
 static volatile int child_exited;
+static volatile int stdio_socket_requested;
 int stdout_msg_type = MSG_DATA_STDOUT;
 pid_t child_process_pid;
 
@@ -47,6 +49,12 @@ static void sigchld_handler(int __attribute__((__unused__))x)
 	signal(SIGCHLD, sigchld_handler);
 }
 
+static void sigusr1_handler(int __attribute__((__unused__))x)
+{
+    stdio_socket_requested = 1;
+    signal(SIGUSR1, SIG_IGN);
+}
+
 
 void no_colon_in_cmd()
 {
@@ -146,7 +154,10 @@ int handle_input(libvchan_t *vchan, int fd, int msg_type)
             return -1;
 
         if (len == 0) {
-            close(fd);
+            if (shutdown(fd, SHUT_RD) < 0) {
+                if (errno == ENOTSOCK)
+                    close(fd);
+            }
             return 0;
         }
     }
@@ -188,14 +199,20 @@ int handle_remote_data(libvchan_t *data_vchan, int stdin_fd)
                     /* discard the data */
                     continue;
                 if (hdr.len == 0) {
-                    close(stdin_fd);
+                    if (shutdown(stdin_fd, SHUT_WR) < 0) {
+                        if (errno == ENOTSOCK)
+                            close(stdin_fd);
+                    }
                     stdin_fd = -1;
                     return 0;
                 } else {
                     /* FIXME: use buffered write here to prevent deadlock */
                     if (!write_all(stdin_fd, buf, hdr.len)) {
-                        if (errno == EPIPE) {
-                            close(stdin_fd);
+                        if (errno == EPIPE || errno == ECONNRESET) {
+                            if (shutdown(stdin_fd, SHUT_WR) < 0) {
+                                if (errno == ENOTSOCK)
+                                    close(stdin_fd);
+                            }
                             stdin_fd = -1;
                         } else {
                             perror("write");
@@ -248,7 +265,10 @@ void process_child_io(libvchan_t *data_vchan,
                 if (pid == child_process_pid) {
                     child_process_status = WEXITSTATUS(status);
                     if (stdin_fd >= 0) {
-                        close(stdin_fd);
+                        if (shutdown(stdin_fd, SHUT_WR) < 0) {
+                            if (errno == ENOTSOCK)
+                                close(stdin_fd);
+                        }
                         stdin_fd = -1;
                     }
                 }
@@ -264,6 +284,13 @@ void process_child_io(libvchan_t *data_vchan,
             }
             break;
         }
+        /* child signaled desire to use single socket for both stdin and stdout */
+        if (stdio_socket_requested) {
+            if (stdout_fd != -1)
+                close(stdout_fd);
+            stdout_fd = stdin_fd;
+            stdio_socket_requested = 0;
+        }
         /* otherwise handle the events */
 
         FD_ZERO(&rdset);
@@ -337,7 +364,10 @@ void process_child_io(libvchan_t *data_vchan,
                 break;
             case -2:
                 /* remote process exited, no sense in sending more data to it */
-                close(stdout_fd);
+                if (shutdown(stdout_fd, SHUT_RD) < 0) {
+                    if (errno == ENOTSOCK)
+                        close(stdout_fd);
+                }
                 stdout_fd = -1;
                 close(stderr_fd);
                 stderr_fd = -1;
@@ -353,6 +383,7 @@ pid_t handle_new_process(int type, int connect_domain, int connect_port,
     libvchan_t *data_vchan;
     pid_t pid;
     int stdin_fd, stdout_fd, stderr_fd;
+    char pid_s[10];
 
     if (type == MSG_SERVICE_CONNECT) {
         if (cmdline_len != sizeof(*svc_params)) {
@@ -394,6 +425,10 @@ pid_t handle_new_process(int type, int connect_domain, int connect_port,
     handle_handshake(data_vchan);
 
     signal(SIGCHLD, sigchld_handler);
+    signal(SIGUSR1, sigusr1_handler);
+    snprintf(pid_s, sizeof(pid_s), "%d", getpid());
+    setenv("QREXEC_AGENT_PID", pid_s, 1);
+    /* TODO: use setresuid to allow child process to actually send the signal? */
 
     switch (type) {
         case MSG_JUST_EXEC: