qrexec: handle data vchan directly from qrexec-client-vm

This way qrexec-client-vm will have much more information, at least:
 - will know whether the service call was accepted or refused
 - potentially will know remote process exit code
This commit implements the first point - the local process will not be
started if service call was refused.
This commit is contained in:
Marek Marczykowski-Górecki 2015-03-16 21:10:25 +01:00
parent 203691fae0
commit 1aa05ebc36
5 changed files with 181 additions and 105 deletions

View File

@ -6,7 +6,7 @@ LDLIBS=`pkg-config --libs vchan-$(BACKEND_VMM)` -lqrexec-utils
all: qrexec-agent qrexec-client-vm qrexec-fork-server all: qrexec-agent qrexec-client-vm qrexec-fork-server
qrexec-agent: qrexec-agent.o qrexec-agent-data.o qrexec-agent: qrexec-agent.o qrexec-agent-data.o
qrexec-fork-server: qrexec-fork-server.o qrexec-agent-data.o qrexec-fork-server: qrexec-fork-server.o qrexec-agent-data.o
qrexec-client-vm: qrexec-client-vm.o qrexec-client-vm: qrexec-client-vm.o qrexec-agent-data.o
clean: clean:
rm -f *.o *~ qrexec-agent qrexec-client-vm rm -f *.o *~ qrexec-agent qrexec-client-vm

View File

@ -32,6 +32,7 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <fcntl.h> #include <fcntl.h>
#include <libvchan.h> #include <libvchan.h>
#include <assert.h>
#include "qrexec.h" #include "qrexec.h"
#include "libqrexec-utils.h" #include "libqrexec-utils.h"
#include "qrexec-agent.h" #include "qrexec-agent.h"
@ -42,6 +43,7 @@ static volatile int child_exited;
static volatile int stdio_socket_requested; static volatile int stdio_socket_requested;
int stdout_msg_type = MSG_DATA_STDOUT; int stdout_msg_type = MSG_DATA_STDOUT;
pid_t child_process_pid; pid_t child_process_pid;
int remote_process_status = 0;
static void sigchld_handler(int __attribute__((__unused__))x) static void sigchld_handler(int __attribute__((__unused__))x)
{ {
@ -386,45 +388,37 @@ void process_child_io(libvchan_t *data_vchan,
stdout_fd = -1; stdout_fd = -1;
close(stderr_fd); close(stderr_fd);
stderr_fd = -1; stderr_fd = -1;
/* we do not care for any local process */
return;
break; break;
} }
} }
} }
pid_t handle_new_process(int type, int connect_domain, int connect_port, /* Behaviour depends on type parameter:
char *cmdline, int cmdline_len) * MSG_SERVICE_CONNECT - create vchan server, pass the data to/from given FDs
* (stdin_fd, stdout_fd, stderr_fd), then return 0
* MSG_JUST_EXEC - connect to vchan server, fork+exec process given by cmdline
* parameter, send artificial exit code "0" (local process can still be
* running), then return 0
* MSG_EXEC_CMDLINE - connect to vchan server, fork+exec process given by
* cmdline parameter, pass the data to/from that process, then return local
* process exit code
*/
int handle_new_process_common(int type, int connect_domain, int connect_port,
char *cmdline, int cmdline_len, /* MSG_JUST_EXEC and MSG_EXEC_CMDLINE */
int stdin_fd, int stdout_fd, int stderr_fd /* MSG_SERVICE_CONNECT */)
{ {
struct service_params *svc_params = (struct service_params*)cmdline;
libvchan_t *data_vchan; libvchan_t *data_vchan;
int exit_code = 0;
pid_t pid; pid_t pid;
int stdin_fd, stdout_fd, stderr_fd;
char pid_s[10]; char pid_s[10];
if (type == MSG_SERVICE_CONNECT) { if (type != MSG_SERVICE_CONNECT) {
if (cmdline_len != sizeof(*svc_params)) { assert(cmdline != NULL);
fprintf(stderr, "Invalid MSG_SERVICE_CONNECT packet (cmdline len %d)\n", cmdline_len); cmdline[cmdline_len-1] = 0;
return -1;
}
sscanf(cmdline, "%d %d %d", &stdin_fd, &stdout_fd, &stderr_fd);
} }
switch (pid=fork()){
case -1:
perror("fork");
return -1;
case 0:
break;
default:
if (type == MSG_SERVICE_CONNECT) {
/* no longer needed in parent process */
close(stdin_fd);
close(stdout_fd);
close(stderr_fd);
}
return pid;
}
/* child process */
if (type == MSG_SERVICE_CONNECT) { if (type == MSG_SERVICE_CONNECT) {
data_vchan = libvchan_server_init(connect_domain, connect_port, data_vchan = libvchan_server_init(connect_domain, connect_port,
VCHAN_BUFFER_SIZE, VCHAN_BUFFER_SIZE); VCHAN_BUFFER_SIZE, VCHAN_BUFFER_SIZE);
@ -448,13 +442,13 @@ pid_t handle_new_process(int type, int connect_domain, int connect_port,
switch (type) { switch (type) {
case MSG_JUST_EXEC: case MSG_JUST_EXEC:
send_exit_code(data_vchan, handle_just_exec(cmdline)); send_exit_code(data_vchan, handle_just_exec(cmdline));
libvchan_close(data_vchan);
break; break;
case MSG_EXEC_CMDLINE: case MSG_EXEC_CMDLINE:
do_fork_exec(cmdline, &pid, &stdin_fd, &stdout_fd, &stderr_fd); do_fork_exec(cmdline, &pid, &stdin_fd, &stdout_fd, &stderr_fd);
fprintf(stderr, "executed %s pid %d\n", cmdline, pid); fprintf(stderr, "executed %s pid %d\n", cmdline, pid);
child_process_pid = pid; child_process_pid = pid;
process_child_io(data_vchan, stdin_fd, stdout_fd, stderr_fd); exit_code = process_child_io(data_vchan, stdin_fd, stdout_fd, stderr_fd);
fprintf(stderr, "pid %d exited with %d\n", pid, exit_code);
break; break;
case MSG_SERVICE_CONNECT: case MSG_SERVICE_CONNECT:
child_process_pid = 0; child_process_pid = 0;
@ -462,7 +456,44 @@ pid_t handle_new_process(int type, int connect_domain, int connect_port,
process_child_io(data_vchan, stdin_fd, stdout_fd, stderr_fd); process_child_io(data_vchan, stdin_fd, stdout_fd, stderr_fd);
break; break;
} }
exit(0); libvchan_close(data_vchan);
return exit_code;
}
/* Returns PID of data processing process */
pid_t handle_new_process(int type, int connect_domain, int connect_port,
char *cmdline, int cmdline_len)
{
int exit_code;
pid_t pid;
assert(type != MSG_SERVICE_CONNECT);
switch (pid=fork()){
case -1:
perror("fork");
return -1;
case 0:
break;
default:
return pid;
}
/* child process */
exit_code = handle_new_process_common(type, connect_domain, connect_port,
cmdline, cmdline_len,
-1, -1, -1);
exit(exit_code);
/* suppress warning */ /* suppress warning */
return 0; return 0;
} }
void handle_data_client(int type, int connect_domain, int connect_port,
int stdin_fd, int stdout_fd, int stderr_fd)
{
assert(type == MSG_SERVICE_CONNECT);
handle_new_process_common(type, connect_domain, connect_port,
NULL, 0, stdin_fd, stdout_fd, stderr_fd);
}

View File

@ -55,7 +55,6 @@ struct _connection_info connection_info[MAX_FDS];
libvchan_t *ctrl_vchan; libvchan_t *ctrl_vchan;
int trigger_fd; int trigger_fd;
int passfd_socket;
int meminfo_write_started = 0; int meminfo_write_started = 0;
@ -107,11 +106,8 @@ void init()
if (handle_handshake(ctrl_vchan) < 0) if (handle_handshake(ctrl_vchan) < 0)
exit(1); exit(1);
umask(0); umask(0);
mkfifo(QREXEC_AGENT_TRIGGER_PATH, 0666); trigger_fd = get_server_socket(QREXEC_AGENT_TRIGGER_PATH);
passfd_socket = get_server_socket(QREXEC_AGENT_FDPASS_PATH);
umask(077); umask(077);
trigger_fd =
open(QREXEC_AGENT_TRIGGER_PATH, O_RDONLY | O_NONBLOCK);
register_exec_func(do_exec); register_exec_func(do_exec);
/* wait for qrexec daemon */ /* wait for qrexec daemon */
@ -229,6 +225,7 @@ void handle_server_exec_request(struct msg_header *hdr)
struct exec_params params; struct exec_params params;
char buf[hdr->len-sizeof(params)]; char buf[hdr->len-sizeof(params)];
pid_t child_agent; pid_t child_agent;
int client_fd;
assert(hdr->len >= sizeof(params)); assert(hdr->len >= sizeof(params));
@ -248,7 +245,24 @@ void handle_server_exec_request(struct msg_header *hdr)
return; return;
} }
} }
if (hdr->type == MSG_SERVICE_CONNECT && sscanf(buf, "SOCKET%d", &client_fd)) {
/* FIXME: Maybe add some check if client_fd is really FD to some
* qrexec-client-vm process; but this data comes from qrexec-daemon
* (which sends back what it got from us earlier), so it isn't critical.
*/
write(client_fd, &params, sizeof(params));
/* No need to send request_id (buf) - the client don't need it, there
* is only meaningless (for the client) socket FD */
/* Register connection even if there was an error sending params to
* qrexec-client-vm. This way the mainloop will clean the things up
* (close socket, send MSG_CONNECTION_TERMINATED) when qrexec-client-vm
* will close the socket (terminate itself). */
register_vchan_connection(-1, client_fd,
params.connect_domain, params.connect_port);
return;
}
/* No fork server case */
child_agent = handle_new_process(hdr->type, child_agent = handle_new_process(hdr->type,
params.connect_domain, params.connect_port, params.connect_domain, params.connect_port,
buf, hdr->len-sizeof(params)); buf, hdr->len-sizeof(params));
@ -260,7 +274,7 @@ void handle_server_exec_request(struct msg_header *hdr)
void handle_service_refused(struct msg_header *hdr) void handle_service_refused(struct msg_header *hdr)
{ {
struct service_params params; struct service_params params;
int stdin_fd, stdout_fd, stderr_fd; int socket_fd;
if (hdr->len != sizeof(params)) { if (hdr->len != sizeof(params)) {
fprintf(stderr, "Invalid msg 0x%x length (%d)\n", MSG_SERVICE_REFUSED, hdr->len); fprintf(stderr, "Invalid msg 0x%x length (%d)\n", MSG_SERVICE_REFUSED, hdr->len);
@ -270,11 +284,10 @@ void handle_service_refused(struct msg_header *hdr)
if (libvchan_recv(ctrl_vchan, &params, sizeof(params)) < 0) if (libvchan_recv(ctrl_vchan, &params, sizeof(params)) < 0)
handle_vchan_error("read exec params"); handle_vchan_error("read exec params");
sscanf(params.ident, "%d %d %d", &stdin_fd, &stdout_fd, &stderr_fd); if (sscanf(params.ident, "SOCKET%d", &socket_fd))
/* TODO: send some signal? some response? */ close(socket_fd);
close(stdin_fd); else
close(stdout_fd); fprintf(stderr, "Received REFUSED for unknown service request '%s'\n", params.ident);
close(stderr_fd);
} }
void handle_server_cmd() void handle_server_cmd()
@ -360,9 +373,6 @@ int fill_fds_for_select(fd_set * rdset, fd_set * wrset)
FD_SET(trigger_fd, rdset); FD_SET(trigger_fd, rdset);
if (trigger_fd > max) if (trigger_fd > max)
max = trigger_fd; max = trigger_fd;
FD_SET(passfd_socket, rdset);
if (passfd_socket > max)
max = passfd_socket;
for (i = 0; i < MAX_FDS; i++) { for (i = 0; i < MAX_FDS; i++) {
if (connection_info[i].pid != 0 && connection_info[i].fd != -1) { if (connection_info[i].pid != 0 && connection_info[i].fd != -1) {
@ -374,41 +384,31 @@ int fill_fds_for_select(fd_set * rdset, fd_set * wrset)
return max; return max;
} }
void handle_new_passfd()
{
int fd = do_accept(passfd_socket);
if (fd >= MAX_FDS) {
fprintf(stderr, "too many clients ?\n");
exit(1);
}
// let client know what fd has been allocated
if (write(fd, &fd, sizeof(fd)) != sizeof(fd)) {
perror("write to client");
}
}
void handle_trigger_io() void handle_trigger_io()
{ {
struct msg_header hdr; struct msg_header hdr;
struct trigger_service_params params; struct trigger_service_params params;
int ret; int ret;
int client_fd;
client_fd = do_accept(trigger_fd);
if (client_fd < 0)
return;
hdr.len = sizeof(params); hdr.len = sizeof(params);
ret = read(trigger_fd, &params, sizeof(params)); ret = read(client_fd, &params, sizeof(params));
if (ret == sizeof(params)) { if (ret == sizeof(params)) {
hdr.type = MSG_TRIGGER_SERVICE; hdr.type = MSG_TRIGGER_SERVICE;
snprintf(params.request_id.ident, sizeof(params.request_id), "SOCKET%d", client_fd);
if (libvchan_send(ctrl_vchan, &hdr, sizeof(hdr)) < 0) if (libvchan_send(ctrl_vchan, &hdr, sizeof(hdr)) < 0)
handle_vchan_error("write hdr"); handle_vchan_error("write hdr");
if (libvchan_send(ctrl_vchan, &params, sizeof(params)) < 0) if (libvchan_send(ctrl_vchan, &params, sizeof(params)) < 0)
handle_vchan_error("write params"); handle_vchan_error("write params");
} }
// trigger_fd is nonblock - so no need to reopen
// not really, need to reopen at EOF
if (ret <= 0) { if (ret <= 0) {
close(trigger_fd); close(client_fd);
trigger_fd =
open(QREXEC_AGENT_TRIGGER_PATH, O_RDONLY | O_NONBLOCK);
} }
/* do not close client_fd - we'll need it to send the connection details
* later (when dom0 accepts the request) */
} }
void handle_terminated_fork_client(fd_set *rdset) { void handle_terminated_fork_client(fd_set *rdset) {
@ -454,9 +454,6 @@ int main()
wait_for_vchan_or_argfd(ctrl_vchan, max, &rdset, &wrset); wait_for_vchan_or_argfd(ctrl_vchan, max, &rdset, &wrset);
sigprocmask(SIG_UNBLOCK, &chld_set, NULL); sigprocmask(SIG_UNBLOCK, &chld_set, NULL);
if (FD_ISSET(passfd_socket, &rdset))
handle_new_passfd();
while (libvchan_data_ready(ctrl_vchan)) while (libvchan_data_ready(ctrl_vchan))
handle_server_cmd(); handle_server_cmd();

View File

@ -28,6 +28,10 @@ void do_exec(const char *cmd);
pid_t handle_new_process(int type, pid_t handle_new_process(int type,
int connect_domain, int connect_port, int connect_domain, int connect_port,
char *cmdline, int cmdline_len); char *cmdline, int cmdline_len);
void handle_data_client(int type,
int connect_domain, int connect_port,
int stdin_fd, int stdout_fd, int stderr_fd);
struct qrexec_cmd_info { struct qrexec_cmd_info {
int type; int type;

View File

@ -20,14 +20,29 @@
*/ */
#define _GNU_SOURCE #define _GNU_SOURCE
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/wait.h>
#include <sys/un.h> #include <sys/un.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <string.h> #include <string.h>
#include "libqrexec-utils.h"
#include "qrexec.h" #include "qrexec.h"
int connect_unix_socket() #include "qrexec-agent.h"
void handle_vchan_error(const char *op)
{
fprintf(stderr, "Error while vchan %s, exiting\n", op);
exit(1);
}
void do_exec(const char *cmd __attribute__((__unused__))) {
fprintf(stderr, "BUG: do_exec function shouldn't be called!\n");
exit(1);
}
int connect_unix_socket(char *path)
{ {
int s, len; int s, len;
struct sockaddr_un remote; struct sockaddr_un remote;
@ -38,7 +53,7 @@ int connect_unix_socket()
} }
remote.sun_family = AF_UNIX; remote.sun_family = AF_UNIX;
strncpy(remote.sun_path, QREXEC_AGENT_FDPASS_PATH, strncpy(remote.sun_path, path,
sizeof(remote.sun_path)); sizeof(remote.sun_path));
len = strlen(remote.sun_path) + sizeof(remote.sun_family); len = strlen(remote.sun_path) + sizeof(remote.sun_family);
if (connect(s, (struct sockaddr *) &remote, len) == -1) { if (connect(s, (struct sockaddr *) &remote, len) == -1) {
@ -61,9 +76,12 @@ int main(int argc, char **argv)
{ {
int trigger_fd; int trigger_fd;
struct trigger_service_params params; struct trigger_service_params params;
int local_fd[3], remote_fd[3]; struct exec_params exec_params;
int i; int ret, i;
char *abs_exec_path; char *abs_exec_path;
pid_t child_pid;
int inpipe[2], outpipe[2];
char pid_s[10];
if (argc < 4) { if (argc < 4) {
fprintf(stderr, fprintf(stderr,
@ -72,50 +90,76 @@ int main(int argc, char **argv)
exit(1); exit(1);
} }
trigger_fd = open(QREXEC_AGENT_TRIGGER_PATH, O_WRONLY); trigger_fd = connect_unix_socket(QREXEC_AGENT_TRIGGER_PATH);
if (trigger_fd < 0) {
perror("open " QREXEC_AGENT_TRIGGER_PATH);
exit(1);
}
for (i = 0; i < 3; i++) {
local_fd[i] = connect_unix_socket();
if (read(local_fd[i], &remote_fd[i], sizeof(remote_fd[i])) != sizeof(remote_fd[i])) {
perror("read client fd");
exit(1);
}
if (i != 2 || getenv("PASS_LOCAL_STDERR")) {
char *env;
if (asprintf(&env, "SAVED_FD_%d=%d", i, dup(i)) < 0) {
perror("prepare SAVED_FD_");
exit(1);
}
putenv(env);
dup2(local_fd[i], i);
close(local_fd[i]);
} else
close(local_fd[i]);
}
memset(&params, 0, sizeof(params)); memset(&params, 0, sizeof(params));
strncpy(params.service_name, argv[2], sizeof(params.service_name)); strncpy(params.service_name, argv[2], sizeof(params.service_name));
strncpy(params.target_domain, argv[1], strncpy(params.target_domain, argv[1],
sizeof(params.target_domain)); sizeof(params.target_domain));
snprintf(params.request_id.ident, snprintf(params.request_id.ident,
sizeof(params.request_id.ident), "%d %d %d", sizeof(params.request_id.ident), "SOCKET");
remote_fd[0], remote_fd[1], remote_fd[2]);
if (write(trigger_fd, &params, sizeof(params)) < 0) { if (write(trigger_fd, &params, sizeof(params)) < 0) {
if (!getenv("PASS_LOCAL_STDERR")) perror("write to agent");
perror("write to agent"); exit(1);
}
ret = read(trigger_fd, &exec_params, sizeof(exec_params));
if (ret == 0) {
fprintf(stderr, "Request refused\n");
exit(1);
}
if (ret < 0 || ret != sizeof(exec_params)) {
perror("read");
exit(1); exit(1);
} }
close(trigger_fd); if (socketpair(AF_UNIX, SOCK_STREAM, 0, inpipe) ||
socketpair(AF_UNIX, SOCK_STREAM, 0, outpipe)) {
perror("socketpair");
exit(1);
}
snprintf(pid_s, sizeof(pid_s), "%d", getpid());
setenv("QREXEC_AGENT_PID", pid_s, 1);
abs_exec_path = strdup(argv[3]); switch (child_pid = fork()) {
argv[3] = get_program_name(argv[3]); case -1:
execv(abs_exec_path, argv + 3); perror("fork");
perror("execv"); exit(-1);
return 1; case 0:
close(inpipe[1]);
close(outpipe[0]);
close(trigger_fd);
for (i = 0; i < 3; i++) {
if (i != 2 || getenv("PASS_LOCAL_STDERR")) {
char *env;
if (asprintf(&env, "SAVED_FD_%d=%d", i, dup(i)) < 0) {
perror("prepare SAVED_FD_");
exit(1);
}
putenv(env);
}
}
dup2(inpipe[0], 0);
dup2(outpipe[1], 1);
close(inpipe[0]);
close(outpipe[1]);
abs_exec_path = strdup(argv[3]);
argv[3] = get_program_name(argv[3]);
execv(abs_exec_path, argv + 3);
perror("execv");
exit(-1);
}
close(inpipe[0]);
close(outpipe[1]);
handle_data_client(MSG_SERVICE_CONNECT,
exec_params.connect_domain, exec_params.connect_port,
inpipe[1], outpipe[0], -1);
close(trigger_fd);
waitpid(child_pid, &i, 0);
return 0;
} }