From b98dffc965c73d18e6baa28728f959fa6300e761 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Fri, 4 Mar 2011 16:32:58 +0100 Subject: [PATCH 01/64] qrexec* tools, initial version --- qrexec/Makefile | 13 ++ qrexec/buffer.c | 93 ++++++++ qrexec/buffer.h | 32 +++ qrexec/exec.c | 74 +++++++ qrexec/glue.h | 49 +++++ qrexec/ioall.c | 67 ++++++ qrexec/qrexec.h | 58 +++++ qrexec/qrexec_agent.c | 452 +++++++++++++++++++++++++++++++++++++++ qrexec/qrexec_client.c | 236 ++++++++++++++++++++ qrexec/qrexec_daemon.c | 319 +++++++++++++++++++++++++++ qrexec/txrx-vchan.c | 207 ++++++++++++++++++ qrexec/unix_server.c | 70 ++++++ qrexec/write_stdin.c | 85 ++++++++ rpm_spec/core-appvm.spec | 3 + rpm_spec/core-dom0.spec | 5 + rpm_spec/core-netvm.spec | 3 + 16 files changed, 1766 insertions(+) create mode 100644 qrexec/Makefile create mode 100644 qrexec/buffer.c create mode 100644 qrexec/buffer.h create mode 100644 qrexec/exec.c create mode 100644 qrexec/glue.h create mode 100644 qrexec/ioall.c create mode 100644 qrexec/qrexec.h create mode 100644 qrexec/qrexec_agent.c create mode 100644 qrexec/qrexec_client.c create mode 100644 qrexec/qrexec_daemon.c create mode 100644 qrexec/txrx-vchan.c create mode 100644 qrexec/unix_server.c create mode 100644 qrexec/write_stdin.c diff --git a/qrexec/Makefile b/qrexec/Makefile new file mode 100644 index 00000000..72089fda --- /dev/null +++ b/qrexec/Makefile @@ -0,0 +1,13 @@ +CC=gcc +CFLAGS+=-g -Wall +XENLIBS=-lvchan -lxenstore -lxenctrl + +all: qrexec_daemon qrexec_agent qrexec_client +qrexec_daemon: qrexec_daemon.o unix_server.o ioall.o txrx-vchan.o buffer.o write_stdin.o + $(CC) -g -o qrexec_daemon qrexec_daemon.o unix_server.o ioall.o txrx-vchan.o write_stdin.o buffer.o $(XENLIBS) +qrexec_agent: qrexec_agent.o ioall.o exec.o txrx-vchan.o write_stdin.o buffer.o + $(CC) -g -o qrexec_agent qrexec_agent.o ioall.o exec.o txrx-vchan.o write_stdin.o buffer.o $(XENLIBS) +qrexec_client: qrexec_client.o ioall.o exec.o + $(CC) -g -o qrexec_client qrexec_client.o ioall.o exec.o +clean: + rm -f *.o *~ qrexec_daemon qrexec_agent qrexec_client diff --git a/qrexec/buffer.c b/qrexec/buffer.c new file mode 100644 index 00000000..be36a039 --- /dev/null +++ b/qrexec/buffer.c @@ -0,0 +1,93 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include "buffer.h" + +#define BUFFER_LIMIT 50000000 +static int total_mem; +static char *limited_malloc(int len) +{ + char *ret; + total_mem += len; + if (total_mem > BUFFER_LIMIT) { + fprintf(stderr, "attempt to allocate >BUFFER_LIMIT\n"); + exit(1); + } + ret = malloc(len); + if (!ret) { + perror("malloc"); + exit(1); + } + return ret; +} + +static void limited_free(char *ptr, int len) +{ + free(ptr); + total_mem -= len; +} + +void buffer_init(struct buffer *b) +{ + b->buflen = 0; + b->data = NULL; +} + +void buffer_free(struct buffer *b) +{ + if (b->buflen) + limited_free(b->data, b->buflen); + buffer_init(b); +} + +void buffer_append(struct buffer *b, char *data, int len) +{ + int newsize = len + b->buflen; + char *qdata = limited_malloc(len + b->buflen); + memcpy(qdata, b->data, b->buflen); + memcpy(qdata + b->buflen, data, len); + buffer_free(b); + b->buflen = newsize; + b->data = qdata; +} + +void buffer_remove(struct buffer *b, int len) +{ + int newsize = b->buflen - len; + char *qdata = limited_malloc(newsize); + memcpy(qdata, b->data + len, newsize); + buffer_free(b); + b->buflen = newsize; + b->data = qdata; +} + +int buffer_len(struct buffer *b) +{ + return b->buflen; +} + +void *buffer_data(struct buffer *b) +{ + return b->data; +} diff --git a/qrexec/buffer.h b/qrexec/buffer.h new file mode 100644 index 00000000..80b3779d --- /dev/null +++ b/qrexec/buffer.h @@ -0,0 +1,32 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +struct buffer { + char *data; + int buflen; +}; + +void buffer_init(struct buffer *b); +void buffer_free(struct buffer *b); +void buffer_append(struct buffer *b, char *data, int len); +void buffer_remove(struct buffer *b, int len); +int buffer_len(struct buffer *b); +void *buffer_data(struct buffer *b); diff --git a/qrexec/exec.c b/qrexec/exec.c new file mode 100644 index 00000000..a87f5050 --- /dev/null +++ b/qrexec/exec.c @@ -0,0 +1,74 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include + +extern void do_exec(char *); + +void fix_fds(int fdin, int fdout, int fderr) +{ + int i; + for (i = 0; i < 256; i++) + if (i != fdin && i != fdout && i != fderr) + close(i); + dup2(fdin, 0); + dup2(fdout, 1); + dup2(fderr, 2); + close(fdin); + close(fdout); + if (fderr != 2) + close(fderr); +} + +void do_fork_exec(char *cmdline, int *pid, int *stdin_fd, int *stdout_fd, + int *stderr_fd) +{ + int inpipe[2], outpipe[2], errpipe[2]; + + if (pipe(inpipe) || pipe(outpipe) || (stderr_fd && pipe(errpipe))) { + perror("pipe"); + exit(1); + } + switch (*pid = fork()) { + case -1: + perror("fork"); + exit(-1); + case 0: + if (stderr_fd) { + fix_fds(inpipe[0], outpipe[1], errpipe[1]); + } else + fix_fds(inpipe[0], outpipe[1], 2); + + do_exec(cmdline); + exit(-1); + default:; + } + close(inpipe[0]); + close(outpipe[1]); + *stdin_fd = inpipe[1]; + *stdout_fd = outpipe[0]; + if (stderr_fd) { + close(errpipe[1]); + *stderr_fd = errpipe[0]; + } +} diff --git a/qrexec/glue.h b/qrexec/glue.h new file mode 100644 index 00000000..228b6866 --- /dev/null +++ b/qrexec/glue.h @@ -0,0 +1,49 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include + +void do_fork_exec(char *cmdline, int *pid, int *stdin_fd, int *stdout_fd, + int *stderr_fd); +int write_all(int fd, void *buf, int size); +int read_all(int fd, void *buf, int size); +int peer_server_init(int port); +char *peer_client_init(int dom, int port); +void wait_for_vchan_or_argfd(int max, fd_set * rdset, fd_set * wrset); +int read_ready_vchan_ext(); +int read_all_vchan_ext(void *buf, int size); +int write_all_vchan_ext(void *buf, int size); +int buffer_space_vchan_ext(); +void fix_fds(int fdin, int fdout, int fderr); + +int get_server_socket(int domid); +int do_accept(int s); +void set_nonblock(int fd); + +enum { + WRITE_STDIN_OK = 0x200, + WRITE_STDIN_BUFFERED, + WRITE_STDIN_ERROR +}; + +int flush_client_data(int fd, int clid, struct buffer *buffer); +int write_stdin(int fd, int clid, char *data, int len, + struct buffer *buffer); diff --git a/qrexec/ioall.c b/qrexec/ioall.c new file mode 100644 index 00000000..9e8d7a3c --- /dev/null +++ b/qrexec/ioall.c @@ -0,0 +1,67 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include + +int write_all(int fd, void *buf, int size) +{ + int written = 0; + int ret; + while (written < size) { + ret = write(fd, (char *) buf + written, size - written); + if (ret <= 0) { + perror("write"); + return 0; + } + written += ret; + } +// fprintf(stderr, "sent %d bytes\n", size); + return 1; +} + +int read_all(int fd, void *buf, int size) +{ + int got_read = 0; + int ret; + while (got_read < size) { + ret = read(fd, (char *) buf + got_read, size - got_read); + if (ret == 0) { + fprintf(stderr, "EOF\n"); + return 0; + } + if (ret < 0) { + perror("read"); + return 0; + } + got_read += ret; + } +// fprintf(stderr, "read %d bytes\n", size); + return 1; +} + +void set_nonblock(int fd) +{ + int fl = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, fl | O_NONBLOCK); +} diff --git a/qrexec/qrexec.h b/qrexec/qrexec.h new file mode 100644 index 00000000..729ff932 --- /dev/null +++ b/qrexec/qrexec.h @@ -0,0 +1,58 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#define QREXEC_DAEMON_SOCKET_DIR "/var/run/qubes" +#define MAX_FDS 256 +#define MAX_DATA_CHUNK 4096 + +#define REXEC_PORT 512 + +enum { + MSG_CLIENT_TO_SERVER_EXEC_CMDLINE = 0x100, + MSG_CLIENT_TO_SERVER_JUST_EXEC, + + MSG_SERVER_TO_AGENT_EXEC_CMDLINE, + MSG_SERVER_TO_AGENT_JUST_EXEC, + MSG_SERVER_TO_AGENT_INPUT, + MSG_SERVER_TO_AGENT_CLIENT_END, + + MSG_XOFF, + MSG_XON, + + MSG_AGENT_TO_SERVER_STDOUT, + MSG_AGENT_TO_SERVER_STDERR, + MSG_AGENT_TO_SERVER_EXIT_CODE, + + MSG_SERVER_TO_CLIENT_STDOUT, + MSG_SERVER_TO_CLIENT_STDERR, + MSG_SERVER_TO_CLIENT_EXIT_CODE +}; + +struct server_header { + unsigned int type; + unsigned int clid; + unsigned int len; +}; + +struct client_header { + unsigned int type; + unsigned int len; +}; diff --git a/qrexec/qrexec_agent.c b/qrexec/qrexec_agent.c new file mode 100644 index 00000000..1b6f7bad --- /dev/null +++ b/qrexec/qrexec_agent.c @@ -0,0 +1,452 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qrexec.h" +#include "buffer.h" +#include "glue.h" + +enum fdtype { + FDTYPE_INVALID, + FDTYPE_STDOUT, + FDTYPE_STDERR +}; + +struct _process_fd { + int clid; + int type; + int is_blocked; +}; +struct _client_info { + int stdin_fd; + int stdout_fd; + int stderr_fd; + + int pid; + int is_blocked; + struct buffer buffer; +}; + +int max_process_fd = -1; + +/* indexed by file descriptor */ +struct _process_fd process_fd[MAX_FDS]; + +/* indexed by client id, which is descriptor number of a client in daemon */ +struct _client_info client_info[MAX_FDS]; + +void init() +{ + peer_server_init(REXEC_PORT); +} + +void do_exec(char *cmd) +{ + char *sep = index(cmd, ':'); + if (!sep) { + fprintf(stderr, + "cmdline is supposed to be in user:command form\n"); + exit(1); + } + *sep = 0; + signal(SIGCHLD, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + + execl("/bin/su", "su", "-", cmd, "-c", sep + 1, NULL); + perror("execl"); + exit(1); +} + +void handle_just_exec(int clid, int len) +{ + char buf[len]; + int fdn, pid; + + read_all_vchan_ext(buf, len); + switch (pid=fork()) { + case -1: + perror("fork"); + exit(1); + case 0: + fdn = open("/dev/null", O_RDWR); + fix_fds(fdn, fdn, fdn); + do_exec(buf); + perror("execl"); + exit(1); + default:; + } + fprintf(stderr, "executed (nowait) %s pid %d\n", buf, pid); +} + +void handle_exec(int clid, int len) +{ + char buf[len]; + int pid, stdin_fd, stdout_fd, stderr_fd; + + read_all_vchan_ext(buf, len); + + do_fork_exec(buf, &pid, &stdin_fd, &stdout_fd, &stderr_fd); + + process_fd[stdout_fd].clid = clid; + process_fd[stdout_fd].type = FDTYPE_STDOUT; + process_fd[stdout_fd].is_blocked = 0; + process_fd[stderr_fd].clid = clid; + process_fd[stderr_fd].type = FDTYPE_STDERR; + process_fd[stderr_fd].is_blocked = 0; + + if (stderr_fd > max_process_fd) + max_process_fd = stderr_fd; + if (stdout_fd > max_process_fd) + max_process_fd = stdout_fd; + + set_nonblock(stdin_fd); + + client_info[clid].stdin_fd = stdin_fd; + client_info[clid].stdout_fd = stdout_fd; + client_info[clid].stderr_fd = stderr_fd; + client_info[clid].pid = pid; + client_info[clid].is_blocked = 0; + buffer_init(&client_info[clid].buffer); + + fprintf(stderr, "executed %s pid %d\n", buf, pid); + +} + + +void update_max_process_fd() +{ + int i; + for (i = max_process_fd; + process_fd[i].type == FDTYPE_INVALID && i >= 0; i--); + max_process_fd = i; +} + +void send_exit_code(int clid, int status) +{ + struct server_header s_hdr; + s_hdr.type = MSG_AGENT_TO_SERVER_EXIT_CODE; + s_hdr.clid = clid; + s_hdr.len = sizeof status; + write_all_vchan_ext(&s_hdr, sizeof s_hdr); + write_all_vchan_ext(&status, sizeof(status)); + fprintf(stderr, "send exit code for clid %d pid %d\n", clid, + client_info[clid].pid); +} + + +// erase process data structures, possibly forced by remote +void remove_process(int clid, int status) +{ + int i; + if (!client_info[clid].pid) + return; + kill(client_info[clid].pid, SIGKILL); + + if (status != -1) + send_exit_code(clid, status); + + + close(client_info[clid].stdin_fd); + client_info[clid].pid = 0; + client_info[clid].stdin_fd = -1; + client_info[clid].is_blocked = 0; + buffer_free(&client_info[clid].buffer); + + for (i = 0; i <= max_process_fd; i++) + if (process_fd[i].type != FDTYPE_INVALID + && process_fd[i].clid == clid) { + process_fd[i].type = FDTYPE_INVALID; + process_fd[i].clid = -1; + process_fd[i].is_blocked = 0; + close(i); + } + update_max_process_fd(); +} + +void handle_input(int clid, int len) +{ + char buf[len]; + + read_all_vchan_ext(buf, len); + if (!client_info[clid].pid) + return; + + if (len == 0) { + close(client_info[clid].stdin_fd); + client_info[clid].stdin_fd = -1; + return; + } + + switch (write_stdin + (client_info[clid].stdin_fd, clid, buf, len, + &client_info[clid].buffer)) { + case WRITE_STDIN_OK: + break; + case WRITE_STDIN_BUFFERED: + client_info[clid].is_blocked = 1; + break; + case WRITE_STDIN_ERROR: + remove_process(clid, 128); + break; + default: + fprintf(stderr, "unknown write_stdin?\n"); + exit(1); + } + +} + +void set_blocked_outerr(int clid, int val) +{ + process_fd[client_info[clid].stdout_fd].is_blocked = val; + process_fd[client_info[clid].stderr_fd].is_blocked = val; +} + +void handle_server_data() +{ + struct server_header s_hdr; + read_all_vchan_ext(&s_hdr, sizeof s_hdr); + +// fprintf(stderr, "got %x %x %x\n", s_hdr.type, s_hdr.clid, +// s_hdr.len); + + switch (s_hdr.type) { + case MSG_XON: + set_blocked_outerr(s_hdr.clid, 0); + break; + case MSG_XOFF: + set_blocked_outerr(s_hdr.clid, 1); + break; + case MSG_SERVER_TO_AGENT_EXEC_CMDLINE: + handle_exec(s_hdr.clid, s_hdr.len); + break; + case MSG_SERVER_TO_AGENT_JUST_EXEC: + handle_just_exec(s_hdr.clid, s_hdr.len); + break; + case MSG_SERVER_TO_AGENT_INPUT: + handle_input(s_hdr.clid, s_hdr.len); + break; + case MSG_SERVER_TO_AGENT_CLIENT_END: + remove_process(s_hdr.clid, -1); + break; + default: + fprintf(stderr, "msg type from daemon is %d ?\n", + s_hdr.type); + exit(1); + } +} + +void handle_process_data(int fd) +{ + struct server_header s_hdr; + char buf[MAX_DATA_CHUNK]; + int ret; + int len; + + len = buffer_space_vchan_ext(); + if (len <= sizeof s_hdr) + return; + + ret = read(fd, buf, len - sizeof s_hdr); + s_hdr.clid = process_fd[fd].clid; + + if (process_fd[fd].type == FDTYPE_STDOUT) + s_hdr.type = MSG_AGENT_TO_SERVER_STDOUT; + else if (process_fd[fd].type == FDTYPE_STDERR) + s_hdr.type = MSG_AGENT_TO_SERVER_STDERR; + else { + fprintf(stderr, "fd=%d, clid=%d, type=%d ?\n", fd, + process_fd[fd].clid, process_fd[fd].type); + exit(1); + } + s_hdr.len = ret; + if (ret >= 0) { + write_all_vchan_ext(&s_hdr, sizeof s_hdr); + write_all_vchan_ext(buf, ret); + } + if (ret == 0) { + process_fd[fd].type = FDTYPE_INVALID; + process_fd[fd].clid = -1; + process_fd[fd].is_blocked = 0; + close(fd); + update_max_process_fd(); + } + if (ret < 0) + remove_process(process_fd[fd].clid, 127); +} + +volatile int child_exited; + +void sigchld_handler(int x) +{ + child_exited = 1; + signal(SIGCHLD, sigchld_handler); +} + +int find_info(int pid) +{ + int i; + for (i = 0; i < MAX_FDS; i++) + if (client_info[i].pid == pid) + return i; + return -1; +} + + +void handle_process_data_all(fd_set * select_fds) +{ + int i; + for (i = 0; i <= max_process_fd; i++) + if (process_fd[i].type != FDTYPE_INVALID + && FD_ISSET(i, select_fds)) + handle_process_data(i); +} + + +void flush_out_err(int clid) +{ + fd_set select_set; + int fd_max = -1; + int i; + int ret; + struct timeval tv; + for (;;) { + FD_ZERO(&select_set); + for (i = 0; i <= max_process_fd; i++) { + if (process_fd[i].type != FDTYPE_INVALID + && !process_fd[i].is_blocked + && process_fd[i].clid == clid) { + FD_SET(i, &select_set); + fd_max = i; + } + } + if (fd_max == -1) + return; + tv.tv_sec = 0; + tv.tv_usec = 0; + ret = select(fd_max + 1, &select_set, NULL, NULL, &tv); + if (ret < 0 && errno != EINTR) { + perror("select"); + exit(1); + } + if (!ret) + return; + handle_process_data_all(&select_set); + } +} + +void reap_children() +{ + int status; + int pid; + int clid; + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + clid = find_info(pid); + if (clid < 0) + continue; + flush_out_err(clid); + remove_process(clid, status); + } + child_exited = 0; +} + +int fill_fds_for_select(fd_set * rdset, fd_set * wrset) +{ + int max = -1; + int fd, i; + FD_ZERO(rdset); + FD_ZERO(wrset); + + for (i = 0; i <= max_process_fd; i++) + if (process_fd[i].type != FDTYPE_INVALID + && !process_fd[i].is_blocked) { + FD_SET(i, rdset); + max = i; + } + for (i = 0; i < MAX_FDS; i++) + if (client_info[i].pid > 0 && client_info[i].is_blocked) { + fd = client_info[i].stdin_fd; + FD_SET(fd, wrset); + if (fd > max) + max = fd; + } + return max; +} + +void flush_client_data_agent(int clid) +{ + struct _client_info *info = &client_info[clid]; + switch (flush_client_data(info->stdin_fd, clid, &info->buffer)) { + case WRITE_STDIN_OK: + info->is_blocked = 0; + break; + case WRITE_STDIN_ERROR: + remove_process(clid, 128); + break; + case WRITE_STDIN_BUFFERED: + break; + default: + fprintf(stderr, "unknown flush_client_data?\n"); + exit(1); + } +} + + +int main() +{ + fd_set rdset, wrset; + int max; + int i; + + init(); + signal(SIGCHLD, sigchld_handler); + signal(SIGPIPE, SIG_IGN); + + + for (;;) { + max = fill_fds_for_select(&rdset, &wrset); + if (buffer_space_vchan_ext() <= + sizeof(struct server_header)) + FD_ZERO(&rdset); + + wait_for_vchan_or_argfd(max, &rdset, &wrset); + + while (read_ready_vchan_ext()) + handle_server_data(); + + handle_process_data_all(&rdset); + for (i = 0; i <= MAX_FDS; i++) + if (client_info[i].pid > 0 + && client_info[i].is_blocked + && FD_ISSET(client_info[i].stdin_fd, &wrset)) + flush_client_data_agent(i); + + if (child_exited) + reap_children(); + } +} diff --git a/qrexec/qrexec_client.c b/qrexec/qrexec_client.c new file mode 100644 index 00000000..e2878480 --- /dev/null +++ b/qrexec/qrexec_client.c @@ -0,0 +1,236 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include "qrexec.h" +#include "buffer.h" +#include "glue.h" + +int connect_unix_socket(int domid) +{ + int s, len; + struct sockaddr_un remote; + + if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("socket"); + return -1; + } + + remote.sun_family = AF_UNIX; + snprintf(remote.sun_path, sizeof remote.sun_path, + QREXEC_DAEMON_SOCKET_DIR "/qrexec.%d", domid); + len = strlen(remote.sun_path) + sizeof(remote.sun_family); + if (connect(s, (struct sockaddr *) &remote, len) == -1) { + perror("connect"); + exit(1); + } + return s; +} + +void do_exec(char *prog) +{ + execl("/bin/bash", "bash", "-c", prog, NULL); +} + +int local_stdin_fd, local_stdout_fd; + +void prepare_local_fds(char *cmdline) +{ + int pid; + if (!cmdline) { + local_stdin_fd = 1; + local_stdout_fd = 0; + return; + } + do_fork_exec(cmdline, &pid, &local_stdin_fd, &local_stdout_fd, + NULL); +} + + +void send_cmdline(int s, int type, char *cmdline) +{ + struct client_header hdr; + hdr.type = type; + hdr.len = strlen(cmdline) + 1; + if (!write_all(s, &hdr, sizeof(hdr)) + || !write_all(s, cmdline, hdr.len)) { + perror("write daemon"); + exit(1); + } +} + +void handle_input(int s) +{ + char buf[MAX_DATA_CHUNK]; + int ret; + ret = read(local_stdout_fd, buf, sizeof(buf)); + if (ret < 0) { + perror("read"); + exit(1); + } + if (ret == 0) { + local_stdout_fd = -1; + shutdown(s, SHUT_WR); + } + if (!write_all(s, buf, ret)) { + perror("write daemon"); + exit(1); + } +} + +void handle_daemon_data(int s) +{ + int status; + struct client_header hdr; + char buf[MAX_DATA_CHUNK]; + + if (!read_all(s, &hdr, sizeof hdr)) { + perror("read daemon"); + exit(1); + } + if (hdr.len > MAX_DATA_CHUNK) { + fprintf(stderr, "client_header.len=%d\n", hdr.len); + exit(1); + } + if (!read_all(s, buf, hdr.len)) { + perror("read daemon"); + exit(1); + } + + switch (hdr.type) { + case MSG_SERVER_TO_CLIENT_STDOUT: + if (hdr.len == 0) + close(local_stdin_fd); + else + write_all(local_stdin_fd, buf, hdr.len); + break; + case MSG_SERVER_TO_CLIENT_STDERR: + write_all(2, buf, hdr.len); + break; + case MSG_SERVER_TO_CLIENT_EXIT_CODE: + status = *(unsigned int *) buf; + if (WIFEXITED(status)) + exit(WEXITSTATUS(status)); + else + exit(255); + break; + default: + fprintf(stderr, "unknown msg %d\n", hdr.type); + exit(1); + } +} + +// perhaps we could save a syscall if we include both sides in both +// rdset and wrset; to be investigated +void handle_daemon_only_until_writable(s) +{ + fd_set rdset, wrset; + + do { + FD_ZERO(&rdset); + FD_ZERO(&wrset); + FD_SET(s, &rdset); + FD_SET(s, &wrset); + + if (select(s + 1, &rdset, &wrset, NULL, NULL) < 0) { + perror("select"); + exit(1); + } + if (FD_ISSET(s, &rdset)) + handle_daemon_data(s); + } while (!FD_ISSET(s, &wrset)); +} + +void select_loop(int s) +{ + fd_set select_set; + int max; + for (;;) { + handle_daemon_only_until_writable(s); + FD_ZERO(&select_set); + FD_SET(s, &select_set); + max = s; + if (local_stdout_fd != -1) { + FD_SET(local_stdout_fd, &select_set); + if (s < local_stdout_fd) + max = local_stdout_fd; + } + if (select(max + 1, &select_set, NULL, NULL, NULL) < 0) { + perror("select"); + exit(1); + } + if (FD_ISSET(s, &select_set)) + handle_daemon_data(s); + if (local_stdout_fd != -1 + && FD_ISSET(local_stdout_fd, &select_set)) + handle_input(s); + } +} + +void usage(char *name) +{ + fprintf(stderr, + "usage: %s -d domain_num [-l local_prog] -e remote_cmdline\n" + "-e means exit after sending cmd\n", name); + exit(1); +} + +int main(int argc, char **argv) +{ + int opt; + int domid = 0; + int s; + int just_exec = 0; + char *local_cmdline = NULL; + while ((opt = getopt(argc, argv, "d:l:e")) != -1) { + switch (opt) { + case 'd': + domid = atoi(optarg); + break; + case 'l': + local_cmdline = strdup(optarg); + break; + case 'e': + just_exec = 1; + break; + default: + usage(argv[0]); + } + } + if (optind >= argc || !domid) + usage(argv[0]); + s = connect_unix_socket(domid); + prepare_local_fds(local_cmdline); + if (just_exec) + send_cmdline(s, MSG_CLIENT_TO_SERVER_JUST_EXEC, + argv[optind]); + else { + send_cmdline(s, MSG_CLIENT_TO_SERVER_EXEC_CMDLINE, + argv[optind]); + select_loop(s); + } + return 0; +} diff --git a/qrexec/qrexec_daemon.c b/qrexec/qrexec_daemon.c new file mode 100644 index 00000000..ef6ec05f --- /dev/null +++ b/qrexec/qrexec_daemon.c @@ -0,0 +1,319 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include "qrexec.h" +#include "buffer.h" +#include "glue.h" + +enum client_flags { + CLIENT_INVALID = 0, + CLIENT_CMDLINE = 1, + CLIENT_DATA = 2, + CLIENT_DONT_READ = 4, + CLIENT_OUTQ_FULL = 8 +}; + +struct _client { + int state; + struct buffer buffer; +}; + +struct _client clients[MAX_FDS]; + +int max_client_fd = -1; +int server_fd; + +void init(int xid) +{ + if (xid <= 0) { + fprintf(stderr, "domain id=0?\n"); + exit(1); + } + server_fd = get_server_socket(xid); + peer_client_init(xid, REXEC_PORT); + setuid(getuid()); + signal(SIGPIPE, SIG_IGN); +} + +void handle_new_client() +{ + int fd = do_accept(server_fd); + if (fd >= MAX_FDS) { + fprintf(stderr, "too many clients ?\n"); + exit(1); + } + clients[fd].state = CLIENT_CMDLINE; + buffer_init(&clients[fd].buffer); + if (fd > max_client_fd) + max_client_fd = fd; +} + +void flush_client(int fd) +{ + int i; + struct server_header s_hdr; + close(fd); + clients[fd].state = CLIENT_INVALID; + buffer_free(&clients[fd].buffer); + if (max_client_fd == fd) { + for (i = fd; clients[i].state == CLIENT_INVALID && i >= 0; + i--); + max_client_fd = i; + } + s_hdr.type = MSG_SERVER_TO_AGENT_CLIENT_END; + s_hdr.clid = fd; + s_hdr.len = 0; + write_all_vchan_ext(&s_hdr, sizeof(s_hdr)); +} + +void pass_to_agent(int fd, struct server_header *s_hdr) +{ + int len = s_hdr->len; + char buf[len]; + if (!read_all(fd, buf, len)) { + flush_client(fd); + return; + } + write_all_vchan_ext(s_hdr, sizeof(*s_hdr)); + write_all_vchan_ext(buf, len); +} + +void handle_client_cmdline(int fd) +{ + struct client_header hdr; + struct server_header s_hdr; + if (!read_all(fd, &hdr, sizeof hdr)) { + flush_client(fd); + return; + } + switch (hdr.type) { + case MSG_CLIENT_TO_SERVER_EXEC_CMDLINE: + s_hdr.type = MSG_SERVER_TO_AGENT_EXEC_CMDLINE; + break; + case MSG_CLIENT_TO_SERVER_JUST_EXEC: + s_hdr.type = MSG_SERVER_TO_AGENT_JUST_EXEC; + break; + default: + flush_client(fd); + return; + } + + s_hdr.clid = fd; + s_hdr.len = hdr.len; + pass_to_agent(fd, &s_hdr); + clients[fd].state = CLIENT_DATA; + set_nonblock(fd); + if (hdr.type == MSG_CLIENT_TO_SERVER_JUST_EXEC) + flush_client(fd); + +} + +void handle_client_data(int fd) +{ + struct server_header s_hdr; + char buf[MAX_DATA_CHUNK]; + int len, ret; + + if (clients[fd].state == CLIENT_CMDLINE) { + handle_client_cmdline(fd); + return; + } + len = buffer_space_vchan_ext(); + if (len <= sizeof s_hdr) + return; + ret = read(fd, buf, len - sizeof(s_hdr)); + if (ret < 0) { + perror("read client"); + flush_client(fd); + return; + } + s_hdr.clid = fd; + s_hdr.len = ret; + s_hdr.type = MSG_SERVER_TO_AGENT_INPUT; + + write_all_vchan_ext(&s_hdr, sizeof(s_hdr)); + write_all_vchan_ext(buf, ret); + if (ret == 0) + clients[fd].state |= CLIENT_DONT_READ; +} + +void flush_client_data_daemon(int clid) +{ + switch (flush_client_data(clid, clid, &clients[clid].buffer)) { + case WRITE_STDIN_OK: + clients[clid].state &= ~CLIENT_OUTQ_FULL; + break; + case WRITE_STDIN_ERROR: + flush_client(clid); + break; + case WRITE_STDIN_BUFFERED: + break; + default: + fprintf(stderr, "unknown flush_client_data?\n"); + exit(1); + } +} + +void pass_to_client(int clid, struct client_header *hdr) +{ + int len = hdr->len; + char buf[sizeof(*hdr) + len]; + + *(struct client_header *) buf = *hdr; + read_all_vchan_ext(buf + sizeof(*hdr), len); + + switch (write_stdin + (clid, clid, buf, len + sizeof(*hdr), + &clients[clid].buffer)) { + case WRITE_STDIN_OK: + break; + case WRITE_STDIN_BUFFERED: + clients[clid].state |= CLIENT_OUTQ_FULL; + break; + case WRITE_STDIN_ERROR: + flush_client(clid); + break; + default: + fprintf(stderr, "unknown write_stdin?\n"); + exit(1); + } +} + +void handle_agent_data() +{ + struct client_header hdr; + struct server_header s_hdr; + read_all_vchan_ext(&s_hdr, sizeof s_hdr); + +// fprintf(stderr, "got %x %x %x\n", s_hdr.type, s_hdr.clid, +// s_hdr.len); + + if (s_hdr.clid >= MAX_FDS || s_hdr.clid < 0) { + fprintf(stderr, "from agent: clid=%d\n", s_hdr.clid); + exit(1); + } + + if (s_hdr.type == MSG_XOFF) { + clients[s_hdr.clid].state |= CLIENT_DONT_READ; + return; + } + if (s_hdr.type == MSG_XON) { + clients[s_hdr.clid].state &= ~CLIENT_DONT_READ; + return; + } + + switch (s_hdr.type) { + case MSG_AGENT_TO_SERVER_STDOUT: + hdr.type = MSG_SERVER_TO_CLIENT_STDOUT; + break; + case MSG_AGENT_TO_SERVER_STDERR: + hdr.type = MSG_SERVER_TO_CLIENT_STDERR; + break; + case MSG_AGENT_TO_SERVER_EXIT_CODE: + hdr.type = MSG_SERVER_TO_CLIENT_EXIT_CODE; + break; + default: + fprintf(stderr, "from agent: type=%d\n", s_hdr.type); + exit(1); + } + hdr.len = s_hdr.len; + if (hdr.len > MAX_DATA_CHUNK) { + fprintf(stderr, "agent feeded %d of data bytes?\n", + hdr.len); + exit(1); + } + if (clients[s_hdr.clid].state == CLIENT_INVALID) { + // benefit of doubt - maybe client exited earlier + char buf[MAX_DATA_CHUNK]; + read_all_vchan_ext(buf, s_hdr.len); + return; + } + pass_to_client(s_hdr.clid, &hdr); + if (s_hdr.type == MSG_AGENT_TO_SERVER_EXIT_CODE) + flush_client(s_hdr.clid); +} + +int fill_fds_for_select(fd_set * rdset, fd_set * wrset) +{ + int i; + int max = -1; + FD_ZERO(rdset); + FD_ZERO(wrset); + for (i = 0; i <= max_client_fd; i++) { + if (clients[i].state != CLIENT_INVALID + && !(clients[i].state & CLIENT_DONT_READ)) { + FD_SET(i, rdset); + max = i; + } + if (clients[i].state != CLIENT_INVALID + && clients[i].state & CLIENT_OUTQ_FULL) { + FD_SET(i, wrset); + max = i; + } + } + FD_SET(server_fd, rdset); + if (server_fd > max) + max = server_fd; + return max; +} + +int main(int argc, char **argv) +{ + fd_set rdset, wrset; + int i; + int max; + + if (argc != 2) { + fprintf(stderr, "usage: %s domainid\n", argv[0]); + exit(1); + } + init(atoi(argv[1])); + for (;;) { + max = fill_fds_for_select(&rdset, &wrset); + if (buffer_space_vchan_ext() <= + sizeof(struct server_header)) + FD_ZERO(&rdset); + + wait_for_vchan_or_argfd(max, &rdset, &wrset); + + if (FD_ISSET(server_fd, &rdset)) + handle_new_client(); + + while (read_ready_vchan_ext()) + handle_agent_data(); + + for (i = 0; i <= max_client_fd; i++) + if (clients[i].state != CLIENT_INVALID + && FD_ISSET(i, &rdset)) + handle_client_data(i); + + for (i = 0; i <= max_client_fd; i++) + if (clients[i].state != CLIENT_INVALID + && FD_ISSET(i, &wrset)) + flush_client_data_daemon(i); + } +} diff --git a/qrexec/txrx-vchan.c b/qrexec/txrx-vchan.c new file mode 100644 index 00000000..2a95180f --- /dev/null +++ b/qrexec/txrx-vchan.c @@ -0,0 +1,207 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include +#include +#include + +static struct libvchan *ctrl; +static int is_server; +int write_all_vchan_ext(void *buf, int size) +{ + int written = 0; + int ret; + + while (written < size) { + ret = + libvchan_write(ctrl, (char *) buf + written, + size - written); + if (ret <= 0) { + perror("write"); + exit(1); + } + written += ret; + } +// fprintf(stderr, "sent %d bytes\n", size); + return size; +} + + +int read_all_vchan_ext(void *buf, int size) +{ + int written = 0; + int ret; + while (written < size) { + ret = + libvchan_read(ctrl, (char *) buf + written, + size - written); + if (ret == 0) { + fprintf(stderr, "EOF\n"); + exit(1); + } + if (ret < 0) { + perror("read"); + exit(1); + } + written += ret; + } +// fprintf(stderr, "read %d bytes\n", size); + return size; +} + +int read_ready_vchan_ext() +{ + return libvchan_data_ready(ctrl); +} + +int buffer_space_vchan_ext() +{ + return libvchan_buffer_space(ctrl); +} + +// if the remote domain is destroyed, we get no notification +// thus, we check for the status periodically + +static int xc_handle = -1; +void slow_check_for_libvchan_is_eof(struct libvchan *ctrl) +{ + struct evtchn_status evst; + evst.port = ctrl->evport; + evst.dom = DOMID_SELF; + if (xc_evtchn_status(xc_handle, &evst)) { + perror("xc_evtchn_status"); + exit(1); + } + if (evst.status != EVTCHNSTAT_interdomain) { + fprintf(stderr, "event channel disconnected\n"); + exit(0); + } +} + + +int wait_for_vchan_or_argfd_once(int max, fd_set * rdset, fd_set * wrset) +{ + int vfd, ret; + struct timeval tv = { 1, 100000 }; + vfd = libvchan_fd_for_select(ctrl); + FD_SET(vfd, rdset); + if (vfd > max) + max = vfd; + max++; + ret = select(max, rdset, wrset, NULL, &tv); + if (ret < 0) { + if (errno != EINTR) { + perror("select"); + exit(1); + } else { + FD_ZERO(rdset); + FD_ZERO(wrset); + fprintf(stderr, "eintr\n"); + return 1; + } + + } + if (libvchan_is_eof(ctrl)) { + fprintf(stderr, "libvchan_is_eof\n"); + exit(0); + } + if (!is_server && ret == 0) + slow_check_for_libvchan_is_eof(ctrl); + if (FD_ISSET(vfd, rdset)) + // the following will never block; we need to do this to + // clear libvchan_fd pending state + libvchan_wait(ctrl); + return ret; +} + +void wait_for_vchan_or_argfd(int max, fd_set * rdset, fd_set * wrset) +{ + fd_set r = *rdset, w = *wrset; + do { + *rdset = r; + *wrset = w; + } + while (wait_for_vchan_or_argfd_once(max, rdset, wrset) == 0); +} + +int peer_server_init(int port) +{ + is_server = 1; + ctrl = libvchan_server_init(port); + if (!ctrl) { + perror("libvchan_server_init"); + exit(1); + } + return 0; +} + +char *peer_client_init(int dom, int port) +{ + struct xs_handle *xs; + char buf[64]; + char *name; + char *dummy; + unsigned int len = 0; + char devbuf[128]; + unsigned int count; + char **vec; + +// double_buffered = 1; // writes to vchan are buffered, nonblocking +// double_buffer_init(); + xs = xs_daemon_open(); + if (!xs) { + perror("xs_daemon_open"); + exit(1); + } + snprintf(buf, sizeof(buf), "/local/domain/%d/name", dom); + name = xs_read(xs, 0, buf, &len); + if (!name) { + perror("xs_read domainname"); + exit(1); + } + snprintf(devbuf, sizeof(devbuf), + "/local/domain/%d/device/vchan/%d/event-channel", dom, + port); + xs_watch(xs, devbuf, devbuf); + do { + vec = xs_read_watch(xs, &count); + if (vec) + free(vec); + len = 0; + dummy = xs_read(xs, 0, devbuf, &len); + } + while (!dummy || !len); // wait for the server to create xenstore entries + free(dummy); + xs_daemon_close(xs); + + // now client init should succeed; "while" is redundant + while (!(ctrl = libvchan_client_init(dom, port))); + + xc_handle = xc_interface_open(); + if (xc_handle < 0) { + perror("xc_interface_open"); + exit(1); + } + return name; +} diff --git a/qrexec/unix_server.c b/qrexec/unix_server.c new file mode 100644 index 00000000..aedf9167 --- /dev/null +++ b/qrexec/unix_server.c @@ -0,0 +1,70 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include "qrexec.h" + +int get_server_socket(int domid) +{ + struct sockaddr_un sockname; + int s; + char socket_address[40]; + + snprintf(socket_address, sizeof(socket_address), + QREXEC_DAEMON_SOCKET_DIR "/qrexec.%d", domid); + unlink(socket_address); + s = socket(AF_UNIX, SOCK_STREAM, 0); + memset(&sockname, 0, sizeof(sockname)); + sockname.sun_family = AF_UNIX; + memcpy(sockname.sun_path, socket_address, strlen(socket_address)); + + if (bind(s, (struct sockaddr *) &sockname, sizeof(sockname)) == -1) { + printf("bind() failed\n"); + close(s); + exit(1); + } +// chmod(sockname.sun_path, 0666); + if (listen(s, 5) == -1) { + perror("listen() failed\n"); + close(s); + exit(1); + } + return s; +} + +int do_accept(int s) +{ + struct sockaddr_un peer; + unsigned int addrlen; + int fd; + addrlen = sizeof(peer); + fd = accept(s, (struct sockaddr *) &peer, &addrlen); + if (fd == -1) { + perror("unix accept"); + exit(1); + } + return fd; +} diff --git a/qrexec/write_stdin.c b/qrexec/write_stdin.c new file mode 100644 index 00000000..9eec24be --- /dev/null +++ b/qrexec/write_stdin.c @@ -0,0 +1,85 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include "qrexec.h" +#include "buffer.h" +#include "glue.h" + +int flush_client_data(int fd, int clid, struct buffer *buffer) +{ + int ret; + int len; + for (;;) { + len = buffer_len(buffer); + if (len > MAX_DATA_CHUNK) + len = MAX_DATA_CHUNK; + ret = write(fd, buffer_data(buffer), len); + if (ret == -1) { + if (errno != EAGAIN) { + return WRITE_STDIN_ERROR; + } else + return WRITE_STDIN_BUFFERED; + } + buffer_remove(buffer, len); + len = buffer_len(buffer); + if (!len) { + struct server_header s_hdr; + s_hdr.type = MSG_XON; + s_hdr.clid = clid; + s_hdr.len = 0; + write_all_vchan_ext(&s_hdr, sizeof s_hdr); + return WRITE_STDIN_OK; + } + } + +} + + +int write_stdin(int fd, int clid, char *data, int len, + struct buffer *buffer) +{ + int ret; + ret = write(fd, data, len); + if (ret == len) + return WRITE_STDIN_OK; + if (ret == -1) { + if (errno == EAGAIN) { + struct server_header s_hdr; + buffer_append(buffer, data, len); + + s_hdr.type = MSG_XOFF; + s_hdr.clid = clid; + s_hdr.len = 0; + write_all_vchan_ext(&s_hdr, sizeof s_hdr); + + return WRITE_STDIN_BUFFERED; + } else + return WRITE_STDIN_ERROR; + } else { + fprintf(stderr, + "writes < PIPE_BUF were supposed to be atomic ?\n"); + return WRITE_STDIN_ERROR; + } + +} diff --git a/rpm_spec/core-appvm.spec b/rpm_spec/core-appvm.spec index c9a009d1..6dccb5fb 100644 --- a/rpm_spec/core-appvm.spec +++ b/rpm_spec/core-appvm.spec @@ -60,6 +60,7 @@ fi %build make clean all make -C ../common +make -C ../qrexec %install @@ -72,6 +73,7 @@ mkdir -p $RPM_BUILD_ROOT/usr/bin cp qubes_timestamp qvm-copy-to-vm qvm-open-in-dvm $RPM_BUILD_ROOT/usr/bin mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes cp qubes_add_pendrive_script qubes_penctl qvm-copy-to-vm.kde $RPM_BUILD_ROOT/usr/lib/qubes +cp ../qrexec/qrexec_agent $RPM_BUILD_ROOT/usr/lib/qubes ln -s /usr/bin/qvm-open-in-dvm $RPM_BUILD_ROOT/usr/lib/qubes/qvm-dvm-transfer cp ../common/meminfo-writer $RPM_BUILD_ROOT/usr/lib/qubes mkdir -p $RPM_BUILD_ROOT/%{kde_service_dir} @@ -199,6 +201,7 @@ rm -rf $RPM_BUILD_ROOT %{kde_service_dir}/qvm-dvm.desktop %attr(4755,root,root) /usr/lib/qubes/qubes_penctl /usr/lib/qubes/qubes_add_pendrive_script +/usr/lib/qubes/qrexec_agent /etc/udev/rules.d/qubes.rules /etc/sysconfig/iptables /var/lib/qubes diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index 9256125b..11182506 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -49,6 +49,7 @@ python -m compileall qvm-core qmemman python -O -m compileall qvm-core qmemman make -C restore make -C ../common +make -C ../qrexec %install @@ -86,6 +87,8 @@ cp aux-tools/reset_vm_configs.py $RPM_BUILD_ROOT/usr/lib/qubes cp pendrive_swapper/qubes_pencmd $RPM_BUILD_ROOT/usr/lib/qubes cp qmemman/server.py $RPM_BUILD_ROOT/usr/lib/qubes/qmemman_daemon.py cp ../common/meminfo-writer $RPM_BUILD_ROOT/usr/lib/qubes/ +cp ../qrexec/qrexec_daemon $RPM_BUILD_ROOT/usr/lib/qubes/ +cp ../qrexec/qrexec_client $RPM_BUILD_ROOT/usr/lib/qubes/ cp restore/xenstore-watch restore/qvm-create-default-dvm $RPM_BUILD_ROOT/usr/bin cp restore/qubes_restore restore/xenfreepages $RPM_BUILD_ROOT/usr/lib/qubes @@ -276,6 +279,8 @@ fi /usr/lib/qubes/qubes_prepare_saved_domain.sh /etc/xen/scripts/block.qubes /etc/xen/scripts/vif-route-qubes +/usr/lib/qubes/qrexec_client +%attr(4750,root,qubes) /usr/lib/qubes/qrexec_daemon %attr(4750,root,qubes) /usr/lib/qubes/xenfreepages %attr(2770,root,qubes) %dir /var/log/qubes %attr(770,root,qubes) %dir /var/run/qubes diff --git a/rpm_spec/core-netvm.spec b/rpm_spec/core-netvm.spec index 83d88c29..678da3a9 100644 --- a/rpm_spec/core-netvm.spec +++ b/rpm_spec/core-netvm.spec @@ -50,6 +50,7 @@ fi %build +make -C ../qrexec %install @@ -61,6 +62,7 @@ mkdir -p $RPM_BUILD_ROOT/etc/init.d cp qubes_core $RPM_BUILD_ROOT/etc/init.d/ mkdir -p $RPM_BUILD_ROOT/var/lib/qubes mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes +cp ../qrexec/qrexec_agent $RPM_BUILD_ROOT/usr/lib/qubes cp ../common/qubes_setup_dnat_to_ns $RPM_BUILD_ROOT/usr/lib/qubes cp ../common/qubes_fix_nm_conf.sh $RPM_BUILD_ROOT/usr/lib/qubes mkdir -p $RPM_BUILD_ROOT/etc/dhclient.d @@ -175,6 +177,7 @@ rm -rf $RPM_BUILD_ROOT /etc/sysconfig/iptables /etc/init.d/qubes_core /var/lib/qubes +/usr/lib/qubes/qrexec_agent /usr/lib/qubes/qubes_setup_dnat_to_ns /usr/lib/qubes/qubes_fix_nm_conf.sh /etc/dhclient.d/qubes_setup_dnat_to_ns.sh From d6f327492dd94f91e1f8bf24110783cbc65aad44 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Fri, 4 Mar 2011 17:19:51 +0100 Subject: [PATCH 02/64] Start qrexec daemon and agent --- appvm/qubes_core | 2 ++ dom0/qvm-tools/qvm-run | 6 ++++++ dom0/qvm-tools/qvm-start | 6 ++++++ netvm/qubes_core | 3 +++ 4 files changed, 17 insertions(+) diff --git a/appvm/qubes_core b/appvm/qubes_core index 714727a2..6b100294 100755 --- a/appvm/qubes_core +++ b/appvm/qubes_core @@ -85,6 +85,8 @@ start() MEMINFO_DELAY_USEC=100000 /usr/lib/qubes/meminfo-writer $MEM_CHANGE_THRESHOLD_KB $MEMINFO_DELAY_USEC & + /usr/lib/qubes/qrexec_agent 2>/var/log/qubes/qrexec_agent.log & + [ -x /rw/config/rc.local ] && /rw/config/rc.local success echo "" diff --git a/dom0/qvm-tools/qvm-run b/dom0/qvm-tools/qvm-run index 6125ad5d..e6f22343 100755 --- a/dom0/qvm-tools/qvm-run +++ b/dom0/qvm-tools/qvm-run @@ -33,6 +33,7 @@ import time qubes_guid_path = "/usr/bin/qubes_guid" qubes_clipd_path = "/usr/bin/qclipd" qubes_qfilexchgd_path= "/usr/bin/qfilexchgd" +qrexec_daemon_path = "/usr/lib/qubes/qrexec_daemon" notify_object = None # how long (in sec) to wait for VMs to shutdown @@ -78,6 +79,11 @@ def vm_run_cmd(vm, cmd, options): if options.tray: tray_notify ("Starting the '{0}' VM...".format(vm.name), label=vm.label) xid = vm.start(verbose=options.verbose) + retcode = subprocess.call ([qrexec_daemon_path, str(xid)]) + if (retcode != 0) : + print "ERROR: Cannot start qrexec_daemon!" + exit (1) + except (IOError, OSError, QubesException) as err: print "ERROR: {0}".format(err) if options.tray: diff --git a/dom0/qvm-tools/qvm-start b/dom0/qvm-tools/qvm-start index 197a3c2c..cd99f71e 100755 --- a/dom0/qvm-tools/qvm-start +++ b/dom0/qvm-tools/qvm-start @@ -26,6 +26,7 @@ from optparse import OptionParser import subprocess qubes_guid_path = "/usr/bin/qubes_guid" +qrexec_daemon_path = "/usr/lib/qubes/qrexec_daemon" def main(): usage = "usage: %prog [options] " @@ -60,6 +61,11 @@ def main(): print "ERROR: {0}".format(err) exit (1) + retcode = subprocess.call ([qrexec_daemon_path, str(xid)]) + if (retcode != 0) : + print "ERROR: Cannot start qrexec_daemon!" + exit (1) + if options.noguid: exit (0) if options.verbose: diff --git a/netvm/qubes_core b/netvm/qubes_core index dbfaad7a..d7f8594d 100755 --- a/netvm/qubes_core +++ b/netvm/qubes_core @@ -28,6 +28,9 @@ start() echo "NS2=$secondary_dns" >> /var/run/qubes/qubes_ns /usr/lib/qubes/qubes_setup_dnat_to_ns echo "1" > /proc/sys/net/ipv4/ip_forward + + /usr/lib/qubes/qrexec_agent 2>/var/log/qubes/qrexec_agent.log & + success echo "" return 0 From b899bfc9ba231fa6f2465f19f167377588c7efd7 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Fri, 4 Mar 2011 17:38:59 +0100 Subject: [PATCH 03/64] Daemonize qrexec_daemon. --- qrexec/qrexec_daemon.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/qrexec/qrexec_daemon.c b/qrexec/qrexec_daemon.c index ef6ec05f..508b164c 100644 --- a/qrexec/qrexec_daemon.c +++ b/qrexec/qrexec_daemon.c @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include "qrexec.h" #include "buffer.h" #include "glue.h" @@ -49,10 +51,37 @@ int server_fd; void init(int xid) { + char dbg_log[256]; + int logfd; + if (xid <= 0) { fprintf(stderr, "domain id=0?\n"); exit(1); } + switch (fork()) { + case -1: + perror("fork"); + exit(1); + case 0: + break; + default: + exit(0); + } + close(0); + snprintf(dbg_log, sizeof(dbg_log), + "/var/log/qubes/qrexec.%d.log", xid); + umask(0007); + logfd = open(dbg_log, O_WRONLY | O_CREAT | O_TRUNC, 0640); + umask(0077); + dup2(logfd, 1); + dup2(logfd, 2); + + chdir("/var/run/qubes"); + if (setsid() < 0) { + perror("setsid()"); + exit(1); + } + server_fd = get_server_socket(xid); peer_client_init(xid, REXEC_PORT); setuid(getuid()); @@ -209,8 +238,8 @@ void handle_agent_data() struct server_header s_hdr; read_all_vchan_ext(&s_hdr, sizeof s_hdr); -// fprintf(stderr, "got %x %x %x\n", s_hdr.type, s_hdr.clid, -// s_hdr.len); +// fprintf(stderr, "got %x %x %x\n", s_hdr.type, s_hdr.clid, +// s_hdr.len); if (s_hdr.clid >= MAX_FDS || s_hdr.clid < 0) { fprintf(stderr, "from agent: clid=%d\n", s_hdr.clid); From bb0507c89af926c3489450543558d3b981526844 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Fri, 4 Mar 2011 17:41:54 +0100 Subject: [PATCH 04/64] Make qrexec_daemon socket accessible. Set restructive umask after socket creation. --- qrexec/qrexec_daemon.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qrexec/qrexec_daemon.c b/qrexec/qrexec_daemon.c index 508b164c..23a01dbf 100644 --- a/qrexec/qrexec_daemon.c +++ b/qrexec/qrexec_daemon.c @@ -72,7 +72,6 @@ void init(int xid) "/var/log/qubes/qrexec.%d.log", xid); umask(0007); logfd = open(dbg_log, O_WRONLY | O_CREAT | O_TRUNC, 0640); - umask(0077); dup2(logfd, 1); dup2(logfd, 2); @@ -82,7 +81,9 @@ void init(int xid) exit(1); } + umask(0); server_fd = get_server_socket(xid); + umask(0077); peer_client_init(xid, REXEC_PORT); setuid(getuid()); signal(SIGPIPE, SIG_IGN); From 50252ec64e23664b37e50fe85994b4ccb8c41577 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Mon, 7 Mar 2011 13:50:30 +0100 Subject: [PATCH 05/64] qrexec_daemon parent should exit after connection to VM. --- qrexec/qrexec_daemon.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/qrexec/qrexec_daemon.c b/qrexec/qrexec_daemon.c index 23a01dbf..06e63068 100644 --- a/qrexec/qrexec_daemon.c +++ b/qrexec/qrexec_daemon.c @@ -49,6 +49,11 @@ struct _client clients[MAX_FDS]; int max_client_fd = -1; int server_fd; +void handle_usr1(int x) +{ + exit(0); +} + void init(int xid) { char dbg_log[256]; @@ -58,6 +63,7 @@ void init(int xid) fprintf(stderr, "domain id=0?\n"); exit(1); } + signal(SIGUSR1, handle_usr1); switch (fork()) { case -1: perror("fork"); @@ -65,6 +71,7 @@ void init(int xid) case 0: break; default: + pause(); exit(0); } close(0); @@ -87,6 +94,7 @@ void init(int xid) peer_client_init(xid, REXEC_PORT); setuid(getuid()); signal(SIGPIPE, SIG_IGN); + signal(SIGUSR1, SIG_DFL); } void handle_new_client() From 27c8b057927b398d797fc34202dadb9edb36412a Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Mon, 7 Mar 2011 13:54:57 +0100 Subject: [PATCH 06/64] qrexec_daemon child should notify the parent. --- qrexec/qrexec_daemon.c | 1 + 1 file changed, 1 insertion(+) diff --git a/qrexec/qrexec_daemon.c b/qrexec/qrexec_daemon.c index 06e63068..5ceb4ab9 100644 --- a/qrexec/qrexec_daemon.c +++ b/qrexec/qrexec_daemon.c @@ -95,6 +95,7 @@ void init(int xid) setuid(getuid()); signal(SIGPIPE, SIG_IGN); signal(SIGUSR1, SIG_DFL); + kill(getppid(), SIGUSR1); } void handle_new_client() From 62d0127647a713d63e29f99a8e64cdaf266d6f48 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Mon, 7 Mar 2011 15:58:04 +0100 Subject: [PATCH 07/64] Integrate qrexec with qvm-run. --- dom0/qvm-tools/qvm-run | 71 ++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/dom0/qvm-tools/qvm-run b/dom0/qvm-tools/qvm-run index e6f22343..b626d929 100755 --- a/dom0/qvm-tools/qvm-run +++ b/dom0/qvm-tools/qvm-run @@ -29,11 +29,14 @@ import socket import errno import dbus import time +import os +import os.path qubes_guid_path = "/usr/bin/qubes_guid" qubes_clipd_path = "/usr/bin/qclipd" qubes_qfilexchgd_path= "/usr/bin/qfilexchgd" qrexec_daemon_path = "/usr/lib/qubes/qrexec_daemon" +qrexec_client_path = "/usr/lib/qubes/qrexec_client" notify_object = None # how long (in sec) to wait for VMs to shutdown @@ -46,6 +49,15 @@ def tray_notify(str, label, timeout = 3000): def tray_notify_error(str, timeout = 3000): notify_object.Notify("Qubes", 0, "dialog-error", "Qubes", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications") +def actually_execute(domid, cmd, options): + args = [qrexec_client_path, "-d", domid, cmd] + if options.localcmd is not None: + args += [ "-l", options.localcmd] + if options.passio and not options.run_on_all_running: + os.execv(qrexec_client_path, args) + exit(1) + args += ["-e"] + subprocess.call(args) def vm_run_cmd(vm, cmd, options): if options.shutdown: @@ -79,10 +91,6 @@ def vm_run_cmd(vm, cmd, options): if options.tray: tray_notify ("Starting the '{0}' VM...".format(vm.name), label=vm.label) xid = vm.start(verbose=options.verbose) - retcode = subprocess.call ([qrexec_daemon_path, str(xid)]) - if (retcode != 0) : - print "ERROR: Cannot start qrexec_daemon!" - exit (1) except (IOError, OSError, QubesException) as err: print "ERROR: {0}".format(err) @@ -96,39 +104,37 @@ def vm_run_cmd(vm, cmd, options): subprocess.call(["kdialog", "--error", "Not enough memory to start '{0}' VM! Close one or more running VMs and try again.".format(vm.name)]) exit (1) - if options.verbose: - print "--> Starting Qubes GUId..." + if os.getenv("DISPLAY") is not None: + if options.verbose: + print "--> Starting Qubes GUId..." - retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-e", cmd, "-i", vm.label.icon, "-l", str(vm.label.index)]) - if (retcode != 0) : - print "ERROR: Cannot start qubes_guid!" - if options.tray: - tray_notify_error ("ERROR: Cannot start qubes_guid!") - exit (1) - else: # VM already running... - guid_is_running = True - xid = vm.get_xid() - s = socket.socket (socket.AF_UNIX) - try: - s.connect ("/var/run/qubes/cmd_socket.{0}".format(xid)) - except (IOError, OSError) as e: - if e.errno in [errno.ENOENT,errno.ECONNREFUSED]: - guid_is_running = False - else: - print "ERROR: unix-connect: {0}".format(e) + retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-i", vm.label.icon, "-l", str(vm.label.index)]) + if (retcode != 0) : + print "ERROR: Cannot start qubes_guid!" if options.tray: - tray_notify_error ("ERROR: Cannot connect to GUI daemon for this VM!") - exit(1) - if guid_is_running: - s.send (cmd) - s.close() - else: - retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-e", cmd, "-i", vm.label.icon, "-l", str(vm.label.index)]) + tray_notify_error ("ERROR: Cannot start qubes_guid!") + exit (1) + + if options.verbose: + print "--> Starting Qubes rexec daemon..." + + retcode = subprocess.call ([qrexec_daemon_path, str(xid)]) + if (retcode != 0) : + print "ERROR: Cannot start qrexec_daemon!" + exit (1) + + actually_execute(str(xid), cmd, options); + + else: # VM already running... + xid = vm.get_xid() + if os.getenv("DISPLAY") is not None and not os.path.isfile("/var/run/qubes/guid_running.{0}".format(xid)): + retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-i", vm.label.icon, "-l", str(vm.label.index)]) if (retcode != 0) : print "ERROR: Cannot start qubes_guid!" if options.tray: tray_notify_error ("ERROR: Cannot start the GUI daemon for this VM!") exit (1) + actually_execute(str(xid), cmd, options); def main(): usage = "usage: %prog [options] [] []" @@ -159,6 +165,11 @@ def main(): parser.add_option ("--unpause", action="store_true", dest="unpause", default=False, help="Do 'xm unpause' for the VM(s) (can be combined this with --all and --wait)") + parser.add_option ("--pass_io", action="store_true", dest="passio", default=False, + help="Pass stdin/stdout/stderr from remote program") + + parser.add_option ("--localcmd", action="store", dest="localcmd", default=None, + help="With --pass_io, pass stdin/stdout/stderr to the given program") (options, args) = parser.parse_args () From eb7821771e38bfe0f2dfc7637302739e7441e3e1 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Mon, 7 Mar 2011 16:05:36 +0100 Subject: [PATCH 08/64] In qvm-start, check $DISPLAY existence, too. --- dom0/qvm-tools/qvm-start | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/dom0/qvm-tools/qvm-start b/dom0/qvm-tools/qvm-start index cd99f71e..f8f155fd 100755 --- a/dom0/qvm-tools/qvm-start +++ b/dom0/qvm-tools/qvm-start @@ -24,6 +24,7 @@ from qubes.qubes import QubesVmCollection from qubes.qubes import QubesException from optparse import OptionParser import subprocess +import os qubes_guid_path = "/usr/bin/qubes_guid" qrexec_daemon_path = "/usr/lib/qubes/qrexec_daemon" @@ -61,20 +62,22 @@ def main(): print "ERROR: {0}".format(err) exit (1) + if not options.noguid and os.getenv("DISPLAY") is not None: + if options.verbose: + print "--> Starting Qubes GUId..." + + retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-i", vm.label.icon, "-l", str(vm.label.index)]) + if (retcode != 0) : + print "ERROR: Cannot start qubes_guid!" + exit (1) + + if options.verbose: + print "--> Starting Qubes rexec..." + retcode = subprocess.call ([qrexec_daemon_path, str(xid)]) if (retcode != 0) : print "ERROR: Cannot start qrexec_daemon!" exit (1) - if options.noguid: - exit (0) - if options.verbose: - print "--> Starting Qubes GUId..." - - retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-i", vm.label.icon, "-l", str(vm.label.index)]) - if (retcode != 0) : - print "ERROR: Cannot start qubes_guid!" - exit (1) - main() From 0d12aeec8826c5344966555874c47349171f2f3d Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Mon, 7 Mar 2011 16:13:15 +0100 Subject: [PATCH 09/64] added "make -C qrexec clean" --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 01085b28..5601dcd7 100644 --- a/Makefile +++ b/Makefile @@ -27,3 +27,4 @@ clean: (cd dom0/restore && make clean) (cd dom0/qmemman && make clean) (cd common && make clean) + make -C qrexec clean From f263aa6b7c0d2cc34a55004eb0e05a568032c626 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Tue, 8 Mar 2011 12:24:47 +0100 Subject: [PATCH 10/64] Moved vchan and u2mfn code to core. --- Makefile | 1 + qrexec/Makefile | 2 +- rpm_spec/core-appvm.spec | 26 +++++ rpm_spec/core-dom0.spec | 7 ++ rpm_spec/core-netvm.spec | 7 ++ u2mfn/Makefile | 33 ++++++ u2mfn/u2mfn-kernel.h | 26 +++++ u2mfn/u2mfnlib.c | 75 +++++++++++++ u2mfn/u2mfnlib.h | 24 +++++ vchan/Makefile | 39 +++++++ vchan/init.c | 224 +++++++++++++++++++++++++++++++++++++++ vchan/io.c | 159 +++++++++++++++++++++++++++ vchan/libvchan.h | 60 +++++++++++ vchan/node-select.c | 133 +++++++++++++++++++++++ vchan/node.c | 157 +++++++++++++++++++++++++++ 15 files changed, 972 insertions(+), 1 deletion(-) create mode 100644 u2mfn/Makefile create mode 100644 u2mfn/u2mfn-kernel.h create mode 100644 u2mfn/u2mfnlib.c create mode 100644 u2mfn/u2mfnlib.h create mode 100644 vchan/Makefile create mode 100644 vchan/init.c create mode 100644 vchan/io.c create mode 100644 vchan/libvchan.h create mode 100644 vchan/node-select.c create mode 100644 vchan/node.c diff --git a/Makefile b/Makefile index 5601dcd7..bab9705b 100644 --- a/Makefile +++ b/Makefile @@ -28,3 +28,4 @@ clean: (cd dom0/qmemman && make clean) (cd common && make clean) make -C qrexec clean + make -C vchan clean diff --git a/qrexec/Makefile b/qrexec/Makefile index 72089fda..b87b09d6 100644 --- a/qrexec/Makefile +++ b/qrexec/Makefile @@ -1,5 +1,5 @@ CC=gcc -CFLAGS+=-g -Wall +CFLAGS+=-g -Wall -I../vchan XENLIBS=-lvchan -lxenstore -lxenctrl all: qrexec_daemon qrexec_agent qrexec_client diff --git a/rpm_spec/core-appvm.spec b/rpm_spec/core-appvm.spec index 6dccb5fb..8ca5daee 100644 --- a/rpm_spec/core-appvm.spec +++ b/rpm_spec/core-appvm.spec @@ -61,6 +61,8 @@ fi make clean all make -C ../common make -C ../qrexec +make -C ../vchan +make -C ../u2mfn %install @@ -98,6 +100,14 @@ cp xorg-preload-apps.conf $RPM_BUILD_ROOT/etc/X11 mkdir -p $RPM_BUILD_ROOT/home_volatile/user chown 500:500 $RPM_BUILD_ROOT/home_volatile/user +install -D ../vchan/libvchan.h $RPM_BUILD_ROOT/usr/include/libvchan.h +install -D ../u2mfn/u2mfnlib.h $RPM_BUILD_ROOT/usr/include/u2mfnlib.h +install -D ../u2mfn/u2mfn-kernel.h $RPM_BUILD_ROOT/usr/include/u2mfn-kernel.h + +install -D ../vchan/libvchan.so $RPM_BUILD_ROOT/%{_libdir}/libvchan.so +install -D ../u2mfn/libu2mfn.so $RPM_BUILD_ROOT/%{_libdir}/libu2mfn.so + + %triggerin -- initscripts cp /var/lib/qubes/serial.conf /etc/init/serial.conf @@ -214,3 +224,19 @@ rm -rf $RPM_BUILD_ROOT %dir /home_volatile %attr(700,user,user) /home_volatile/user /etc/X11/xorg-preload-apps.conf +/usr/include/libvchan.h +%{_libdir}/libvchan.so +%{_libdir}/libu2mfn.so + + +%package devel +Summary: Include files for qubes core libraries +License: GPL v2 only +Group: Development/Sources + +%description devel + +%files devel +/usr/include/libvchan.h +/usr/include/u2mfnlib.h +/usr/include/u2mfn-kernel.h diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index 11182506..90163e28 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -50,6 +50,8 @@ python -O -m compileall qvm-core qmemman make -C restore make -C ../common make -C ../qrexec +make -C ../vchan +make -C ../u2mfn %install @@ -125,6 +127,9 @@ cp pm-utils/02qubes-pause-vms $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d/ mkdir -p $RPM_BUILD_ROOT/var/log/qubes mkdir -p $RPM_BUILD_ROOT/var/run/qubes +install -D ../vchan/libvchan.so $RPM_BUILD_ROOT/%{_libdir}/libvchan.so +install -D ../u2mfn/libu2mfn.so $RPM_BUILD_ROOT/%{_libdir}/libu2mfn.so + %post /usr/lib/qubes/qubes_fix_nm_conf.sh @@ -284,3 +289,5 @@ fi %attr(4750,root,qubes) /usr/lib/qubes/xenfreepages %attr(2770,root,qubes) %dir /var/log/qubes %attr(770,root,qubes) %dir /var/run/qubes +%{_libdir}/libvchan.so +%{_libdir}/libu2mfn.so diff --git a/rpm_spec/core-netvm.spec b/rpm_spec/core-netvm.spec index 678da3a9..6c88fcac 100644 --- a/rpm_spec/core-netvm.spec +++ b/rpm_spec/core-netvm.spec @@ -51,6 +51,8 @@ fi %build make -C ../qrexec +make -C ../vchan +make -C ../u2mfn %install @@ -78,6 +80,9 @@ cp ../common/serial.conf $RPM_BUILD_ROOT/var/lib/qubes/ mkdir -p $RPM_BUILD_ROOT/var/run/qubes mkdir -p $RPM_BUILD_ROOT/etc/xen/scripts cp ../common/vif-route-qubes $RPM_BUILD_ROOT/etc/xen/scripts +install -D ../vchan/libvchan.so $RPM_BUILD_ROOT/%{_libdir}/libvchan.so +install -D ../u2mfn/libu2mfn.so $RPM_BUILD_ROOT/%{_libdir}/libu2mfn.so + %triggerin -- initscripts cp /var/lib/qubes/serial.conf /etc/init/serial.conf @@ -186,3 +191,5 @@ rm -rf $RPM_BUILD_ROOT /sbin/qubes_serial_login /etc/xen/scripts/vif-route-qubes %dir /var/run/qubes +%{_libdir}/libvchan.so +%{_libdir}/libu2mfn.so diff --git a/u2mfn/Makefile b/u2mfn/Makefile new file mode 100644 index 00000000..9f08dcce --- /dev/null +++ b/u2mfn/Makefile @@ -0,0 +1,33 @@ +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# + +CC=gcc +CFLAGS=-g -Wall +all: libu2mfn.so + +libu2mfn.so : u2mfnlib.o + gcc -shared -o libu2mfn.so u2mfnlib.o +u2mfnlib.o: u2mfnlib.c + gcc -fPIC -Wall -g -c u2mfnlib.c +clean: + rm -f *.o *so *~ libu2mfn.so + + diff --git a/u2mfn/u2mfn-kernel.h b/u2mfn/u2mfn-kernel.h new file mode 100644 index 00000000..ee244bc1 --- /dev/null +++ b/u2mfn/u2mfn-kernel.h @@ -0,0 +1,26 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#include + +#define U2MFN_MAGIC 0xf5 // See ioctl-number.txt in kernel docs + +#define U2MFN_GET_MFN_FOR_PAGE _IOW (U2MFN_MAGIC, 1, int) +#define U2MFN_GET_LAST_MFN _IO (U2MFN_MAGIC, 2) diff --git a/u2mfn/u2mfnlib.c b/u2mfn/u2mfnlib.c new file mode 100644 index 00000000..998f47e9 --- /dev/null +++ b/u2mfn/u2mfnlib.c @@ -0,0 +1,75 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#include +#include +#include +#include +#include +#include +#include "u2mfn-kernel.h" + + +static int u2mfn_fd = -1; + +static int get_fd() +{ + if (u2mfn_fd == -1) { + u2mfn_fd = open("/proc/u2mfn", O_RDWR); + if (u2mfn_fd < 0) + return -1; + } + return 0; +} + +int u2mfn_get_mfn_for_page(long va, int *mfn) +{ + if (get_fd()) + return -1; + *mfn = ioctl(u2mfn_fd, U2MFN_GET_MFN_FOR_PAGE, va); + if (*mfn == -1) + return -1; + + return 0; +} + +int u2mfn_get_last_mfn(int *mfn) +{ + if (get_fd()) + return -1; + + *mfn = ioctl(u2mfn_fd, U2MFN_GET_LAST_MFN, 0); + if (*mfn == -1) + return -1; + + return 0; +} + + + +char *u2mfn_alloc_kpage() +{ + char *ret; + if (get_fd()) + return MAP_FAILED; + ret = + mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, u2mfn_fd, 0); + return ret; +} diff --git a/u2mfn/u2mfnlib.h b/u2mfn/u2mfnlib.h new file mode 100644 index 00000000..e64431af --- /dev/null +++ b/u2mfn/u2mfnlib.h @@ -0,0 +1,24 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +int u2mfn_get_mfn_for_page(long va, int *mfn) ; +int u2mfn_get_last_mfn(int *mfn) ; +char *u2mfn_alloc_kpage(void) ; diff --git a/vchan/Makefile b/vchan/Makefile new file mode 100644 index 00000000..ca7cc724 --- /dev/null +++ b/vchan/Makefile @@ -0,0 +1,39 @@ +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# + +CC=gcc +CFLAGS=-g -Wall -I../u2mfn +all: libvchan.so + +libvchan.so : init.o io.o + gcc -shared -o libvchan.so init.o io.o -L ../u2mfn -lu2mfn +init.o: init.c + gcc -fPIC -Wall -g -c init.c +io.o: io.c + gcc -fPIC -Wall -g -c io.c +node: node.o libvchan.so + gcc -g -o node node.o -L. -lvchan -lxenctrl -lxenstore +node-select: node-select.o libvchan.so + gcc -g -o node-select node-select.o -L. -lvchan -lxenctrl -lxenstore +clean: + rm -f *.o *so *~ client server node node-select + + diff --git a/vchan/init.c b/vchan/init.c new file mode 100644 index 00000000..4a3da4f2 --- /dev/null +++ b/vchan/init.c @@ -0,0 +1,224 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libvchan.h" +#include "../u2mfn/u2mfnlib.h" + +static int ring_init(struct libvchan *ctrl) +{ + int u2mfn = open("/proc/u2mfn", O_RDONLY); + int mfn; + struct vchan_interface *ring; + ring = (struct vchan_interface *) u2mfn_alloc_kpage (); + + if (ring == MAP_FAILED) + return -1; + + ctrl->ring = ring; + if (u2mfn_get_last_mfn (&mfn) < 0) + return -1; + + ctrl->ring_ref = mfn; + close(u2mfn); + ring->cons_in = ring->prod_in = ring->cons_out = ring->prod_out = + 0; + ring->server_closed = ring->client_closed = 0; + ring->debug = 0xaabbccdd; + return 0; +} +/** + creates event channel; + creates "ring-ref" and "event-channel" xenstore entries; + waits for connection to event channel from the peer +*/ +static int server_interface_init(struct libvchan *ctrl, int devno) +{ + int ret = -1; + struct xs_handle *xs; + char buf[64]; + char ref[16]; + int evfd; + evtchn_port_or_error_t port; + xs = xs_domain_open(); + if (!xs) { + return ret; + } + evfd = xc_evtchn_open(); + if (evfd < 0) + goto fail; + ctrl->evfd = evfd; + // the following hardcoded 0 is the peer domain id + port = xc_evtchn_bind_unbound_port(evfd, 0); + if (port < 0) + goto fail2; + ctrl->evport = port; + snprintf(ref, sizeof ref, "%d", ctrl->ring_ref); + snprintf(buf, sizeof buf, "device/vchan/%d/ring-ref", devno); + if (!xs_write(xs, 0, buf, ref, strlen(ref))) + goto fail2; + snprintf(ref, sizeof ref, "%d", ctrl->evport); + snprintf(buf, sizeof buf, "device/vchan/%d/event-channel", devno); + if (!xs_write(xs, 0, buf, ref, strlen(ref))) + goto fail2; + // wait for the peer to arrive + if (xc_evtchn_pending(evfd) == -1) + goto fail2; + xc_evtchn_unmask(ctrl->evfd, ctrl->evport); + snprintf(buf, sizeof buf, "device/vchan/%d", devno); + xs_rm(xs, 0, buf); + + ret = 0; + fail2: + if (ret) + close(evfd); + fail: + xs_daemon_close(xs); + return ret; +} + +#define dir_select(dir1, dir2) \ + ctrl->wr_cons = &ctrl->ring->cons_##dir1; \ + ctrl->wr_prod = &ctrl->ring->prod_##dir1; \ + ctrl->rd_cons = &ctrl->ring->cons_##dir2; \ + ctrl->rd_prod = &ctrl->ring->prod_##dir2; \ + ctrl->wr_ring = ctrl->ring->buf_##dir1; \ + ctrl->rd_ring = ctrl->ring->buf_##dir2; \ + ctrl->wr_ring_size = sizeof(ctrl->ring->buf_##dir1); \ + ctrl->rd_ring_size = sizeof(ctrl->ring->buf_##dir2) + +/** + Run in AppVM (any domain). + Sleeps until the connection is established. + \param devno something like a well-known port. + \returns NULL on failure, handle on success +*/ +struct libvchan *libvchan_server_init(int devno) +{ + struct libvchan *ctrl = + (struct libvchan *) malloc(sizeof(struct libvchan)); + if (!ctrl) + return 0; + if (ring_init(ctrl)) + return 0;; + if (server_interface_init(ctrl, devno)) + return 0; +/* + We want the same code for read/write functions, regardless whether + we are client, or server. Thus, we do not access buf_in nor buf_out + buffers directly. Instead, in *_init functions, the dir_select + macro assigns proper values to wr* and rd* pointers, so that they + point to correct one out of buf_in or buf_out related fields. +*/ + dir_select(in, out); + ctrl->is_server = 1; + return ctrl; +} + +/** + retrieves ring-ref and event-channel numbers from xenstore (if + they don't exist, return error, because nobody seems to listen); + map the ring, connect the event channel +*/ +static int client_interface_init(struct libvchan *ctrl, int domain, int devno) +{ + int ret = -1; + unsigned int len; + struct xs_handle *xs; + int xcfd; + char buf[64]; + char *ref; + int evfd; + int remote_port; + xs = xs_daemon_open(); + if (!xs) { + return ret; + } + snprintf(buf, sizeof buf, + "/local/domain/%d/device/vchan/%d/ring-ref", domain, + devno); + ref = xs_read(xs, 0, buf, &len); + if (!ref) + goto fail; + ctrl->ring_ref = atoi(ref); + if (!ctrl->ring_ref) + goto fail; + free(ref); + snprintf(buf, sizeof buf, + "/local/domain/%d/device/vchan/%d/event-channel", domain, + devno); + ref = xs_read(xs, 0, buf, &len); + if (!ref) + goto fail; + remote_port = atoi(ref); + if (!remote_port) + goto fail; + free(ref); + xcfd = xc_interface_open(); + if (xcfd < 0) + goto fail; + ctrl->ring = (struct vchan_interface *) + xc_map_foreign_range(xcfd, domain, 4096, + PROT_READ | PROT_WRITE, ctrl->ring_ref); + close(xcfd); + if (ctrl->ring == 0 || ctrl->ring == MAP_FAILED) + goto fail; + evfd = xc_evtchn_open(); + if (evfd < 0) + goto fail; + ctrl->evfd = evfd; + ctrl->evport = + xc_evtchn_bind_interdomain(evfd, domain, remote_port); + if (ctrl->evport < 0 || xc_evtchn_notify(evfd, ctrl->evport)) + close(evfd); + else + ret = 0; + fail: + xs_daemon_close(xs); + return ret; +} + +/** + Run on the client side of connection (currently, must be dom0). + \returns NULL on failure (e.g. noone listening), handle on success +*/ +struct libvchan *libvchan_client_init(int domain, int devno) +{ + struct libvchan *ctrl = + (struct libvchan *) malloc(sizeof(struct libvchan)); + if (!ctrl) + return 0; + if (client_interface_init(ctrl, domain, devno)) + return 0; +// See comment in libvchan_server_init + dir_select(out, in); + ctrl->is_server = 0; + return ctrl; +} diff --git a/vchan/io.c b/vchan/io.c new file mode 100644 index 00000000..7b524279 --- /dev/null +++ b/vchan/io.c @@ -0,0 +1,159 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "libvchan.h" +#include +#include +/** + \return How much data is immediately available for reading +*/ +int libvchan_data_ready(struct libvchan *ctrl) +{ + return *ctrl->rd_prod - *ctrl->rd_cons; +} + +/** + \return How much space is available for writing, without blocking +*/ +int libvchan_buffer_space(struct libvchan *ctrl) +{ + return ctrl->wr_ring_size - (*ctrl->wr_prod - *ctrl->wr_cons); +} + +static int do_notify(struct libvchan *ctrl) +{ + return xc_evtchn_notify(ctrl->evfd, ctrl->evport); +} + +/// returns nonzero if the peer has closed connection +int libvchan_is_eof(struct libvchan *ctrl) +{ + if (ctrl->is_server) { + if (ctrl->ring->client_closed) + return -1; + } else { + if (ctrl->ring->server_closed) { + ctrl->ring->client_closed = 1; + do_notify(ctrl); + return -1; + } + + } + return 0; +} + +/// waits for the peer to do any action +/** + \return -1 return value means peer has closed +*/ +int libvchan_wait(struct libvchan *ctrl) +{ + int ret; + ret = xc_evtchn_pending(ctrl->evfd); + if (ret!=-1 && xc_evtchn_unmask(ctrl->evfd, ctrl->evport)) + return -1; + if (ret!=-1 && libvchan_is_eof(ctrl)) + return -1; + return ret; +} + +/** + may sleep (only if no buffer space available); + may write less data than requested; + returns the amount of data processed, -1 on error or peer close +*/ +int libvchan_write(struct libvchan *ctrl, char *data, int size) +{ + int avail, avail_contig; + int real_idx; + while ((avail = libvchan_buffer_space(ctrl)) == 0) + if (libvchan_wait(ctrl) < 0) + return -1; + if (avail > size) + avail = size; + real_idx = (*ctrl->wr_prod) & (ctrl->wr_ring_size - 1); + avail_contig = ctrl->wr_ring_size - real_idx; + if (avail_contig < avail) + avail = avail_contig; + memcpy(ctrl->wr_ring + real_idx, data, avail); + *ctrl->wr_prod += avail; + if (do_notify(ctrl) < 0) + return -1; + return avail; +} + +/** + may sleep (only if no data is available for reading); + may return less data than requested; + returns the amount of data processed, -1 on error or peer close +*/ +int libvchan_read(struct libvchan *ctrl, char *data, int size) +{ + int avail, avail_contig; + int real_idx; + while ((avail = libvchan_data_ready(ctrl)) == 0) + if (libvchan_wait(ctrl) < 0) + return -1; + if (avail > size) + avail = size; + real_idx = (*ctrl->rd_cons) & (ctrl->rd_ring_size - 1); + avail_contig = ctrl->rd_ring_size - real_idx; + if (avail_contig < avail) + avail = avail_contig; + memcpy(data, ctrl->rd_ring + real_idx, avail); + *ctrl->rd_cons += avail; + if (do_notify(ctrl) < 0) + return -1; + return avail; +} + +/** + Wait fot the writes to finish, then notify the peer of closing + On server side, it waits for the peer to acknowledge +*/ +int libvchan_close(struct libvchan *ctrl) +{ + while (*ctrl->wr_prod != *ctrl->wr_cons) + if (libvchan_wait(ctrl) < 0) + return -1; + if (ctrl->is_server) { + ctrl->ring->server_closed = 1; + do_notify(ctrl); + while (!ctrl->ring->client_closed + && libvchan_wait(ctrl) == 0); + } else { + ctrl->ring->client_closed = 1; + do_notify(ctrl); + } + return 0; +} + +/// The fd to use for select() set +int libvchan_fd_for_select(struct libvchan *ctrl) +{ + return ctrl->evfd; +} + +/// Unmasks event channel; must be called before calling select(), and only then +void libvchan_prepare_to_select(struct libvchan *ctrl) +{ + xc_evtchn_unmask(ctrl->evfd, ctrl->evport); +} diff --git a/vchan/libvchan.h b/vchan/libvchan.h new file mode 100644 index 00000000..652284ba --- /dev/null +++ b/vchan/libvchan.h @@ -0,0 +1,60 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +typedef uint32_t VCHAN_RING_IDX; + +/// struct vchan_interface is placed in memory shared between domains +struct vchan_interface { + // One buffer for each data direction + char buf_in[1024]; + char buf_out[2048]; + // standard consumer/producer interface, one pair per buffer + VCHAN_RING_IDX cons_in, prod_in, cons_out, prod_out; + uint32_t debug; + int client_closed, server_closed; +}; +/// struct libvchan is a control structure, passed to all library calls +struct libvchan { + struct vchan_interface *ring; + uint32_t ring_ref; + /// descriptor to event channel interface + int evfd; + int evport; + VCHAN_RING_IDX *wr_cons, *wr_prod, *rd_cons, *rd_prod; + char *rd_ring, *wr_ring; + int rd_ring_size, wr_ring_size; + int is_server; +}; + +struct libvchan *libvchan_server_init(int devno); + +struct libvchan *libvchan_client_init(int domain, int devno); + +int libvchan_write(struct libvchan *ctrl, char *data, int size); +int libvchan_read(struct libvchan *ctrl, char *data, int size); +int libvchan_wait(struct libvchan *ctrl); +int libvchan_close(struct libvchan *ctrl); +void libvchan_prepare_to_select(struct libvchan *ctrl); +int libvchan_fd_for_select(struct libvchan *ctrl); +int libvchan_is_eof(struct libvchan *ctrl); +int libvchan_data_ready(struct libvchan *ctrl); +int libvchan_buffer_space(struct libvchan *ctrl); diff --git a/vchan/node-select.c b/vchan/node-select.c new file mode 100644 index 00000000..a4f1cdaf --- /dev/null +++ b/vchan/node-select.c @@ -0,0 +1,133 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "libvchan.h" +#include +#include +#include +#include +int libvchan_write_all(struct libvchan *ctrl, char *buf, int size) +{ + int written = 0; + int ret; + while (written < size) { + ret = libvchan_write(ctrl, buf + written, size - written); + if (ret <= 0) { + perror("write"); + exit(1); + } + written += ret; + } + return size; +} + +int write_all(int fd, char *buf, int size) +{ + int written = 0; + int ret; + while (written < size) { + ret = write(fd, buf + written, size - written); + if (ret <= 0) { + perror("write"); + exit(1); + } + written += ret; + } + return size; +} + + +void usage() +{ + fprintf(stderr, "usage:\n\tnode-select server nodeid\n" + "or\n" "\tnode-select client domainid nodeid\n"); + exit(1); +} + +#define BUFSIZE 5000 +char buf[BUFSIZE]; + +/** + Simple libvchan application, both client and server. + Both sides may write and read, both from the libvchan and from + stdin/stdout (just like netcat). More code is required to avoid + deadlock when both sides write, and noone reads. +*/ + +int main(int argc, char **argv) +{ + int ret; + int libvchan_fd; + struct libvchan *ctrl = 0; + if (argc < 3) + usage(); + if (!strcmp(argv[1], "server")) + ctrl = libvchan_server_init(atoi(argv[2])); + else if (!strcmp(argv[1], "client")) + ctrl = libvchan_client_init(atoi(argv[2]), atoi(argv[3])); + else + usage(); + if (!ctrl) { + perror("libvchan_*_init"); + exit(1); + } + + libvchan_fd = libvchan_fd_for_select(ctrl); + for (;;) { + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(0, &rfds); + FD_SET(libvchan_fd, &rfds); +// libvchan_prepare_to_select(ctrl); + ret = select(libvchan_fd + 1, &rfds, NULL, NULL, NULL); + if (ret < 0) { + perror("select"); + exit(1); + } + if (libvchan_is_eof(ctrl)) + exit(0); + if (FD_ISSET(libvchan_fd, &rfds)) +// we don't care about the result, but we need to do the read to +// clear libvchan_fd pendind state + libvchan_wait(ctrl); + while (libvchan_data_ready(ctrl) > 0) { + ret = libvchan_read(ctrl, buf, BUFSIZE); + if (ret < 0) + exit(0); + write_all(1, buf, ret); + } + if (FD_ISSET(0, &rfds)) { + ret = read(0, buf, BUFSIZE); + if (ret == 0) { + libvchan_close(ctrl); + exit(0); + } + if (ret < 0) { + perror("read 0"); + exit(1); + } +// libvchan_write_all can block; so if both sides write a lot, +// we can deadlock. Need higher level solution; would libvchan_write be ok ? + libvchan_write_all(ctrl, buf, ret); + } + + } +} diff --git a/vchan/node.c b/vchan/node.c new file mode 100644 index 00000000..d739fe7f --- /dev/null +++ b/vchan/node.c @@ -0,0 +1,157 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "libvchan.h" +#include +#include +#include +#include +#include +int libvchan_write_all(struct libvchan *ctrl, char *buf, int size) +{ + int written = 0; + int ret; + while (written < size) { + ret = libvchan_write(ctrl, buf + written, size - written); + if (ret <= 0) { + perror("write"); + exit(1); + } + written += ret; + } + return size; +} + +int write_all(int fd, char *buf, int size) +{ + int written = 0; + int ret; + while (written < size) { + ret = write(fd, buf + written, size - written); + if (ret <= 0) { + perror("write"); + exit(1); + } + written += ret; + } + return size; +} + +void usage() +{ + fprintf(stderr, "usage:\n\tnode server [read|write] nodeid\n" + "or\n" "\tnode client [read|write] domainid nodeid\n"); + exit(1); +} + +#define BUFSIZE 5000 +char buf[BUFSIZE]; +void reader(struct libvchan *ctrl) +{ + int size; + for (;;) { + size = rand() % (BUFSIZE - 1) + 1; + size = libvchan_read(ctrl, buf, size); + fprintf(stderr, "#"); + if (size < 0) { + perror("read vchan"); + libvchan_close(ctrl); + exit(1); + } + if (size == 0) + break; + size = write_all(1, buf, size); + if (size < 0) { + perror("stdout write"); + exit(1); + } + if (size == 0) { + perror("write size=0?\n"); + exit(1); + } + } +} + +void writer(struct libvchan *ctrl) +{ + int size; + for (;;) { + size = rand() % (BUFSIZE - 1) + 1; + size = read(0, buf, size); + if (size < 0) { + perror("read stdin"); + libvchan_close(ctrl); + exit(1); + } + if (size == 0) + break; + size = libvchan_write_all(ctrl, buf, size); + fprintf(stderr, "#"); + if (size < 0) { + perror("vchan write"); + exit(1); + } + if (size == 0) { + perror("write size=0?\n"); + exit(1); + } + } +} + + +/** + Simple libvchan application, both client and server. + One side does writing, the other side does reading; both from + standard input/output fds. +*/ +int main(int argc, char **argv) +{ + int seed = time(0); + struct libvchan *ctrl = 0; + int wr; + if (argc < 4) + usage(); + if (!strcmp(argv[2], "read")) + wr = 0; + else if (!strcmp(argv[2], "write")) + wr = 1; + else + usage(); + if (!strcmp(argv[1], "server")) + ctrl = libvchan_server_init(atoi(argv[3])); + else if (!strcmp(argv[1], "client")) + ctrl = libvchan_client_init(atoi(argv[3]), atoi(argv[4])); + else + usage(); + if (!ctrl) { + perror("libvchan_*_init"); + exit(1); + } + + srand(seed); + fprintf(stderr, "seed=%d\n", seed); + if (wr) + writer(ctrl); + else + reader(ctrl); + libvchan_close(ctrl); + return 0; +} From a7cc09071f95b98d30cba1c7225238450f1ecf06 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Tue, 8 Mar 2011 13:03:55 +0100 Subject: [PATCH 11/64] Make qubes_restore rexec-aware. --- dom0/qvm-tools/qvm-start | 15 +++++++----- dom0/restore/qubes_prepare_saved_domain.sh | 2 +- dom0/restore/qubes_restore.c | 27 +++++++++++++++++++++- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/dom0/qvm-tools/qvm-start b/dom0/qvm-tools/qvm-start index f8f155fd..8a3d277e 100755 --- a/dom0/qvm-tools/qvm-start +++ b/dom0/qvm-tools/qvm-start @@ -35,6 +35,8 @@ def main(): parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True) parser.add_option ("--no-guid", action="store_true", dest="noguid", default=False, help="Do not start the GUId") + parser.add_option ("--no-rexec", action="store_true", dest="norexec", default=False, + help="Do not start rexec") parser.add_option ("--console", action="store_true", dest="debug_console", default=False, help="Attach debugging console to the newly started VM") parser.add_option ("--dvm", action="store_true", dest="preparing_dvm", default=False, @@ -71,13 +73,14 @@ def main(): print "ERROR: Cannot start qubes_guid!" exit (1) - if options.verbose: - print "--> Starting Qubes rexec..." + if not options.norexec: + if options.verbose: + print "--> Starting Qubes rexec..." - retcode = subprocess.call ([qrexec_daemon_path, str(xid)]) - if (retcode != 0) : - print "ERROR: Cannot start qrexec_daemon!" - exit (1) + retcode = subprocess.call ([qrexec_daemon_path, str(xid)]) + if (retcode != 0) : + print "ERROR: Cannot start qrexec_daemon!" + exit (1) main() diff --git a/dom0/restore/qubes_prepare_saved_domain.sh b/dom0/restore/qubes_prepare_saved_domain.sh index 1d1bf0c3..f427f59d 100755 --- a/dom0/restore/qubes_prepare_saved_domain.sh +++ b/dom0/restore/qubes_prepare_saved_domain.sh @@ -21,7 +21,7 @@ if ! [ -d $VMDIR ] ; then echo $VMDIR does not exist ? exit 1 fi -if ! qvm-start $1 --no-guid --dvm ; then +if ! qvm-start $1 --no-guid --no-rexec --dvm ; then exit 1 fi diff --git a/dom0/restore/qubes_restore.c b/dom0/restore/qubes_restore.c index 3788fb66..bafa55c4 100644 --- a/dom0/restore/qubes_restore.c +++ b/dom0/restore/qubes_restore.c @@ -182,6 +182,29 @@ int xend_connect() return s; } +void start_rexec(int domid) +{ + int pid, status; + char dstr[40]; + snprintf(dstr, sizeof(dstr), "%d", domid); + switch (pid = fork()) { + case -1: + perror("fork"); + exit(1); + case 0: + execl("/usr/lib/qubes/qrexec_daemon", "qrexec_daemon", + dstr, NULL); + perror("execl"); + exit(1); + default:; + } + if (waitpid(pid, &status, 0) < 0) { + perror("waitpid"); + exit(1); + } +} + + void start_guid(int domid, int argc, char **argv) { int i; @@ -197,6 +220,7 @@ void start_guid(int domid, int argc, char **argv) execv("/usr/bin/qubes_guid", guid_args); perror("execv"); } + // modify the savefile. fd = fd to the open savefile, // buf - already read 1st page of the savefile // pattern - pattern to search for @@ -452,10 +476,11 @@ int main(int argc, char **argv) resp = recv_resp(fd); // printf("%s\n", resp); fprintf(stderr, "time=%s, creating xenstore entries\n", gettime()); -#endif +#endif setup_xenstore(netvm_id, domid, dispid, name); fprintf(stderr, "time=%s, starting qubes_guid\n", gettime()); rm_fast_flag(); + start_rexec(domid); start_guid(domid, argc, argv); return 0; } From 8f906236619fac16f891cc21764d1fb554bb2c97 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Thu, 10 Mar 2011 13:08:06 +0100 Subject: [PATCH 12/64] Add ability to execute command without help of /bin/su It is important, if the program closes stdout, but does not exit. Then, qrexec_agent does not see EOF (because su still holds the file descriptor). --- qrexec/qrexec_agent.c | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/qrexec/qrexec_agent.c b/qrexec/qrexec_agent.c index 1b6f7bad..8b1309c2 100644 --- a/qrexec/qrexec_agent.c +++ b/qrexec/qrexec_agent.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include "qrexec.h" #include "buffer.h" #include "glue.h" @@ -66,18 +68,46 @@ void init() peer_server_init(REXEC_PORT); } +void no_colon_in_cmd() +{ + fprintf(stderr, + "cmdline is supposed to be in user:command form\n"); + exit(1); +} + +void do_exec_directly(char *cmd) +{ + struct passwd *pwd; + char *sep = index(cmd, ':'); + if (!sep) + no_colon_in_cmd(); + *sep = 0; + pwd = getpwnam(cmd); + if (!pwd) { + perror("getpwnam"); + exit(1); + } + setgid(pwd->pw_gid); + initgroups(cmd, pwd->pw_gid); + setuid(pwd->pw_uid); + setenv("HOME", pwd->pw_dir, 1); + setenv("USER", cmd, 1); + execl(sep + 1, sep + 1, NULL); + perror("execl"); + exit(1); +} + void do_exec(char *cmd) { char *sep = index(cmd, ':'); - if (!sep) { - fprintf(stderr, - "cmdline is supposed to be in user:command form\n"); - exit(1); - } + if (!sep) + no_colon_in_cmd(); *sep = 0; signal(SIGCHLD, SIG_DFL); signal(SIGPIPE, SIG_DFL); + if (!strcmp(cmd, "directly")) + do_exec_directly(sep + 1); execl("/bin/su", "su", "-", cmd, "-c", sep + 1, NULL); perror("execl"); exit(1); @@ -89,7 +119,7 @@ void handle_just_exec(int clid, int len) int fdn, pid; read_all_vchan_ext(buf, len); - switch (pid=fork()) { + switch (pid = fork()) { case -1: perror("fork"); exit(1); From f1a7df6e9568909f15d17ddf562d0d18df5d1962 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Thu, 10 Mar 2011 15:41:31 +0100 Subject: [PATCH 13/64] Implemented mechanism to trigger predefined execution in dom0. Processes in AppVM can ask qrexec-agent to send a MSG_AGENT_TO_SERVER_TRIGGER_EXEC message to qrexec-daemon. The latter will execute predefined program. It is useful for the purpose of file copy; the predefined program will create a connected qfile-daemon<->qfile-agent pair. --- qrexec/qrexec.h | 8 +++++++ qrexec/qrexec_agent.c | 38 +++++++++++++++++++++++++++++++ qrexec/qrexec_daemon.c | 48 ++++++++++++++++++++++++++++++++++++++-- rpm_spec/core-appvm.spec | 2 ++ rpm_spec/core-netvm.spec | 2 ++ 5 files changed, 96 insertions(+), 2 deletions(-) diff --git a/qrexec/qrexec.h b/qrexec/qrexec.h index 729ff932..ec29ed51 100644 --- a/qrexec/qrexec.h +++ b/qrexec/qrexec.h @@ -25,6 +25,8 @@ #define REXEC_PORT 512 +#define QREXEC_AGENT_TRIGGER_PATH "/var/run/qubes/qrexec_agent" + enum { MSG_CLIENT_TO_SERVER_EXEC_CMDLINE = 0x100, MSG_CLIENT_TO_SERVER_JUST_EXEC, @@ -40,12 +42,18 @@ enum { MSG_AGENT_TO_SERVER_STDOUT, MSG_AGENT_TO_SERVER_STDERR, MSG_AGENT_TO_SERVER_EXIT_CODE, + MSG_AGENT_TO_SERVER_TRIGGER_EXEC, MSG_SERVER_TO_CLIENT_STDOUT, MSG_SERVER_TO_CLIENT_STDERR, MSG_SERVER_TO_CLIENT_EXIT_CODE }; +enum { + QREXEC_EXECUTE_FILE_COPY=0x700, + QREXEC_EXECUTE_FILE_COPY_FOR_DISPVM +}; + struct server_header { unsigned int type; unsigned int clid; diff --git a/qrexec/qrexec_agent.c b/qrexec/qrexec_agent.c index 8b1309c2..ef02ddb1 100644 --- a/qrexec/qrexec_agent.c +++ b/qrexec/qrexec_agent.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "qrexec.h" #include "buffer.h" #include "glue.h" @@ -63,9 +64,16 @@ struct _process_fd process_fd[MAX_FDS]; /* indexed by client id, which is descriptor number of a client in daemon */ struct _client_info client_info[MAX_FDS]; +int trigger_fd; + void init() { peer_server_init(REXEC_PORT); + umask(0); + mkfifo(QREXEC_AGENT_TRIGGER_PATH, 0666); + umask(077); + trigger_fd = + open(QREXEC_AGENT_TRIGGER_PATH, O_RDONLY | O_NONBLOCK); } void no_colon_in_cmd() @@ -418,6 +426,11 @@ int fill_fds_for_select(fd_set * rdset, fd_set * wrset) FD_SET(i, rdset); max = i; } + + FD_SET(trigger_fd, rdset); + if (trigger_fd > max) + max = trigger_fd; + for (i = 0; i < MAX_FDS; i++) if (client_info[i].pid > 0 && client_info[i].is_blocked) { fd = client_info[i].stdin_fd; @@ -446,6 +459,28 @@ void flush_client_data_agent(int clid) } } +void handle_trigger_io() +{ + struct server_header s_hdr; + char buf[5]; + + s_hdr.clid = 0; + s_hdr.len = 0; + if (read(trigger_fd, buf, 4) == 4) { + buf[4] = 0; + if (!strcmp(buf, "FCPR")) + s_hdr.clid = QREXEC_EXECUTE_FILE_COPY; + else if (!strcmp(buf, "DVMR")) + s_hdr.clid = QREXEC_EXECUTE_FILE_COPY_FOR_DISPVM; + if (s_hdr.clid) { + s_hdr.type = MSG_AGENT_TO_SERVER_TRIGGER_EXEC; + write_all_vchan_ext(&s_hdr, sizeof s_hdr); + } + } + close(trigger_fd); + trigger_fd = + open(QREXEC_AGENT_TRIGGER_PATH, O_RDONLY | O_NONBLOCK); +} int main() { @@ -469,6 +504,9 @@ int main() while (read_ready_vchan_ext()) handle_server_data(); + if (FD_ISSET(trigger_fd, &rdset)) + handle_trigger_io(); + handle_process_data_all(&rdset); for (i = 0; i <= MAX_FDS; i++) if (client_info[i].pid > 0 diff --git a/qrexec/qrexec_daemon.c b/qrexec/qrexec_daemon.c index 5ceb4ab9..9f801983 100644 --- a/qrexec/qrexec_daemon.c +++ b/qrexec/qrexec_daemon.c @@ -51,9 +51,11 @@ int server_fd; void handle_usr1(int x) { - exit(0); + exit(0); } +char domain_id[64]; + void init(int xid) { char dbg_log[256]; @@ -63,6 +65,7 @@ void init(int xid) fprintf(stderr, "domain id=0?\n"); exit(1); } + snprintf(domain_id, sizeof(domain_id), "%d", xid); signal(SIGUSR1, handle_usr1); switch (fork()) { case -1: @@ -71,7 +74,7 @@ void init(int xid) case 0: break; default: - pause(); + pause(); exit(0); } close(0); @@ -94,6 +97,7 @@ void init(int xid) peer_client_init(xid, REXEC_PORT); setuid(getuid()); signal(SIGPIPE, SIG_IGN); + signal(SIGCHLD, SIG_IGN); signal(SIGUSR1, SIG_DFL); kill(getppid(), SIGUSR1); } @@ -242,6 +246,41 @@ void pass_to_client(int clid, struct client_header *hdr) } } +void handle_trigger_exec(int req) +{ + char *rcmd = NULL, *lcmd = NULL; + int i; + switch (req) { + case QREXEC_EXECUTE_FILE_COPY: + rcmd = "directly:user:/usr/lib/qubes/qfile-agent"; + lcmd = "/usr/lib/qubes/qfile-daemon"; + break; + case QREXEC_EXECUTE_FILE_COPY_FOR_DISPVM: + rcmd = "directly:user:/usr/lib/qubes/qfile-agent-dvm"; + lcmd = "/usr/lib/qubes/qfile-daemon-dvm"; + break; + default: + fprintf(stderr, "got trigger exec no %d\n", req); + exit(1); + } + switch (fork()) { + case -1: + perror("fork"); + exit(1); + case 0: + break; + default: + return; + } + for (i = 3; i < 256; i++) + close(i); + signal(SIGCHLD, SIG_DFL); + execl("/usr/lib/qubes/qrexec_client", "qrexec_client", "-d", + domain_id, "-l", lcmd, rcmd, NULL); + perror("execl"); + exit(1); +} + void handle_agent_data() { struct client_header hdr; @@ -251,6 +290,11 @@ void handle_agent_data() // fprintf(stderr, "got %x %x %x\n", s_hdr.type, s_hdr.clid, // s_hdr.len); + if (s_hdr.type == MSG_AGENT_TO_SERVER_TRIGGER_EXEC) { + handle_trigger_exec(s_hdr.clid); + return; + } + if (s_hdr.clid >= MAX_FDS || s_hdr.clid < 0) { fprintf(stderr, "from agent: clid=%d\n", s_hdr.clid); exit(1); diff --git a/rpm_spec/core-appvm.spec b/rpm_spec/core-appvm.spec index 8ca5daee..5d3038f3 100644 --- a/rpm_spec/core-appvm.spec +++ b/rpm_spec/core-appvm.spec @@ -107,6 +107,7 @@ install -D ../u2mfn/u2mfn-kernel.h $RPM_BUILD_ROOT/usr/include/u2mfn-kernel.h install -D ../vchan/libvchan.so $RPM_BUILD_ROOT/%{_libdir}/libvchan.so install -D ../u2mfn/libu2mfn.so $RPM_BUILD_ROOT/%{_libdir}/libu2mfn.so +mkdir -p $RPM_BUILD_ROOT/var/run/qubes %triggerin -- initscripts cp /var/lib/qubes/serial.conf /etc/init/serial.conf @@ -227,6 +228,7 @@ rm -rf $RPM_BUILD_ROOT /usr/include/libvchan.h %{_libdir}/libvchan.so %{_libdir}/libu2mfn.so +%dir /var/run/qubes %package devel diff --git a/rpm_spec/core-netvm.spec b/rpm_spec/core-netvm.spec index 6c88fcac..84df5cbe 100644 --- a/rpm_spec/core-netvm.spec +++ b/rpm_spec/core-netvm.spec @@ -83,6 +83,7 @@ cp ../common/vif-route-qubes $RPM_BUILD_ROOT/etc/xen/scripts install -D ../vchan/libvchan.so $RPM_BUILD_ROOT/%{_libdir}/libvchan.so install -D ../u2mfn/libu2mfn.so $RPM_BUILD_ROOT/%{_libdir}/libu2mfn.so +mkdir -p $RPM_BUILD_ROOT/var/run/qubes %triggerin -- initscripts cp /var/lib/qubes/serial.conf /etc/init/serial.conf @@ -193,3 +194,4 @@ rm -rf $RPM_BUILD_ROOT %dir /var/run/qubes %{_libdir}/libvchan.so %{_libdir}/libu2mfn.so +%dir /var/run/qubes From c2214e854c269e2f27a71f29b70ef84af8c8eff2 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Thu, 10 Mar 2011 16:50:40 +0100 Subject: [PATCH 14/64] Added dvm_file_editor. It works with qrexec - reads/writes data from stdin/stdout. --- appvm/Makefile | 6 +- appvm/dvm2.h | 1 + appvm/dvm_file_editor.c | 126 +++++++++++++++++++++++++++++++++++++++ rpm_spec/core-appvm.spec | 2 + 4 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 appvm/dvm2.h create mode 100644 appvm/dvm_file_editor.c diff --git a/appvm/Makefile b/appvm/Makefile index 6bb1dea6..e247ce9b 100644 --- a/appvm/Makefile +++ b/appvm/Makefile @@ -1,6 +1,8 @@ CC=gcc CFLAGS=-Wall -all: qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm +all: qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm dvm_file_editor +dvm_file_editor: dvm_file_editor.o + $(CC) -o dvm_file_editor dvm_file_editor.o qubes_penctl: qubes_penctl.o $(CC) -o qubes_penctl qubes_penctl.o -lxenstore qubes_add_pendrive_script: qubes_add_pendrive_script.o @@ -8,4 +10,4 @@ qubes_add_pendrive_script: qubes_add_pendrive_script.o qvm-open-in-dvm: qvm-open-in-dvm.o $(CC) -o qvm-open-in-dvm qvm-open-in-dvm.o -lxenstore clean: - rm -f qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm *.o *~ + rm -f dvm_file_editor qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm *.o *~ diff --git a/appvm/dvm2.h b/appvm/dvm2.h new file mode 100644 index 00000000..837999c2 --- /dev/null +++ b/appvm/dvm2.h @@ -0,0 +1 @@ +#define DVM_FILENAME_SIZE 256 diff --git a/appvm/dvm_file_editor.c b/appvm/dvm_file_editor.c new file mode 100644 index 00000000..cadb4509 --- /dev/null +++ b/appvm/dvm_file_editor.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include +#include "dvm2.h" + +int write_all(int fd, void *buf, int size) +{ + int written = 0; + int ret; + while (written < size) { + ret = write(fd, (char *) buf + written, size - written); + if (ret <= 0) { + perror("write"); + return 0; + } + written += ret; + } +// fprintf(stderr, "sent %d bytes\n", size); + return 1; +} + +int read_all(int fd, void *buf, int size) +{ + int got_read = 0; + int ret; + while (got_read < size) { + ret = read(fd, (char *) buf + got_read, size - got_read); + if (ret == 0) { + fprintf(stderr, "EOF\n"); + return 0; + } + if (ret < 0) { + perror("read"); + return 0; + } + got_read += ret; + } +// fprintf(stderr, "read %d bytes\n", size); + return 1; +} + +char *get_filename() +{ + char buf[DVM_FILENAME_SIZE]; + static char retname[sizeof(buf) + sizeof("/tmp/")]; + if (!read_all(0, buf, sizeof(buf))) + exit(1); + if (index(buf, '/')) { + fprintf(stderr, "filename contains /"); + exit(1); + } + snprintf(retname, sizeof(retname), "/tmp/%s", buf); + return retname; +} + +void copy_all(int fdout, int fdin) +{ + int ret; + char buf[4096]; + for (;;) { + ret = read(fdin, buf, sizeof(buf)); + if (!ret) + break; + if (ret < 0) { + perror("read"); + exit(1); + } + if (!write_all(fdout, buf, ret)) { + perror("write"); + exit(1); + } + } +} + + +void copy_file(char *filename) +{ + int fd = open(filename, O_WRONLY | O_CREAT, 0600); + if (fd < 0) { + perror("open file"); + exit(1); + } + copy_all(fd, 0); + close(fd); +} + +void send_file_back(char * filename) +{ + int fd = open(filename, O_RDONLY); + if (fd < 0) { + perror("open file"); + exit(1); + } + copy_all(1, fd); + close(fd); +} + +int +main() +{ + char cmdbuf[512]; + struct stat stat_pre, stat_post; + char *filename = get_filename(); + + copy_file(filename); + if (stat(filename, &stat_pre)) { + perror("stat pre"); + exit(1); + } + snprintf(cmdbuf, sizeof(cmdbuf), + "HOME=/home/user DISPLAY=:0 /usr/bin/mimeopen -n -M '%s' 2>&1 > /tmp/kde-open.log Date: Fri, 11 Mar 2011 11:34:07 +0100 Subject: [PATCH 15/64] Added DVM_SPOOL definition to dvm2.h --- appvm/dvm2.h | 1 + 1 file changed, 1 insertion(+) diff --git a/appvm/dvm2.h b/appvm/dvm2.h index 837999c2..0e5922cd 100644 --- a/appvm/dvm2.h +++ b/appvm/dvm2.h @@ -1 +1,2 @@ #define DVM_FILENAME_SIZE 256 +#define DVM_SPOOL "/home/user/.dvmspool" From e19390ca1cefc49247b8a25945c8acc14f8af3bc Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Fri, 11 Mar 2011 11:47:20 +0100 Subject: [PATCH 16/64] Moved ioall.c file to "common" --- {qrexec => common}/ioall.c | 6 ------ common/ioall.h | 2 ++ qrexec/Makefile | 15 ++++++++------- qrexec/glue.h | 3 --- qrexec/qrexec_agent.c | 6 ++++++ qrexec/qrexec_client.c | 1 + qrexec/qrexec_daemon.c | 7 +++++++ 7 files changed, 24 insertions(+), 16 deletions(-) rename {qrexec => common}/ioall.c (93%) create mode 100644 common/ioall.h diff --git a/qrexec/ioall.c b/common/ioall.c similarity index 93% rename from qrexec/ioall.c rename to common/ioall.c index 9e8d7a3c..ce550c7e 100644 --- a/qrexec/ioall.c +++ b/common/ioall.c @@ -59,9 +59,3 @@ int read_all(int fd, void *buf, int size) // fprintf(stderr, "read %d bytes\n", size); return 1; } - -void set_nonblock(int fd) -{ - int fl = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, fl | O_NONBLOCK); -} diff --git a/common/ioall.h b/common/ioall.h new file mode 100644 index 00000000..1e76353e --- /dev/null +++ b/common/ioall.h @@ -0,0 +1,2 @@ +int write_all(int fd, void *buf, int size); +int read_all(int fd, void *buf, int size); diff --git a/qrexec/Makefile b/qrexec/Makefile index b87b09d6..d4a5c857 100644 --- a/qrexec/Makefile +++ b/qrexec/Makefile @@ -1,13 +1,14 @@ CC=gcc -CFLAGS+=-g -Wall -I../vchan +CFLAGS+=-g -Wall -I../vchan -I../common XENLIBS=-lvchan -lxenstore -lxenctrl +COMMONIOALL=../common/ioall.o all: qrexec_daemon qrexec_agent qrexec_client -qrexec_daemon: qrexec_daemon.o unix_server.o ioall.o txrx-vchan.o buffer.o write_stdin.o - $(CC) -g -o qrexec_daemon qrexec_daemon.o unix_server.o ioall.o txrx-vchan.o write_stdin.o buffer.o $(XENLIBS) -qrexec_agent: qrexec_agent.o ioall.o exec.o txrx-vchan.o write_stdin.o buffer.o - $(CC) -g -o qrexec_agent qrexec_agent.o ioall.o exec.o txrx-vchan.o write_stdin.o buffer.o $(XENLIBS) -qrexec_client: qrexec_client.o ioall.o exec.o - $(CC) -g -o qrexec_client qrexec_client.o ioall.o exec.o +qrexec_daemon: qrexec_daemon.o unix_server.o $(COMMONIOALL) txrx-vchan.o buffer.o write_stdin.o + $(CC) -g -o qrexec_daemon qrexec_daemon.o unix_server.o $(COMMONIOALL) txrx-vchan.o write_stdin.o buffer.o $(XENLIBS) +qrexec_agent: qrexec_agent.o exec.o txrx-vchan.o write_stdin.o buffer.o + $(CC) -g -o qrexec_agent qrexec_agent.o exec.o txrx-vchan.o write_stdin.o buffer.o $(XENLIBS) +qrexec_client: qrexec_client.o $(COMMONIOALL) exec.o + $(CC) -g -o qrexec_client qrexec_client.o $(COMMONIOALL) exec.o clean: rm -f *.o *~ qrexec_daemon qrexec_agent qrexec_client diff --git a/qrexec/glue.h b/qrexec/glue.h index 228b6866..abcad649 100644 --- a/qrexec/glue.h +++ b/qrexec/glue.h @@ -23,8 +23,6 @@ void do_fork_exec(char *cmdline, int *pid, int *stdin_fd, int *stdout_fd, int *stderr_fd); -int write_all(int fd, void *buf, int size); -int read_all(int fd, void *buf, int size); int peer_server_init(int port); char *peer_client_init(int dom, int port); void wait_for_vchan_or_argfd(int max, fd_set * rdset, fd_set * wrset); @@ -36,7 +34,6 @@ void fix_fds(int fdin, int fdout, int fderr); int get_server_socket(int domid); int do_accept(int s); -void set_nonblock(int fd); enum { WRITE_STDIN_OK = 0x200, diff --git a/qrexec/qrexec_agent.c b/qrexec/qrexec_agent.c index ef02ddb1..94cab149 100644 --- a/qrexec/qrexec_agent.c +++ b/qrexec/qrexec_agent.c @@ -142,6 +142,12 @@ void handle_just_exec(int clid, int len) fprintf(stderr, "executed (nowait) %s pid %d\n", buf, pid); } +void set_nonblock(int fd) +{ + int fl = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, fl | O_NONBLOCK); +} + void handle_exec(int clid, int len) { char buf[len]; diff --git a/qrexec/qrexec_client.c b/qrexec/qrexec_client.c index e2878480..38271cf8 100644 --- a/qrexec/qrexec_client.c +++ b/qrexec/qrexec_client.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "qrexec.h" #include "buffer.h" #include "glue.h" diff --git a/qrexec/qrexec_daemon.c b/qrexec/qrexec_daemon.c index 9f801983..54077185 100644 --- a/qrexec/qrexec_daemon.c +++ b/qrexec/qrexec_daemon.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "qrexec.h" #include "buffer.h" #include "glue.h" @@ -145,6 +146,12 @@ void pass_to_agent(int fd, struct server_header *s_hdr) write_all_vchan_ext(buf, len); } +void set_nonblock(int fd) +{ + int fl = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, fl | O_NONBLOCK); +} + void handle_client_cmdline(int fd) { struct client_header hdr; From 19943f093c16f1a5076259abb8854cdf67c62420 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Fri, 11 Mar 2011 11:50:52 +0100 Subject: [PATCH 17/64] Make dvm_file_editor use ioall.c --- appvm/Makefile | 6 +++--- appvm/dvm_file_editor.c | 37 +------------------------------------ 2 files changed, 4 insertions(+), 39 deletions(-) diff --git a/appvm/Makefile b/appvm/Makefile index e247ce9b..be08e07b 100644 --- a/appvm/Makefile +++ b/appvm/Makefile @@ -1,8 +1,8 @@ CC=gcc -CFLAGS=-Wall +CFLAGS=-Wall -I../common all: qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm dvm_file_editor -dvm_file_editor: dvm_file_editor.o - $(CC) -o dvm_file_editor dvm_file_editor.o +dvm_file_editor: dvm_file_editor.o ../common/ioall.o + $(CC) -o dvm_file_editor dvm_file_editor.o ../common/ioall.o qubes_penctl: qubes_penctl.o $(CC) -o qubes_penctl qubes_penctl.o -lxenstore qubes_add_pendrive_script: qubes_add_pendrive_script.o diff --git a/appvm/dvm_file_editor.c b/appvm/dvm_file_editor.c index cadb4509..fd60ca40 100644 --- a/appvm/dvm_file_editor.c +++ b/appvm/dvm_file_editor.c @@ -4,44 +4,9 @@ #include #include #include +#include #include "dvm2.h" -int write_all(int fd, void *buf, int size) -{ - int written = 0; - int ret; - while (written < size) { - ret = write(fd, (char *) buf + written, size - written); - if (ret <= 0) { - perror("write"); - return 0; - } - written += ret; - } -// fprintf(stderr, "sent %d bytes\n", size); - return 1; -} - -int read_all(int fd, void *buf, int size) -{ - int got_read = 0; - int ret; - while (got_read < size) { - ret = read(fd, (char *) buf + got_read, size - got_read); - if (ret == 0) { - fprintf(stderr, "EOF\n"); - return 0; - } - if (ret < 0) { - perror("read"); - return 0; - } - got_read += ret; - } -// fprintf(stderr, "read %d bytes\n", size); - return 1; -} - char *get_filename() { char buf[DVM_FILENAME_SIZE]; From 64bce77ef7840a2b485598f03b2e60155df6440b Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Fri, 11 Mar 2011 11:54:39 +0100 Subject: [PATCH 18/64] Changed copy_all signature. --- appvm/dvm_file_editor.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/appvm/dvm_file_editor.c b/appvm/dvm_file_editor.c index fd60ca40..3cc8818f 100644 --- a/appvm/dvm_file_editor.c +++ b/appvm/dvm_file_editor.c @@ -21,7 +21,7 @@ char *get_filename() return retname; } -void copy_all(int fdout, int fdin) +int copy_fd_all(int fdout, int fdin) { int ret; char buf[4096]; @@ -31,13 +31,14 @@ void copy_all(int fdout, int fdin) break; if (ret < 0) { perror("read"); - exit(1); + return 0; } if (!write_all(fdout, buf, ret)) { perror("write"); - exit(1); + return 0; } } + return 1; } @@ -48,7 +49,8 @@ void copy_file(char *filename) perror("open file"); exit(1); } - copy_all(fd, 0); + if (!copy_fd_all(fd, 0)) + exit(1); close(fd); } @@ -59,7 +61,8 @@ void send_file_back(char * filename) perror("open file"); exit(1); } - copy_all(1, fd); + if (!copy_fd_all(1, fd)) + exit(1); close(fd); } From bd89fa06311c603451ca31f8c2554225b387d8be Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Fri, 11 Mar 2011 11:57:16 +0100 Subject: [PATCH 19/64] Move copy_all_fd from dvm_file_editor.c to ioall.c It is useful in e.g. qfile-agent-dvm. --- appvm/dvm_file_editor.c | 21 --------------------- common/ioall.c | 21 +++++++++++++++++++++ common/ioall.h | 1 + 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/appvm/dvm_file_editor.c b/appvm/dvm_file_editor.c index 3cc8818f..4eed30ba 100644 --- a/appvm/dvm_file_editor.c +++ b/appvm/dvm_file_editor.c @@ -21,27 +21,6 @@ char *get_filename() return retname; } -int copy_fd_all(int fdout, int fdin) -{ - int ret; - char buf[4096]; - for (;;) { - ret = read(fdin, buf, sizeof(buf)); - if (!ret) - break; - if (ret < 0) { - perror("read"); - return 0; - } - if (!write_all(fdout, buf, ret)) { - perror("write"); - return 0; - } - } - return 1; -} - - void copy_file(char *filename) { int fd = open(filename, O_WRONLY | O_CREAT, 0600); diff --git a/common/ioall.c b/common/ioall.c index ce550c7e..1fca6f12 100644 --- a/common/ioall.c +++ b/common/ioall.c @@ -59,3 +59,24 @@ int read_all(int fd, void *buf, int size) // fprintf(stderr, "read %d bytes\n", size); return 1; } + +int copy_fd_all(int fdout, int fdin) +{ + int ret; + char buf[4096]; + for (;;) { + ret = read(fdin, buf, sizeof(buf)); + if (!ret) + break; + if (ret < 0) { + perror("read"); + return 0; + } + if (!write_all(fdout, buf, ret)) { + perror("write"); + return 0; + } + } + return 1; +} + diff --git a/common/ioall.h b/common/ioall.h index 1e76353e..1a700c6c 100644 --- a/common/ioall.h +++ b/common/ioall.h @@ -1,2 +1,3 @@ int write_all(int fd, void *buf, int size); int read_all(int fd, void *buf, int size); +int copy_fd_all(int fdout, int fdin); From 04da9b62a70eb9af7f3f6f8a4805761ef2159a19 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Fri, 11 Mar 2011 12:44:19 +0100 Subject: [PATCH 20/64] Added qfile-agent-dvm.c Nations, rejoice. --- appvm/Makefile | 4 +- appvm/qfile-agent-dvm.c | 156 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 appvm/qfile-agent-dvm.c diff --git a/appvm/Makefile b/appvm/Makefile index be08e07b..d6bfb6a2 100644 --- a/appvm/Makefile +++ b/appvm/Makefile @@ -1,8 +1,10 @@ CC=gcc CFLAGS=-Wall -I../common -all: qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm dvm_file_editor +all: qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm dvm_file_editor qfile-agent-dvm dvm_file_editor: dvm_file_editor.o ../common/ioall.o $(CC) -o dvm_file_editor dvm_file_editor.o ../common/ioall.o +qfile-agent-dvm: qfile-agent-dvm.o ../common/ioall.o + $(CC) -o qfile-agent-dvm qfile-agent-dvm.o ../common/ioall.o qubes_penctl: qubes_penctl.o $(CC) -o qubes_penctl qubes_penctl.o -lxenstore qubes_add_pendrive_script: qubes_add_pendrive_script.o diff --git a/appvm/qfile-agent-dvm.c b/appvm/qfile-agent-dvm.c new file mode 100644 index 00000000..194d67cb --- /dev/null +++ b/appvm/qfile-agent-dvm.c @@ -0,0 +1,156 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dvm2.h" + +//the below usage of "system" is smelly, but should be fine - input comes from the same vm +void fatal(char *msg) +{ + char *errcmd; + asprintf(&errcmd, "DISPLAY=:0 kdialog --sorry 'qfile-agent-dvm: %s'", msg); + system(errcmd); + perror(msg); + exit(1); +} + +void nonfatal(char *msg) +{ + char *errcmd; + asprintf(&errcmd, "DISPLAY=:0 kdialog --sorry 'qfile-agent-dvm: %s'", msg); + system(errcmd); + fprintf(stderr, "%s", msg); +} + +void send_file(char *fname) +{ + char *base; + int fd = open(fname, O_RDONLY); + if (fd < 0) + fatal("open file_to_be_edited"); + base = rindex(fname, '/'); + if (!base) + base = fname; + else + base++; + if (strlen(base) >= DVM_FILENAME_SIZE) + base += strlen(base) - DVM_FILENAME_SIZE + 1; + if (!write_all(1, base, DVM_FILENAME_SIZE)) + fatal("send filename"); + if (!copy_fd_all(1, fd)) + fatal("send file"); + close(1); +} + +int copy_and_return_nonemptiness(int tmpfd) +{ + struct stat st; + if (!copy_fd_all(tmpfd, 0)) + fatal("receiving file"); + if (fstat(tmpfd, &st)) + fatal("fstat"); + close(tmpfd); + + return st.st_size; +} + +void recv_file_nowrite(char *fname) +{ + char *tempfile; + char *errmsg; + int tmpfd; + + asprintf(&tempfile, "/tmp/file_edited_in_dvm.XXXXXX"); + tmpfd = mkstemp(tempfile); + if (tmpfd < 0) + fatal("unable to create any temporary file, aborting"); + if (!copy_and_return_nonemptiness(tmpfd)) { + unlink(tempfile); + return; + } + asprintf(&errmsg, + "The file %s has been edited in Disposable VM and the modified content has been received, " + "but this file is in nonwritable directory and thus cannot be modified safely. The edited file has been " + "saved to %s", fname, tempfile); + nonfatal(errmsg); +} + +void actually_recv_file(char *fname, char *tempfile, int tmpfd) +{ + if (!copy_and_return_nonemptiness(tmpfd)) { + unlink(tempfile); + return; + } + if (rename(tempfile, fname)) + fatal("rename"); +} + +void recv_file(char *fname) +{ + int tmpfd; + char *tempfile; + asprintf(&tempfile, "%s.XXXXXX", fname); + tmpfd = mkstemp(tempfile); + if (tmpfd < 0) + recv_file_nowrite(fname); + else + actually_recv_file(fname, tempfile, tmpfd); +} + +void talk_to_daemon(char *fname) +{ + send_file(fname); + recv_file(fname); +} + +void process_spoolentry(char *entry_name) +{ + char *abs_spool_entry_name; + int entry_fd; + struct stat st; + char *filename; + int entry_size; + asprintf(&abs_spool_entry_name, "%s/%s", DVM_SPOOL, entry_name); + entry_fd = open(abs_spool_entry_name, O_RDONLY); + unlink(abs_spool_entry_name); + if (entry_fd < 0 || fstat(entry_fd, &st)) + fatal("bad dvm_entry"); + entry_size = st.st_size; + filename = calloc(1, entry_size + DVM_FILENAME_SIZE); + if (!filename) + fatal("malloc"); + if (!read_all(entry_fd, filename, entry_size)) + fatal("read dvm entry"); + close(entry_fd); + talk_to_daemon(filename); +} + +void scan_spool(char *name) +{ + struct dirent *ent; + DIR *dir = opendir(name); + if (!dir) + fatal("opendir"); + while ((ent = readdir(dir))) { + char *fname = ent->d_name; + if (!strcmp(fname, ".") || !strcmp(fname, "..")) + continue; + process_spoolentry(fname); + break; + } + closedir(dir); +} + +int main() +{ + signal(SIGPIPE, SIG_IGN); + scan_spool(DVM_SPOOL); + return 0; +} From 1a5bfd8c2b0a2510d6fc4b8b9055e605e6879a1e Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Fri, 11 Mar 2011 13:08:19 +0100 Subject: [PATCH 21/64] Reset SIGPIPE in qrexec_daemon, too. --- qrexec/qrexec_daemon.c | 1 + 1 file changed, 1 insertion(+) diff --git a/qrexec/qrexec_daemon.c b/qrexec/qrexec_daemon.c index 54077185..906905e4 100644 --- a/qrexec/qrexec_daemon.c +++ b/qrexec/qrexec_daemon.c @@ -282,6 +282,7 @@ void handle_trigger_exec(int req) for (i = 3; i < 256; i++) close(i); signal(SIGCHLD, SIG_DFL); + signal(SIGPIPE, SIG_DFL); execl("/usr/lib/qubes/qrexec_client", "qrexec_client", "-d", domain_id, "-l", lcmd, rcmd, NULL); perror("execl"); From b9e0e93a9072a360c4d7f905c3c0f8804f6d3953 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Fri, 11 Mar 2011 13:16:33 +0100 Subject: [PATCH 22/64] In qrexec_client, check write_all(local_stdin_fd,..) value --- qrexec/qrexec_client.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qrexec/qrexec_client.c b/qrexec/qrexec_client.c index 38271cf8..db4ba607 100644 --- a/qrexec/qrexec_client.c +++ b/qrexec/qrexec_client.c @@ -125,8 +125,10 @@ void handle_daemon_data(int s) case MSG_SERVER_TO_CLIENT_STDOUT: if (hdr.len == 0) close(local_stdin_fd); - else - write_all(local_stdin_fd, buf, hdr.len); + else if (!write_all(local_stdin_fd, buf, hdr.len)) { + perror("write local stdout"); + exit(1); + } break; case MSG_SERVER_TO_CLIENT_STDERR: write_all(2, buf, hdr.len); From 470ddce435f04b56005a31fc6a1b1813415c1260 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Fri, 11 Mar 2011 14:14:04 +0100 Subject: [PATCH 23/64] qrexec_daemon creates VMname-based link to its socket --- qrexec/glue.h | 2 +- qrexec/qrexec_daemon.c | 13 ++++++------- qrexec/unix_server.c | 8 +++++++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/qrexec/glue.h b/qrexec/glue.h index abcad649..81c6c9e5 100644 --- a/qrexec/glue.h +++ b/qrexec/glue.h @@ -32,7 +32,7 @@ int write_all_vchan_ext(void *buf, int size); int buffer_space_vchan_ext(); void fix_fds(int fdin, int fdout, int fderr); -int get_server_socket(int domid); +int get_server_socket(int domid, char * domname); int do_accept(int s); enum { diff --git a/qrexec/qrexec_daemon.c b/qrexec/qrexec_daemon.c index 906905e4..c955dbd2 100644 --- a/qrexec/qrexec_daemon.c +++ b/qrexec/qrexec_daemon.c @@ -55,7 +55,7 @@ void handle_usr1(int x) exit(0); } -char domain_id[64]; +char *remote_domain_name; void init(int xid) { @@ -66,7 +66,6 @@ void init(int xid) fprintf(stderr, "domain id=0?\n"); exit(1); } - snprintf(domain_id, sizeof(domain_id), "%d", xid); signal(SIGUSR1, handle_usr1); switch (fork()) { case -1: @@ -83,6 +82,8 @@ void init(int xid) "/var/log/qubes/qrexec.%d.log", xid); umask(0007); logfd = open(dbg_log, O_WRONLY | O_CREAT | O_TRUNC, 0640); + umask(0077); + dup2(logfd, 1); dup2(logfd, 2); @@ -92,11 +93,9 @@ void init(int xid) exit(1); } - umask(0); - server_fd = get_server_socket(xid); - umask(0077); - peer_client_init(xid, REXEC_PORT); + remote_domain_name = peer_client_init(xid, REXEC_PORT); setuid(getuid()); + server_fd = get_server_socket(xid, remote_domain_name); signal(SIGPIPE, SIG_IGN); signal(SIGCHLD, SIG_IGN); signal(SIGUSR1, SIG_DFL); @@ -284,7 +283,7 @@ void handle_trigger_exec(int req) signal(SIGCHLD, SIG_DFL); signal(SIGPIPE, SIG_DFL); execl("/usr/lib/qubes/qrexec_client", "qrexec_client", "-d", - domain_id, "-l", lcmd, rcmd, NULL); + remote_domain_name, "-l", lcmd, rcmd, NULL); perror("execl"); exit(1); } diff --git a/qrexec/unix_server.c b/qrexec/unix_server.c index aedf9167..14a61273 100644 --- a/qrexec/unix_server.c +++ b/qrexec/unix_server.c @@ -27,15 +27,21 @@ #include #include "qrexec.h" -int get_server_socket(int domid) +int get_server_socket(int domid, char *domname) { struct sockaddr_un sockname; int s; char socket_address[40]; + char link_to_socket_name[strlen(domname) + sizeof(socket_address)]; snprintf(socket_address, sizeof(socket_address), QREXEC_DAEMON_SOCKET_DIR "/qrexec.%d", domid); + snprintf(link_to_socket_name, sizeof link_to_socket_name, + QREXEC_DAEMON_SOCKET_DIR "/qrexec.%s", domname); unlink(socket_address); + unlink(link_to_socket_name); + symlink(socket_address, link_to_socket_name); + s = socket(AF_UNIX, SOCK_STREAM, 0); memset(&sockname, 0, sizeof(sockname)); sockname.sun_family = AF_UNIX; From 00f4bf119789f15847f2204e92e18c5ad030ae74 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Fri, 11 Mar 2011 16:06:00 +0100 Subject: [PATCH 24/64] qrexec_client accepts non-numeric domain description. Just tries to open qrexec.argv[1]. --- qrexec/qrexec_client.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/qrexec/qrexec_client.c b/qrexec/qrexec_client.c index db4ba607..23a4dac1 100644 --- a/qrexec/qrexec_client.c +++ b/qrexec/qrexec_client.c @@ -30,7 +30,7 @@ #include "buffer.h" #include "glue.h" -int connect_unix_socket(int domid) +int connect_unix_socket(char *domname) { int s, len; struct sockaddr_un remote; @@ -42,7 +42,7 @@ int connect_unix_socket(int domid) remote.sun_family = AF_UNIX; snprintf(remote.sun_path, sizeof remote.sun_path, - QREXEC_DAEMON_SOCKET_DIR "/qrexec.%d", domid); + QREXEC_DAEMON_SOCKET_DIR "/qrexec.%s", domname); len = strlen(remote.sun_path) + sizeof(remote.sun_family); if (connect(s, (struct sockaddr *) &remote, len) == -1) { perror("connect"); @@ -204,14 +204,14 @@ void usage(char *name) int main(int argc, char **argv) { int opt; - int domid = 0; + char *domname = NULL; int s; int just_exec = 0; char *local_cmdline = NULL; while ((opt = getopt(argc, argv, "d:l:e")) != -1) { switch (opt) { case 'd': - domid = atoi(optarg); + domname = strdup(optarg); break; case 'l': local_cmdline = strdup(optarg); @@ -223,10 +223,13 @@ int main(int argc, char **argv) usage(argv[0]); } } - if (optind >= argc || !domid) + if (optind >= argc || !domname) usage(argv[0]); - s = connect_unix_socket(domid); + + s = connect_unix_socket(domname); + setenv("QREXEC_REMOTE_DOMAIN", domname, 1); prepare_local_fds(local_cmdline); + if (just_exec) send_cmdline(s, MSG_CLIENT_TO_SERVER_JUST_EXEC, argv[optind]); From 5d3c43e4fabb4e18ee08e6eb04096f95b983ba09 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Mon, 14 Mar 2011 10:43:09 +0100 Subject: [PATCH 25/64] created qfile-daemon-dvm Mostly code from qfilexchgd; it will be removed soon. --- dom0/restore/qfile-daemon-dvm | 133 ++++++++++++++++++++++++++++++++++ rpm_spec/core-dom0.spec | 2 + 2 files changed, 135 insertions(+) create mode 100755 dom0/restore/qfile-daemon-dvm diff --git a/dom0/restore/qfile-daemon-dvm b/dom0/restore/qfile-daemon-dvm new file mode 100755 index 00000000..886d858b --- /dev/null +++ b/dom0/restore/qfile-daemon-dvm @@ -0,0 +1,133 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# +import os +import dbus +import subprocess +import sys + +from qubes.qubes import QubesVmCollection +from qubes.qubes import QubesException +from qubes.qubes import QubesDaemonPidfile +from qubes.qmemman_client import QMemmanClient + +current_savefile = '/var/run/qubes/current_savefile' +notify_object = None + +class QfileDaemonDvm: + def __init__(self, name): + self.name = name + + def do_get_dvm(self): + qmemman_client = QMemmanClient() + if not qmemman_client.request_memory(400*1024*1024): + qmemman_client.close() + errmsg = 'Not enough memory to create DVM. ' + errmsg +='Terminate some appVM and retry.' + subprocess.call(['/usr/bin/kdialog', '--sorry', errmsg]) + return None + + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_writing() + qvm_collection.load() + + vm = qvm_collection.get_vm_by_name(self.name) + if vm is None: + sys.stderr.write( 'Domain ' + vmname + ' does not exist ?') + qvm_collection.unlock_db() + qmemman_client.close() + return None + retcode = subprocess.call(['/usr/lib/qubes/qubes_restore', + current_savefile, + '-c', vm.label.color, + '-i', vm.label.icon, + '-l', str(vm.label.index)]) + qmemman_client.close() + if retcode != 0: + subprocess.call(['/usr/bin/kdialog', '--sorry', 'DisposableVM creation failed, see qubes_restore.log']) + qvm_collection.unlock_db() + return None + f = open('/var/run/qubes/dispVM_xid', 'r'); + disp_xid = f.readline().rstrip('\n') + disp_name = f.readline().rstrip('\n') + disptempl = f.readline().rstrip('\n') + f.close() + vm_disptempl = qvm_collection.get_vm_by_name(disptempl); + if vm_disptempl is None: + sys.stderr.write( 'Domain ' + disptempl + ' does not exist ?') + qvm_collection.unlock_db() + return None + qvm_collection.add_new_disposablevm(disp_name, vm_disptempl.template_vm, label=vm.label) + qvm_collection.save() + qvm_collection.unlock_db() + + return disp_name + + def dvm_setup_ok(self): + dvmdata_dir = '/var/lib/qubes/dvmdata/' + if not os.path.isfile(current_savefile): + return False + if not os.path.isfile(dvmdata_dir+'default_savefile') or not os.path.isfile(dvmdata_dir+'savefile_root'): + return False + dvm_mtime = os.stat(current_savefile).st_mtime + root_mtime = os.stat(dvmdata_dir+'savefile_root').st_mtime + if dvm_mtime < root_mtime: + return False + return True + + def tray_notify(self, str, timeout = 3000): + notify_object.Notify("Qubes", 0, "red", "Qubes", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications") + + def tray_notify_error(self, str, timeout = 3000): + notify_object.Notify("Qubes", 0, "dialog-error", "Qubes", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications") + + def get_dvm(self): + if not self.dvm_setup_ok(): + self.tray_notify("Updating DisposableVM savefile, please wait") + if os.system("qvm-create-default-dvm --default-template --default-script >/var/run/qubes/qvm-create-default-dvm.stdout Date: Mon, 14 Mar 2011 11:25:18 +0100 Subject: [PATCH 26/64] Added new qvm-open-in-dvm, aka qvm-open-in-dvm2 Small, childless bash script. --- appvm/qvm-dvm.desktop | 2 +- appvm/qvm-open-in-dvm2 | 40 ++++++++++++++++++++++++++++++++++++++++ rpm_spec/core-appvm.spec | 3 ++- 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100755 appvm/qvm-open-in-dvm2 diff --git a/appvm/qvm-dvm.desktop b/appvm/qvm-dvm.desktop index a7f5ad77..67f9ea51 100644 --- a/appvm/qvm-dvm.desktop +++ b/appvm/qvm-dvm.desktop @@ -4,7 +4,7 @@ Type=Service X-KDE-ServiceTypes=KonqPopupMenu/Plugin,all/allfiles [Desktop Action QvmDvm] -Exec=/usr/bin/qvm-open-in-dvm disposable %U +Exec=/usr/bin/qvm-open-in-dvm2 %U Icon=kget Name=Open In DisposableVM diff --git a/appvm/qvm-open-in-dvm2 b/appvm/qvm-open-in-dvm2 new file mode 100755 index 00000000..eb0d4e2e --- /dev/null +++ b/appvm/qvm-open-in-dvm2 @@ -0,0 +1,40 @@ +#!/bin/bash +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# + +if ! [ $# = 1 ] ; then + echo "Usage: $0 filename" + exit 1 +fi + +FILE="$1" +if ! [ "X""${FILE:0:1}" = X/ ] ; then + FILE="$PWD"/"$1" +fi + +DVMSPOOL=/home/user/.dvmspool +if ! [ -e $DVMSPOOL ] ; then + mkdir $DVMSPOOL || exit 1 +fi + +echo -n "$FILE" > $DVMSPOOL/req.$$ +echo -n DVMR > /var/run/qubes/qrexec_agent + diff --git a/rpm_spec/core-appvm.spec b/rpm_spec/core-appvm.spec index f7cc7311..b947b83b 100644 --- a/rpm_spec/core-appvm.spec +++ b/rpm_spec/core-appvm.spec @@ -72,7 +72,7 @@ mkdir -p $RPM_BUILD_ROOT/etc/init.d cp qubes_core $RPM_BUILD_ROOT/etc/init.d/ mkdir -p $RPM_BUILD_ROOT/var/lib/qubes mkdir -p $RPM_BUILD_ROOT/usr/bin -cp qubes_timestamp qvm-copy-to-vm qvm-open-in-dvm $RPM_BUILD_ROOT/usr/bin +cp qubes_timestamp qvm-copy-to-vm qvm-open-in-dvm qvm-open-in-dvm2 $RPM_BUILD_ROOT/usr/bin mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes cp qubes_add_pendrive_script qubes_penctl qvm-copy-to-vm.kde $RPM_BUILD_ROOT/usr/lib/qubes cp ../qrexec/qrexec_agent $RPM_BUILD_ROOT/usr/lib/qubes @@ -207,6 +207,7 @@ rm -rf $RPM_BUILD_ROOT /usr/bin/qvm-copy-to-vm /usr/lib/qubes/qvm-copy-to-vm.kde %attr(4755,root,root) /usr/bin/qvm-open-in-dvm +/usr/bin/qvm-open-in-dvm2 /usr/lib/qubes/qvm-dvm-transfer /usr/lib/qubes/meminfo-writer /usr/lib/qubes/dvm_file_editor From d82001819dd92030579556d50518a5b676360ac9 Mon Sep 17 00:00:00 2001 From: Tomasz Sterna Date: Mon, 14 Mar 2011 20:57:08 +0100 Subject: [PATCH 27/64] Properly call QubesProxyVm superclass --- dom0/qvm-core/qubes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dom0/qvm-core/qubes.py b/dom0/qvm-core/qubes.py index 2ac48904..176673dd 100755 --- a/dom0/qvm-core/qubes.py +++ b/dom0/qvm-core/qubes.py @@ -1116,7 +1116,7 @@ class QubesProxyVm(QubesNetVm): def start(self, debug_console = False, verbose = False, preparing_dvm = False): if dry_run: return - retcode = super(QubesFirewallVm, self).start(debug_console=debug_console, verbose=verbose, preparing_dvm=preparing_dvm) + retcode = super(QubesProxyVm, self).start(debug_console=debug_console, verbose=verbose, preparing_dvm=preparing_dvm) self.netvm_vm.add_external_ip_permission(self.get_xid()) self.write_netvm_domid_entry() return retcode @@ -1125,7 +1125,7 @@ class QubesProxyVm(QubesNetVm): if dry_run: return self.netvm_vm.remove_external_ip_permission(self.get_xid()) - super(QubesFirewallVm, self).force_shutdown() + super(QubesProxyVm, self).force_shutdown() def create_xenstore_entries(self, xid): if dry_run: From c0ca1a9f50ab1d8a1d549526306f0a8a7ae9405b Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Tue, 15 Mar 2011 10:36:50 +0100 Subject: [PATCH 28/64] Make sure read_all sets errno to 0 at EOF. --- common/ioall.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/ioall.c b/common/ioall.c index 1fca6f12..413477fc 100644 --- a/common/ioall.c +++ b/common/ioall.c @@ -23,6 +23,7 @@ #include #include #include +#include int write_all(int fd, void *buf, int size) { @@ -47,6 +48,7 @@ int read_all(int fd, void *buf, int size) while (got_read < size) { ret = read(fd, (char *) buf + got_read, size - got_read); if (ret == 0) { + errno = 0; fprintf(stderr, "EOF\n"); return 0; } From 0ed004904c82c5110d0e3fa92fdc15652d9d7676 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Tue, 15 Mar 2011 13:00:12 +0100 Subject: [PATCH 29/64] Handy gui_fatal() etc routines. --- common/gui-fatal.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++ common/gui-fatal.h | 2 ++ 2 files changed, 52 insertions(+) create mode 100644 common/gui-fatal.c create mode 100644 common/gui-fatal.h diff --git a/common/gui-fatal.c b/common/gui-fatal.c new file mode 100644 index 00000000..ed2b3d47 --- /dev/null +++ b/common/gui-fatal.c @@ -0,0 +1,50 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +static void fix_display() +{ + setenv("DISPLAY", ":0", 1); +} + +static void produce_message(char * type, const char *fmt, va_list args) +{ + char *kdialog_msg; + char buf[1024]; + (void) vsnprintf(buf, sizeof(buf), fmt, args); + asprintf(&kdialog_msg, "%s: %s: %s (error type: %s)", + program_invocation_short_name, type, buf, strerror(errno)); + fprintf(stderr, "%s", kdialog_msg); + switch (fork()) { + case -1: + exit(1); //what else + case 0: + fix_display(); + execlp("kdialog", "kdialog", "--sorry", kdialog_msg, NULL); + exit(1); + default:; + } +} + +void gui_fatal(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + produce_message("Fatal error", fmt, args); + va_end(args); + exit(1); +} + +void gui_nonfatal(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + produce_message("Information", fmt, args); + va_end(args); +} diff --git a/common/gui-fatal.h b/common/gui-fatal.h new file mode 100644 index 00000000..de9799f9 --- /dev/null +++ b/common/gui-fatal.h @@ -0,0 +1,2 @@ +void gui_fatal(const char *fmt, ...); +void gui_nonfatal(const char *fmt, ...); From 66bf0abb5370fc8c327739187664b9b1bd922f36 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Tue, 15 Mar 2011 13:12:21 +0100 Subject: [PATCH 30/64] Use gui_fatal in qfile-agent-dvm.c --- appvm/Makefile | 4 ++-- appvm/qfile-agent-dvm.c | 43 +++++++++++++---------------------------- 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/appvm/Makefile b/appvm/Makefile index d6bfb6a2..3f81fb94 100644 --- a/appvm/Makefile +++ b/appvm/Makefile @@ -3,8 +3,8 @@ CFLAGS=-Wall -I../common all: qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm dvm_file_editor qfile-agent-dvm dvm_file_editor: dvm_file_editor.o ../common/ioall.o $(CC) -o dvm_file_editor dvm_file_editor.o ../common/ioall.o -qfile-agent-dvm: qfile-agent-dvm.o ../common/ioall.o - $(CC) -o qfile-agent-dvm qfile-agent-dvm.o ../common/ioall.o +qfile-agent-dvm: qfile-agent-dvm.o ../common/ioall.o ../common/gui-fatal.o + $(CC) -o qfile-agent-dvm qfile-agent-dvm.o ../common/ioall.o ../common/gui-fatal.o qubes_penctl: qubes_penctl.o $(CC) -o qubes_penctl qubes_penctl.o -lxenstore qubes_add_pendrive_script: qubes_add_pendrive_script.o diff --git a/appvm/qfile-agent-dvm.c b/appvm/qfile-agent-dvm.c index 194d67cb..5db430cc 100644 --- a/appvm/qfile-agent-dvm.c +++ b/appvm/qfile-agent-dvm.c @@ -9,32 +9,15 @@ #include #include #include +#include #include "dvm2.h" -//the below usage of "system" is smelly, but should be fine - input comes from the same vm -void fatal(char *msg) -{ - char *errcmd; - asprintf(&errcmd, "DISPLAY=:0 kdialog --sorry 'qfile-agent-dvm: %s'", msg); - system(errcmd); - perror(msg); - exit(1); -} - -void nonfatal(char *msg) -{ - char *errcmd; - asprintf(&errcmd, "DISPLAY=:0 kdialog --sorry 'qfile-agent-dvm: %s'", msg); - system(errcmd); - fprintf(stderr, "%s", msg); -} - void send_file(char *fname) { char *base; int fd = open(fname, O_RDONLY); if (fd < 0) - fatal("open file_to_be_edited"); + gui_fatal("open %s", fname); base = rindex(fname, '/'); if (!base) base = fname; @@ -43,9 +26,9 @@ void send_file(char *fname) if (strlen(base) >= DVM_FILENAME_SIZE) base += strlen(base) - DVM_FILENAME_SIZE + 1; if (!write_all(1, base, DVM_FILENAME_SIZE)) - fatal("send filename"); + gui_fatal("send filename to dispVM"); if (!copy_fd_all(1, fd)) - fatal("send file"); + gui_fatal("send file to dispVM"); close(1); } @@ -53,9 +36,9 @@ int copy_and_return_nonemptiness(int tmpfd) { struct stat st; if (!copy_fd_all(tmpfd, 0)) - fatal("receiving file"); + gui_fatal("receiving file from dispVM"); if (fstat(tmpfd, &st)) - fatal("fstat"); + gui_fatal("fstat"); close(tmpfd); return st.st_size; @@ -70,7 +53,7 @@ void recv_file_nowrite(char *fname) asprintf(&tempfile, "/tmp/file_edited_in_dvm.XXXXXX"); tmpfd = mkstemp(tempfile); if (tmpfd < 0) - fatal("unable to create any temporary file, aborting"); + gui_fatal("unable to create any temporary file, aborting"); if (!copy_and_return_nonemptiness(tmpfd)) { unlink(tempfile); return; @@ -79,7 +62,7 @@ void recv_file_nowrite(char *fname) "The file %s has been edited in Disposable VM and the modified content has been received, " "but this file is in nonwritable directory and thus cannot be modified safely. The edited file has been " "saved to %s", fname, tempfile); - nonfatal(errmsg); + gui_nonfatal(errmsg); } void actually_recv_file(char *fname, char *tempfile, int tmpfd) @@ -89,7 +72,7 @@ void actually_recv_file(char *fname, char *tempfile, int tmpfd) return; } if (rename(tempfile, fname)) - fatal("rename"); + gui_fatal("rename"); } void recv_file(char *fname) @@ -121,13 +104,13 @@ void process_spoolentry(char *entry_name) entry_fd = open(abs_spool_entry_name, O_RDONLY); unlink(abs_spool_entry_name); if (entry_fd < 0 || fstat(entry_fd, &st)) - fatal("bad dvm_entry"); + gui_fatal("bad dvm_entry"); entry_size = st.st_size; filename = calloc(1, entry_size + DVM_FILENAME_SIZE); if (!filename) - fatal("malloc"); + gui_fatal("malloc"); if (!read_all(entry_fd, filename, entry_size)) - fatal("read dvm entry"); + gui_fatal("read dvm entry %s", abs_spool_entry_name); close(entry_fd); talk_to_daemon(filename); } @@ -137,7 +120,7 @@ void scan_spool(char *name) struct dirent *ent; DIR *dir = opendir(name); if (!dir) - fatal("opendir"); + gui_fatal("opendir %s", name); while ((ent = readdir(dir))) { char *fname = ent->d_name; if (!strcmp(fname, ".") || !strcmp(fname, "..")) From b8d983cfa905aeaad3cf91c577afb20db27a6612 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Tue, 15 Mar 2011 16:07:00 +0100 Subject: [PATCH 31/64] Added qfile-agent --- appvm/Makefile | 6 +- appvm/copy_file.c | 28 ++++++ appvm/filecopy.h | 18 ++++ appvm/qfile-agent.c | 201 +++++++++++++++++++++++++++++++++++++++ rpm_spec/core-appvm.spec | 3 +- 5 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 appvm/copy_file.c create mode 100644 appvm/filecopy.h create mode 100644 appvm/qfile-agent.c diff --git a/appvm/Makefile b/appvm/Makefile index 3f81fb94..c6f4d256 100644 --- a/appvm/Makefile +++ b/appvm/Makefile @@ -1,10 +1,12 @@ CC=gcc CFLAGS=-Wall -I../common -all: qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm dvm_file_editor qfile-agent-dvm +all: qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm dvm_file_editor qfile-agent-dvm qfile-agent dvm_file_editor: dvm_file_editor.o ../common/ioall.o $(CC) -o dvm_file_editor dvm_file_editor.o ../common/ioall.o qfile-agent-dvm: qfile-agent-dvm.o ../common/ioall.o ../common/gui-fatal.o - $(CC) -o qfile-agent-dvm qfile-agent-dvm.o ../common/ioall.o ../common/gui-fatal.o + $(CC) -o qfile-agent-dvm qfile-agent-dvm.o ../common/ioall.o ../common/gui-fatal.o +qfile-agent: qfile-agent.o ../common/ioall.o ../common/gui-fatal.o copy_file.o + $(CC) -o qfile-agent qfile-agent.o ../common/ioall.o ../common/gui-fatal.o copy_file.o qubes_penctl: qubes_penctl.o $(CC) -o qubes_penctl qubes_penctl.o -lxenstore qubes_add_pendrive_script: qubes_add_pendrive_script.o diff --git a/appvm/copy_file.c b/appvm/copy_file.c new file mode 100644 index 00000000..5f7fc793 --- /dev/null +++ b/appvm/copy_file.c @@ -0,0 +1,28 @@ +#include +#include +extern void notify_progress(int, int); + +char * copy_file(int outfd, int infd, long long size) +{ + char buf[4096]; + long long written = 0; + int ret; + int count; + while (written < size) { + if (size - written > sizeof(buf)) + count = sizeof buf; + else + count = size - written; + ret = read(infd, buf, count); + if (!ret) + return("EOF while reading file"); + if (ret < 0) + return("error reading file"); + if (!write_all(outfd, buf, ret)) + return("error writing file content"); + notify_progress(ret, 0); + written += ret; + } + return NULL; +} + diff --git a/appvm/filecopy.h b/appvm/filecopy.h new file mode 100644 index 00000000..b4f6638c --- /dev/null +++ b/appvm/filecopy.h @@ -0,0 +1,18 @@ +#define FILECOPY_SPOOL "/home/user/.filecopyspool" +#define FILECOPY_VMNAME_SIZE 32 +#define PROGRESS_NOTIFY_DELTA (15*1000*1000) +#define MAX_PATH_LENGTH 16384 + +#define LEGAL_EOF 31415926 + +struct file_header { +unsigned int namelen; +unsigned int mode; +unsigned long long filelen; +unsigned int atime; +unsigned int atime_nsec; +unsigned int mtime; +unsigned int mtime_nsec; +}; + +char * copy_file(int outfd, int infd, long long size); diff --git a/appvm/qfile-agent.c b/appvm/qfile-agent.c new file mode 100644 index 00000000..b4ba354e --- /dev/null +++ b/appvm/qfile-agent.c @@ -0,0 +1,201 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "filecopy.h" + +char *client_flags; +void do_notify_progress(long long total) +{ + FILE *progress; + if (!client_flags[0]) + return; + progress = fopen(client_flags, "w"); + if (!progress) + return; + fprintf(progress, "%d %lld", getpid(), total); + fclose(progress); +} + +void notify_progress(int size, int force) +{ + static long long total = 0; + static long long prev_total = 0; + total += size; + if (total > prev_total + PROGRESS_NOTIFY_DELTA || force) { + do_notify_progress(total); + prev_total = total; + } +} + +void write_headers(struct file_header *hdr, char *filename) +{ + if (!write_all(1, hdr, sizeof(*hdr)) + || !write_all(1, filename, hdr->namelen)) + gui_fatal("writing file headers to remove AppVM"); +} + +int single_file_processor(char *filename, struct stat *st) +{ + struct file_header hdr; + int fd; + mode_t mode = st->st_mode; + + hdr.namelen = strlen(filename) + 1; + hdr.mode = mode; + hdr.atime = st->st_atim.tv_sec; + hdr.atime_nsec = st->st_atim.tv_nsec; + hdr.mtime = st->st_mtim.tv_sec; + hdr.mtime_nsec = st->st_mtim.tv_nsec; + + if (S_ISREG(mode)) { + char * ret; + fd = open(filename, O_RDONLY); + if (!fd) + gui_fatal("open %s", filename); + hdr.filelen = st->st_size; + write_headers(&hdr, filename); + ret=copy_file(1, fd, hdr.filelen); + if (ret) + gui_fatal("Copying file %s: %s", filename, ret); + close(fd); + } + if (S_ISDIR(mode)) { + hdr.filelen = 0; + write_headers(&hdr, filename); + } + if (S_ISLNK(mode)) { + char name[st->st_size + 1]; + if (readlink(filename, name, sizeof(name)) != st->st_size) + gui_fatal("readlink %s", filename); + hdr.filelen = st->st_size + 1; + write_headers(&hdr, filename); + if (!write_all(1, name, st->st_size + 1)) + gui_fatal("write to remote VM"); + } + return 0; +} + +int do_fs_walk(char *file) +{ + char *newfile; + struct stat st; + struct dirent *ent; + DIR *dir; + + if (lstat(file, &st)) + gui_fatal("stat %s", file); + single_file_processor(file, &st); + if (!S_ISDIR(st.st_mode)) + return 0; + dir = opendir(file); + if (!dir) + gui_fatal("opendir %s", file); + while ((ent = readdir(dir))) { + char *fname = ent->d_name; + if (!strcmp(fname, ".") || !strcmp(fname, "..")) + continue; + asprintf(&newfile, "%s/%s", file, fname); + do_fs_walk(newfile); + free(newfile); + } + closedir(dir); + // directory metadata is resent; this makes the code simple, + // and the atime/mtime is set correctly at the second time + single_file_processor(file, &st); + return 0; +} + +void send_vmname(char *vmname) +{ + char buf[FILECOPY_VMNAME_SIZE]; + memset(buf, 0, sizeof(buf)); + strncat(buf, vmname, sizeof(buf) - 1); + if (!write_all(1, buf, sizeof buf)) + gui_fatal("writing vmname to remote VM"); +} + +char *get_item(char *data, char **current, int size) +{ + char *ret; + if ((unsigned long) *current >= (unsigned long) data + size) + return NULL; + ret = *current; + *current += strlen(ret) + 1; + return ret; +} + +void parse_entry(char *data, int datasize) +{ + char *current = data; + char *vmname, *entry, *sep; + vmname = get_item(data, ¤t, datasize); + client_flags = get_item(data, ¤t, datasize); + notify_progress(0, 1); + send_vmname(vmname); + while ((entry = get_item(data, ¤t, datasize))) { + sep = rindex(entry, '/'); + if (!sep) + gui_fatal("Internal error: nonabsolute filenames not allowed"); + *sep = 0; + if (entry[0] == 0) + chdir("/"); + else if (chdir(entry)) + gui_fatal("chdir to %s", entry); + do_fs_walk(sep + 1); + } + notify_progress(0, 1); +} + +void process_spoolentry(char *entry_name) +{ + char *abs_spool_entry_name; + int entry_fd; + struct stat st; + char *entry; + int entry_size; + asprintf(&abs_spool_entry_name, "%s/%s", FILECOPY_SPOOL, + entry_name); + entry_fd = open(abs_spool_entry_name, O_RDONLY); + unlink(abs_spool_entry_name); + if (entry_fd < 0 || fstat(entry_fd, &st)) + gui_fatal("bad file copy spool entry"); + entry_size = st.st_size; + entry = calloc(1, entry_size + 1); + if (!entry) + gui_fatal("malloc"); + if (!read_all(entry_fd, entry, entry_size)) + gui_fatal("read filecopy entry"); + close(entry_fd); + parse_entry(entry, entry_size); +} + +void scan_spool(char *name) +{ + struct dirent *ent; + DIR *dir = opendir(name); + if (!dir) + gui_fatal("opendir %s", name); + while ((ent = readdir(dir))) { + char *fname = ent->d_name; + if (fname[0] != '.') + process_spoolentry(fname); + break; + } + closedir(dir); +} + +int main() +{ + signal(SIGPIPE, SIG_IGN); + scan_spool(FILECOPY_SPOOL); + return 0; +} diff --git a/rpm_spec/core-appvm.spec b/rpm_spec/core-appvm.spec index b947b83b..df0b5907 100644 --- a/rpm_spec/core-appvm.spec +++ b/rpm_spec/core-appvm.spec @@ -76,7 +76,7 @@ cp qubes_timestamp qvm-copy-to-vm qvm-open-in-dvm qvm-open-in-dvm2 $RPM_BUILD_RO mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes cp qubes_add_pendrive_script qubes_penctl qvm-copy-to-vm.kde $RPM_BUILD_ROOT/usr/lib/qubes cp ../qrexec/qrexec_agent $RPM_BUILD_ROOT/usr/lib/qubes -cp dvm_file_editor $RPM_BUILD_ROOT/usr/lib/qubes +cp dvm_file_editor qfile-agent $RPM_BUILD_ROOT/usr/lib/qubes ln -s /usr/bin/qvm-open-in-dvm $RPM_BUILD_ROOT/usr/lib/qubes/qvm-dvm-transfer cp ../common/meminfo-writer $RPM_BUILD_ROOT/usr/lib/qubes mkdir -p $RPM_BUILD_ROOT/%{kde_service_dir} @@ -216,6 +216,7 @@ rm -rf $RPM_BUILD_ROOT %attr(4755,root,root) /usr/lib/qubes/qubes_penctl /usr/lib/qubes/qubes_add_pendrive_script /usr/lib/qubes/qrexec_agent +/usr/lib/qubes/qfile-agent /etc/udev/rules.d/qubes.rules /etc/sysconfig/iptables /var/lib/qubes From f0a76204494dea36219ec30c1ca911e954d9b9bf Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Tue, 15 Mar 2011 16:19:42 +0100 Subject: [PATCH 32/64] Package qfile-agent-dvm, too. --- rpm_spec/core-appvm.spec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rpm_spec/core-appvm.spec b/rpm_spec/core-appvm.spec index df0b5907..3023ffa0 100644 --- a/rpm_spec/core-appvm.spec +++ b/rpm_spec/core-appvm.spec @@ -76,7 +76,7 @@ cp qubes_timestamp qvm-copy-to-vm qvm-open-in-dvm qvm-open-in-dvm2 $RPM_BUILD_RO mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes cp qubes_add_pendrive_script qubes_penctl qvm-copy-to-vm.kde $RPM_BUILD_ROOT/usr/lib/qubes cp ../qrexec/qrexec_agent $RPM_BUILD_ROOT/usr/lib/qubes -cp dvm_file_editor qfile-agent $RPM_BUILD_ROOT/usr/lib/qubes +cp dvm_file_editor qfile-agent qfile-agent-dvm $RPM_BUILD_ROOT/usr/lib/qubes ln -s /usr/bin/qvm-open-in-dvm $RPM_BUILD_ROOT/usr/lib/qubes/qvm-dvm-transfer cp ../common/meminfo-writer $RPM_BUILD_ROOT/usr/lib/qubes mkdir -p $RPM_BUILD_ROOT/%{kde_service_dir} @@ -217,6 +217,7 @@ rm -rf $RPM_BUILD_ROOT /usr/lib/qubes/qubes_add_pendrive_script /usr/lib/qubes/qrexec_agent /usr/lib/qubes/qfile-agent +/usr/lib/qubes/qfile-agent-dvm /etc/udev/rules.d/qubes.rules /etc/sysconfig/iptables /var/lib/qubes From 84b1a186ffe485cb6f9f605c54b0b09a53a601cd Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Tue, 15 Mar 2011 16:43:43 +0100 Subject: [PATCH 33/64] Added qfile-unpacker and qfile-daemon --- appvm/Makefile | 4 +- appvm/qfile-unpacker.c | 83 +++++++++++++++++++++++++++++++ appvm/unpack.c | 101 ++++++++++++++++++++++++++++++++++++++ dom0/restore/qfile-daemon | 59 ++++++++++++++++++++++ rpm_spec/core-appvm.spec | 3 +- rpm_spec/core-dom0.spec | 2 + 6 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 appvm/qfile-unpacker.c create mode 100644 appvm/unpack.c create mode 100644 dom0/restore/qfile-daemon diff --git a/appvm/Makefile b/appvm/Makefile index c6f4d256..c1abcfad 100644 --- a/appvm/Makefile +++ b/appvm/Makefile @@ -1,12 +1,14 @@ CC=gcc CFLAGS=-Wall -I../common -all: qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm dvm_file_editor qfile-agent-dvm qfile-agent +all: qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm dvm_file_editor qfile-agent-dvm qfile-agent qfile-unpacker dvm_file_editor: dvm_file_editor.o ../common/ioall.o $(CC) -o dvm_file_editor dvm_file_editor.o ../common/ioall.o qfile-agent-dvm: qfile-agent-dvm.o ../common/ioall.o ../common/gui-fatal.o $(CC) -o qfile-agent-dvm qfile-agent-dvm.o ../common/ioall.o ../common/gui-fatal.o qfile-agent: qfile-agent.o ../common/ioall.o ../common/gui-fatal.o copy_file.o $(CC) -o qfile-agent qfile-agent.o ../common/ioall.o ../common/gui-fatal.o copy_file.o +qfile-unpacker: qfile-unpacker.o ../common/ioall.o ../common/gui-fatal.o copy_file.o unpack.o + $(CC) -o qfile-unpacker qfile-unpacker.o ../common/ioall.o ../common/gui-fatal.o copy_file.o unpack.o qubes_penctl: qubes_penctl.o $(CC) -o qubes_penctl qubes_penctl.o -lxenstore qubes_add_pendrive_script: qubes_add_pendrive_script.o diff --git a/appvm/qfile-unpacker.c b/appvm/qfile-unpacker.c new file mode 100644 index 00000000..eaa5c067 --- /dev/null +++ b/appvm/qfile-unpacker.c @@ -0,0 +1,83 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "filecopy.h" +#define INCOMING_DIR_ROOT "/home/user/incoming" +int prepare_creds_return_uid(char *username) +{ + struct passwd *pwd; + pwd = getpwnam(username); + if (!pwd) { + perror("getpwnam"); + exit(1); + } + setenv("HOME", pwd->pw_dir, 1); + setenv("USER", username, 1); + setgid(pwd->pw_gid); + initgroups(username, pwd->pw_gid); + setfsuid(pwd->pw_uid); + return pwd->pw_uid; +} + +void wait_for_child(int statusfd) +{ + int status; + if (read(statusfd, &status, sizeof status)!=sizeof status) + gui_fatal("File copy error: Internal error reading status from unpacker"); + errno = status; + switch (status) { + case LEGAL_EOF: break; + case 0: gui_fatal("File copy: Connection terminated unexpectedly"); break; + case EINVAL: gui_fatal("File copy: Corrupted data from packer"); break; + case EEXIST: gui_fatal("File copy: not overwriting existing file. Clean ~/incoming, and retry copy"); break; + default: gui_fatal("File copy"); + } +} + +extern void do_unpack(int); + +int main(int argc, char ** argv) +{ + char *incoming_dir; + int pipefds[2]; + int uid; + + pipe(pipefds); + + uid = prepare_creds_return_uid("user"); + + mkdir(INCOMING_DIR_ROOT, 0700); + asprintf(&incoming_dir, "%s/from-%s", INCOMING_DIR_ROOT, argv[1]); + mkdir(incoming_dir, 0700); + if (chdir(incoming_dir)) + gui_fatal("Error chdir to %s", incoming_dir); + switch (fork()) { + case -1: + perror("fork"); + exit(1); + case 0: + if (chroot(incoming_dir)) //impossible + gui_fatal("Error chroot to %s", incoming_dir); + setuid(uid); + close(pipefds[0]); + do_unpack(pipefds[1]); + exit(0); + default:; + } + + setuid(uid); + close(pipefds[1]); + wait_for_child(pipefds[0]); + + return 0; +} diff --git a/appvm/unpack.c b/appvm/unpack.c new file mode 100644 index 00000000..c0353c11 --- /dev/null +++ b/appvm/unpack.c @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "filecopy.h" + +char namebuf[MAX_PATH_LENGTH]; +void notify_progress(int p1, int p2) +{ +} + +int global_status_fd; +void do_exit(int code) +{ + int codebuf = code; + write(global_status_fd, &codebuf, sizeof codebuf); + exit(0); +} + + +void fix_times_and_perms(struct file_header *hdr, char *name) +{ + struct timeval times[2] = + { {hdr->atime, hdr->atime_nsec / 1000}, {hdr->mtime, + hdr->mtime_nsec / 1000} + }; + if (chmod(name, hdr->mode & 07777)) + do_exit(errno); + if (utimes(name, times)) + do_exit(errno); +} + + + +void process_one_file_reg(struct file_header *hdr, char *name) +{ + char *ret; + int fdout = + open(name, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0700); + if (fdout < 0) + do_exit(errno); + ret = copy_file(fdout, 0, hdr->filelen); + if (ret) + do_exit(errno); + close(fdout); + fix_times_and_perms(hdr, name); +} + + +void process_one_file_dir(struct file_header *hdr, char *name) +{ + if (mkdir(name, 0700) && errno != EEXIST) + do_exit(errno); + fix_times_and_perms(hdr, name); +} + +void process_one_file_link(struct file_header *hdr, char *name) +{ + char content[MAX_PATH_LENGTH]; + if (hdr->filelen > MAX_PATH_LENGTH - 1) + do_exit(ENAMETOOLONG); + if (!read_all(0, content, hdr->filelen)) + do_exit(errno); + content[hdr->filelen] = 0; + if (symlink(content, name)) + do_exit(errno); + +} + +void process_one_file(struct file_header *hdr) +{ + if (hdr->namelen > MAX_PATH_LENGTH - 1) + do_exit(ENAMETOOLONG); + if (!read_all(0, namebuf, hdr->namelen)) + do_exit(errno); + namebuf[hdr->namelen] = 0; + if (S_ISREG(hdr->mode)) + process_one_file_reg(hdr, namebuf); + else if (S_ISLNK(hdr->mode)) + process_one_file_link(hdr, namebuf); + else if (S_ISDIR(hdr->mode)) + process_one_file_dir(hdr, namebuf); + else + do_exit(EINVAL); +} + +void do_unpack(int fd) +{ + global_status_fd = fd; + struct file_header hdr; + while (read_all(0, &hdr, sizeof hdr)) + process_one_file(&hdr); + if (errno) + do_exit(errno); + else + do_exit(LEGAL_EOF); +} diff --git a/dom0/restore/qfile-daemon b/dom0/restore/qfile-daemon new file mode 100644 index 00000000..6b589279 --- /dev/null +++ b/dom0/restore/qfile-daemon @@ -0,0 +1,59 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# +import os +import sys +import subprocess +from qubes.qubes import QubesVmCollection + +def is_copy_allowed(vm): +# if vm.copy_allowed: +# return True + q = 'Do you authorize file copy from ' + q+= os.getenv("QREXEC_REMOTE_DOMAIN") + q+= ' to ' + vm.name + ' ?' + retcode = subprocess.call(['/usr/bin/kdialog', '--yesno', q, '--title', 'File transfer confirmation']) + return retcode == 0 + +def main(): + FILECOPY_VMNAME_SIZE = 32 + blob=os.read(0, FILECOPY_VMNAME_SIZE) + vmname = blob.split("\x00")[0] + + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_reading() + qvm_collection.load() + qvm_collection.unlock_db() + + vm = qvm_collection.get_vm_by_name(vmname) +# we do not want to flood dom0 with error windows; so just log to stderr + if vm is None: + print >> sys.stderr, 'Domain ' + vmname + ' does not exist ?' + exit(1) + if not vm.is_running(): + print >> sys.stderr, 'Domain ' + vmname + ' is not running ?' + exit(1) + if not is_copy_allowed(vm): + exit(1) + cmd = "root:/usr/lib/qubes/qfile-unpacker " + os.getenv("QREXEC_REMOTE_DOMAIN") + os.execl("/usr/lib/qubes/qrexec_client", "qrexec_client", "-d", vmname, cmd) + +main() diff --git a/rpm_spec/core-appvm.spec b/rpm_spec/core-appvm.spec index 3023ffa0..f3c875d4 100644 --- a/rpm_spec/core-appvm.spec +++ b/rpm_spec/core-appvm.spec @@ -76,7 +76,7 @@ cp qubes_timestamp qvm-copy-to-vm qvm-open-in-dvm qvm-open-in-dvm2 $RPM_BUILD_RO mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes cp qubes_add_pendrive_script qubes_penctl qvm-copy-to-vm.kde $RPM_BUILD_ROOT/usr/lib/qubes cp ../qrexec/qrexec_agent $RPM_BUILD_ROOT/usr/lib/qubes -cp dvm_file_editor qfile-agent qfile-agent-dvm $RPM_BUILD_ROOT/usr/lib/qubes +cp dvm_file_editor qfile-agent qfile-agent-dvm qfile-unpacker $RPM_BUILD_ROOT/usr/lib/qubes ln -s /usr/bin/qvm-open-in-dvm $RPM_BUILD_ROOT/usr/lib/qubes/qvm-dvm-transfer cp ../common/meminfo-writer $RPM_BUILD_ROOT/usr/lib/qubes mkdir -p $RPM_BUILD_ROOT/%{kde_service_dir} @@ -218,6 +218,7 @@ rm -rf $RPM_BUILD_ROOT /usr/lib/qubes/qrexec_agent /usr/lib/qubes/qfile-agent /usr/lib/qubes/qfile-agent-dvm +/usr/lib/qubes/qfile-unpacker /etc/udev/rules.d/qubes.rules /etc/sysconfig/iptables /var/lib/qubes diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index 89cbd284..ed72a347 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -96,6 +96,7 @@ cp restore/xenstore-watch restore/qvm-create-default-dvm $RPM_BUILD_ROOT/usr/bin cp restore/qubes_restore restore/xenfreepages $RPM_BUILD_ROOT/usr/lib/qubes cp restore/qubes_prepare_saved_domain.sh $RPM_BUILD_ROOT/usr/lib/qubes cp restore/qfile-daemon-dvm $RPM_BUILD_ROOT/usr/lib/qubes +cp restore/qfile-daemon $RPM_BUILD_ROOT/usr/lib/qubes mkdir -p $RPM_BUILD_ROOT/var/lib/qubes mkdir -p $RPM_BUILD_ROOT/var/lib/qubes/vm-templates @@ -265,6 +266,7 @@ fi /usr/lib/qubes/qmemman_daemon.py* /usr/lib/qubes/meminfo-writer /usr/lib/qubes/qfile-daemon-dvm* +/usr/lib/qubes/qfile-daemon %attr(770,root,qubes) %dir /var/lib/qubes %attr(770,root,qubes) %dir /var/lib/qubes/vm-templates %attr(770,root,qubes) %dir /var/lib/qubes/appvms From 8ce0e0f39b210f5f0a3c82d30d33671f3e4a35e9 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Tue, 15 Mar 2011 16:48:17 +0100 Subject: [PATCH 34/64] Fixed permissions of qfile-daemon --- dom0/restore/qfile-daemon | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 dom0/restore/qfile-daemon diff --git a/dom0/restore/qfile-daemon b/dom0/restore/qfile-daemon old mode 100644 new mode 100755 From 2ea7a0e77a27b3fd727db9eafff0f3852e42ecd6 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 16 Mar 2011 10:11:55 +0100 Subject: [PATCH 35/64] Build filecopy tools with -g. --- appvm/Makefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/appvm/Makefile b/appvm/Makefile index c1abcfad..56f90913 100644 --- a/appvm/Makefile +++ b/appvm/Makefile @@ -1,14 +1,14 @@ CC=gcc -CFLAGS=-Wall -I../common +CFLAGS=-g -Wall -I../common all: qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm dvm_file_editor qfile-agent-dvm qfile-agent qfile-unpacker dvm_file_editor: dvm_file_editor.o ../common/ioall.o - $(CC) -o dvm_file_editor dvm_file_editor.o ../common/ioall.o + $(CC) -g -o dvm_file_editor dvm_file_editor.o ../common/ioall.o qfile-agent-dvm: qfile-agent-dvm.o ../common/ioall.o ../common/gui-fatal.o - $(CC) -o qfile-agent-dvm qfile-agent-dvm.o ../common/ioall.o ../common/gui-fatal.o + $(CC) -g -o qfile-agent-dvm qfile-agent-dvm.o ../common/ioall.o ../common/gui-fatal.o qfile-agent: qfile-agent.o ../common/ioall.o ../common/gui-fatal.o copy_file.o - $(CC) -o qfile-agent qfile-agent.o ../common/ioall.o ../common/gui-fatal.o copy_file.o + $(CC) -g -o qfile-agent qfile-agent.o ../common/ioall.o ../common/gui-fatal.o copy_file.o qfile-unpacker: qfile-unpacker.o ../common/ioall.o ../common/gui-fatal.o copy_file.o unpack.o - $(CC) -o qfile-unpacker qfile-unpacker.o ../common/ioall.o ../common/gui-fatal.o copy_file.o unpack.o + $(CC) -g -o qfile-unpacker qfile-unpacker.o ../common/ioall.o ../common/gui-fatal.o copy_file.o unpack.o qubes_penctl: qubes_penctl.o $(CC) -o qubes_penctl qubes_penctl.o -lxenstore qubes_add_pendrive_script: qubes_add_pendrive_script.o From e6da61cb5e12553dbc3fa99b329c830f9c179d64 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 16 Mar 2011 10:14:39 +0100 Subject: [PATCH 36/64] Scan filecopy sppool properly. --- appvm/qfile-agent.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/appvm/qfile-agent.c b/appvm/qfile-agent.c index b4ba354e..7ab2639a 100644 --- a/appvm/qfile-agent.c +++ b/appvm/qfile-agent.c @@ -186,9 +186,10 @@ void scan_spool(char *name) gui_fatal("opendir %s", name); while ((ent = readdir(dir))) { char *fname = ent->d_name; - if (fname[0] != '.') + if (fname[0] != '.') { process_spoolentry(fname); - break; + break; + } } closedir(dir); } From 5230c1293405e74817623b5efd0cb400fc2c0118 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 16 Mar 2011 10:48:27 +0100 Subject: [PATCH 37/64] qfile-agent: Handle filenames with trailing slash properly. --- appvm/qfile-agent.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/appvm/qfile-agent.c b/appvm/qfile-agent.c index 7ab2639a..71c86e5f 100644 --- a/appvm/qfile-agent.c +++ b/appvm/qfile-agent.c @@ -57,15 +57,15 @@ int single_file_processor(char *filename, struct stat *st) hdr.mtime_nsec = st->st_mtim.tv_nsec; if (S_ISREG(mode)) { - char * ret; + char *ret; fd = open(filename, O_RDONLY); if (!fd) gui_fatal("open %s", filename); hdr.filelen = st->st_size; write_headers(&hdr, filename); - ret=copy_file(1, fd, hdr.filelen); + ret = copy_file(1, fd, hdr.filelen); if (ret) - gui_fatal("Copying file %s: %s", filename, ret); + gui_fatal("Copying file %s: %s", filename, ret); close(fd); } if (S_ISDIR(mode)) { @@ -142,10 +142,13 @@ void parse_entry(char *data, int datasize) notify_progress(0, 1); send_vmname(vmname); while ((entry = get_item(data, ¤t, datasize))) { - sep = rindex(entry, '/'); - if (!sep) - gui_fatal("Internal error: nonabsolute filenames not allowed"); - *sep = 0; + do { + sep = rindex(entry, '/'); + if (!sep) + gui_fatal + ("Internal error: nonabsolute filenames not allowed"); + *sep = 0; + } while (sep[1] == 0); if (entry[0] == 0) chdir("/"); else if (chdir(entry)) @@ -188,8 +191,8 @@ void scan_spool(char *name) char *fname = ent->d_name; if (fname[0] != '.') { process_spoolentry(fname); - break; - } + break; + } } closedir(dir); } From b01464670b5fa7d6e6006f7f866dfb61c4957cee Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 16 Mar 2011 10:50:11 +0100 Subject: [PATCH 38/64] New qvm-copy-to-vm, aka qvm-copy-to-vm2 --- appvm/qvm-copy-to-vm2 | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100755 appvm/qvm-copy-to-vm2 diff --git a/appvm/qvm-copy-to-vm2 b/appvm/qvm-copy-to-vm2 new file mode 100755 index 00000000..ddababe7 --- /dev/null +++ b/appvm/qvm-copy-to-vm2 @@ -0,0 +1,47 @@ +#!/bin/sh +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# +set -x + +if [ $# -lt 2 ] ; then + echo usage: $0 'vmname file [file]*' + exit 1 +fi + +FILECOPY_SPOOL=/home/user/.filecopyspool +if ! [ -e $FILECOPY_SPOOL ] ; then + mkdir $FILECOPY_SPOOL +fi + +REQ_FILE_TMP=$FILECOPY_SPOOL/.req.$$ +echo -ne "$1""\x00" > $REQ_FILE_TMP +echo -ne "$PROGRESS_FILE""\x00" >> $REQ_FILE_TMP + +shift +for FILE in "$@" ; do + if ! [ "X""${FILE:0:1}" = X/ ] ; then + FILE="$PWD"/"$FILE" + fi + echo -ne "$FILE""\x00" >> $REQ_FILE_TMP +done + +mv $REQ_FILE_TMP $FILECOPY_SPOOL/req.$$ +echo -n FCPR > /var/run/qubes/qrexec_agent From ecf007b3a2c45d0eb5060ca1e204d20af0c89fc0 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 16 Mar 2011 11:06:27 +0100 Subject: [PATCH 39/64] qfile-agent writes DONE to the status file at the end of work. --- appvm/qfile-agent.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/appvm/qfile-agent.c b/appvm/qfile-agent.c index 71c86e5f..f7e27a98 100644 --- a/appvm/qfile-agent.c +++ b/appvm/qfile-agent.c @@ -12,8 +12,16 @@ #include #include "filecopy.h" +enum { + PROGRESS_FLAG_NORMAL, + PROGRESS_FLAG_INIT, + PROGRESS_FLAG_DONE +}; + + + char *client_flags; -void do_notify_progress(long long total) +void do_notify_progress(long long total, int flag) { FILE *progress; if (!client_flags[0]) @@ -21,17 +29,19 @@ void do_notify_progress(long long total) progress = fopen(client_flags, "w"); if (!progress) return; - fprintf(progress, "%d %lld", getpid(), total); + fprintf(progress, "%d %lld %s", getpid(), total, + flag == PROGRESS_FLAG_DONE ? "DONE" : "BUSY"); fclose(progress); } -void notify_progress(int size, int force) +void notify_progress(int size, int flag) { static long long total = 0; static long long prev_total = 0; total += size; - if (total > prev_total + PROGRESS_NOTIFY_DELTA || force) { - do_notify_progress(total); + if (total > prev_total + PROGRESS_NOTIFY_DELTA + || (flag != PROGRESS_FLAG_NORMAL)) { + do_notify_progress(total, flag); prev_total = total; } } @@ -139,7 +149,7 @@ void parse_entry(char *data, int datasize) char *vmname, *entry, *sep; vmname = get_item(data, ¤t, datasize); client_flags = get_item(data, ¤t, datasize); - notify_progress(0, 1); + notify_progress(0, PROGRESS_FLAG_INIT); send_vmname(vmname); while ((entry = get_item(data, ¤t, datasize))) { do { @@ -155,7 +165,7 @@ void parse_entry(char *data, int datasize) gui_fatal("chdir to %s", entry); do_fs_walk(sep + 1); } - notify_progress(0, 1); + notify_progress(0, PROGRESS_FLAG_DONE); } void process_spoolentry(char *entry_name) From 2938ee53565523d64b6501b4a0d943d8ede6deea Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 16 Mar 2011 11:07:27 +0100 Subject: [PATCH 40/64] removed set -x from qvm-copy-to-vm2 --- appvm/qvm-copy-to-vm2 | 1 - 1 file changed, 1 deletion(-) diff --git a/appvm/qvm-copy-to-vm2 b/appvm/qvm-copy-to-vm2 index ddababe7..56dcdef8 100755 --- a/appvm/qvm-copy-to-vm2 +++ b/appvm/qvm-copy-to-vm2 @@ -19,7 +19,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # -set -x if [ $# -lt 2 ] ; then echo usage: $0 'vmname file [file]*' From 821f707053ed0d85daffd5e7de42dc57e4772b9d Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 16 Mar 2011 12:00:22 +0100 Subject: [PATCH 41/64] Added qvm-copy-to-vm2.kde --- appvm/qvm-copy-to-vm2.kde | 48 +++++++++++++++++++++++++++++++++++++++ appvm/qvm-copy.desktop | 2 +- 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100755 appvm/qvm-copy-to-vm2.kde diff --git a/appvm/qvm-copy-to-vm2.kde b/appvm/qvm-copy-to-vm2.kde new file mode 100755 index 00000000..879279be --- /dev/null +++ b/appvm/qvm-copy-to-vm2.kde @@ -0,0 +1,48 @@ +#!/bin/sh +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# + +VM=$(kdialog -inputbox "Enter the VM name to send files to:") +if [ X$VM = X ] ; then exit 0 ; fi + +SIZE=$(du -c "$@" | tail -1 | cut -f 1) +REF=$(kdialog --progressbar "Copy progress") +qdbus $REF org.freedesktop.DBus.Properties.Set "" maximum $SIZE + +export PROGRESS_FILE=$(mktemp) +qvm-copy-to-vm2 $VM "$@" +while ! [ -s $PROGRESS_FILE ] ; do + sleep 0.1 +done +while true ; do + read agentpid sentsize agentstatus < $PROGRESS_FILE + if ! [ -e /proc/$agentpid ] ; then break ; fi + if [ "x"$agentstatus = xdone ] ; then break ; fi + CURRSIZE=$(($sentsize/1024)) + qdbus $REF org.freedesktop.DBus.Properties.Set "" value $CURRSIZE + sleep 0.4 +done + +qdbus $REF close +rm -f $PROGRESS_FILE +if ! [ "x"$agentstatus = xDONE ] ; then + kdialog --sorry 'Abnormal file copy termination; see /var/log/qubes/qrexec.xid.log in dom0 for more details' +fi diff --git a/appvm/qvm-copy.desktop b/appvm/qvm-copy.desktop index 5795eb61..4d5e800f 100644 --- a/appvm/qvm-copy.desktop +++ b/appvm/qvm-copy.desktop @@ -4,7 +4,7 @@ Type=Service X-KDE-ServiceTypes=KonqPopupMenu/Plugin,inode/directory,all/allfiles [Desktop Action QvmCopy] -Exec=/usr/lib/qubes/qvm-copy-to-vm.kde %U +Exec=/usr/lib/qubes/qvm-copy-to-vm2.kde %U Icon=kget Name=Send To VM From a195f436b72f95926f693584ccdddf7ecf726213 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 16 Mar 2011 12:48:29 +0100 Subject: [PATCH 42/64] In qfile-unpacker, set perms on the directory only on second pass. It solves problem with transferring r.x directory. Originally, it would fail when creating files in the directory (as it is not writable). Now, we will create it rwx, create files in it, and fix perms and utimes on the second pass. [user@devel fcopy]$ ls -ald /boot dr-xr-xr-x 4 root root 4096 Sep 1 2010 /boot --- appvm/unpack.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/appvm/unpack.c b/appvm/unpack.c index c0353c11..ad53ebf5 100644 --- a/appvm/unpack.c +++ b/appvm/unpack.c @@ -53,7 +53,11 @@ void process_one_file_reg(struct file_header *hdr, char *name) void process_one_file_dir(struct file_header *hdr, char *name) { - if (mkdir(name, 0700) && errno != EEXIST) +// fix perms only when the directory is sent for the second time +// it allows to transfer r.x directory contents, as we create it rwx initially + if (!mkdir(name, 0700)) + return; + if (errno != EEXIST) do_exit(errno); fix_times_and_perms(hdr, name); } From 777eaa2168bc2a4e7d0a404010d8ec2ec5433c73 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 16 Mar 2011 12:58:40 +0100 Subject: [PATCH 43/64] In read_all()/write_all(), continue upon EINTR. --- common/ioall.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/ioall.c b/common/ioall.c index 413477fc..a99a8aac 100644 --- a/common/ioall.c +++ b/common/ioall.c @@ -31,6 +31,8 @@ int write_all(int fd, void *buf, int size) int ret; while (written < size) { ret = write(fd, (char *) buf + written, size - written); + if (ret == -1 && errno == EINTR) + continue; if (ret <= 0) { perror("write"); return 0; @@ -47,6 +49,8 @@ int read_all(int fd, void *buf, int size) int ret; while (got_read < size) { ret = read(fd, (char *) buf + got_read, size - got_read); + if (ret == -1 && errno == EINTR) + continue; if (ret == 0) { errno = 0; fprintf(stderr, "EOF\n"); @@ -68,6 +72,8 @@ int copy_fd_all(int fdout, int fdin) char buf[4096]; for (;;) { ret = read(fdin, buf, sizeof(buf)); + if (ret == -1 && errno == EINTR) + continue; if (!ret) break; if (ret < 0) { @@ -81,4 +87,3 @@ int copy_fd_all(int fdout, int fdin) } return 1; } - From 27cfd6111a49eda01057453ca060020f69bc7dfc Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 16 Mar 2011 14:21:45 +0100 Subject: [PATCH 44/64] qrexec_daemon limits the number of its children So that evil VM cannot just send flood of exec qfile-daemon requests, and DoS dom0. --- qrexec/qrexec_daemon.c | 47 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/qrexec/qrexec_daemon.c b/qrexec/qrexec_daemon.c index c955dbd2..8507c90f 100644 --- a/qrexec/qrexec_daemon.c +++ b/qrexec/qrexec_daemon.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "qrexec.h" #include "buffer.h" @@ -55,6 +56,8 @@ void handle_usr1(int x) exit(0); } +void sigchld_handler(int x); + char *remote_domain_name; void init(int xid) @@ -97,7 +100,7 @@ void init(int xid) setuid(getuid()); server_fd = get_server_socket(xid, remote_domain_name); signal(SIGPIPE, SIG_IGN); - signal(SIGCHLD, SIG_IGN); + signal(SIGCHLD, sigchld_handler); signal(SIGUSR1, SIG_DFL); kill(getppid(), SIGUSR1); } @@ -252,10 +255,48 @@ void pass_to_client(int clid, struct client_header *hdr) } } +int children_count; +int child_exited; + +void sigchld_handler(int x) +{ + child_exited = 1; + signal(SIGCHLD, sigchld_handler); +} + +void reap_children() +{ + int status; + while (waitpid(-1, &status, WNOHANG) > 0) + children_count--; + child_exited = 0; +} + +void wait_for_child() +{ + int status; + waitpid(-1, &status, 0); + children_count--; +} + +#define MAX_CHILDREN 10 +void check_children_count() +{ + if (children_count > MAX_CHILDREN) { + fprintf(stderr, + "max number of children reached, waiting for child exit...\n"); + wait_for_child(); + fprintf(stderr, "now children_count=%d, continuing.\n", + children_count); + } +} + void handle_trigger_exec(int req) { char *rcmd = NULL, *lcmd = NULL; int i; + + check_children_count(); switch (req) { case QREXEC_EXECUTE_FILE_COPY: rcmd = "directly:user:/usr/lib/qubes/qfile-agent"; @@ -276,6 +317,7 @@ void handle_trigger_exec(int req) case 0: break; default: + children_count++; return; } for (i = 3; i < 256; i++) @@ -405,5 +447,8 @@ int main(int argc, char **argv) if (clients[i].state != CLIENT_INVALID && FD_ISSET(i, &wrset)) flush_client_data_daemon(i); + if (child_exited) + reap_children(); + } } From 769eedd33a8c988ba37bef3b53d53c4593c814b6 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 16 Mar 2011 14:52:35 +0100 Subject: [PATCH 45/64] Make qrexec_client wait for its local child before exiting If we do not wait and exit imemdiately, qrexec_daemon will decrease the children count and continue spawning processes, while e.g. qfile-daemon still waits for kdialog - so dom0 will be DoSed by multiple processes. --- qrexec/qrexec_client.c | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/qrexec/qrexec_client.c b/qrexec/qrexec_client.c index 23a4dac1..c378a550 100644 --- a/qrexec/qrexec_client.c +++ b/qrexec/qrexec_client.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "qrexec.h" #include "buffer.h" #include "glue.h" @@ -58,6 +59,18 @@ void do_exec(char *prog) int local_stdin_fd, local_stdout_fd; +void do_exit(int code) +{ + int status; +// sever communication lines; wait for child, if any +// so that qrexec-daemon can count (recursively) spawned processes correctly + close(local_stdin_fd); + close(local_stdout_fd); + waitpid(-1, &status, 0); + exit(code); +} + + void prepare_local_fds(char *cmdline) { int pid; @@ -79,7 +92,7 @@ void send_cmdline(int s, int type, char *cmdline) if (!write_all(s, &hdr, sizeof(hdr)) || !write_all(s, cmdline, hdr.len)) { perror("write daemon"); - exit(1); + do_exit(1); } } @@ -90,7 +103,7 @@ void handle_input(int s) ret = read(local_stdout_fd, buf, sizeof(buf)); if (ret < 0) { perror("read"); - exit(1); + do_exit(1); } if (ret == 0) { local_stdout_fd = -1; @@ -98,7 +111,7 @@ void handle_input(int s) } if (!write_all(s, buf, ret)) { perror("write daemon"); - exit(1); + do_exit(1); } } @@ -110,15 +123,15 @@ void handle_daemon_data(int s) if (!read_all(s, &hdr, sizeof hdr)) { perror("read daemon"); - exit(1); + do_exit(1); } if (hdr.len > MAX_DATA_CHUNK) { fprintf(stderr, "client_header.len=%d\n", hdr.len); - exit(1); + do_exit(1); } if (!read_all(s, buf, hdr.len)) { perror("read daemon"); - exit(1); + do_exit(1); } switch (hdr.type) { @@ -127,7 +140,7 @@ void handle_daemon_data(int s) close(local_stdin_fd); else if (!write_all(local_stdin_fd, buf, hdr.len)) { perror("write local stdout"); - exit(1); + do_exit(1); } break; case MSG_SERVER_TO_CLIENT_STDERR: @@ -136,13 +149,13 @@ void handle_daemon_data(int s) case MSG_SERVER_TO_CLIENT_EXIT_CODE: status = *(unsigned int *) buf; if (WIFEXITED(status)) - exit(WEXITSTATUS(status)); + do_exit(WEXITSTATUS(status)); else - exit(255); + do_exit(255); break; default: fprintf(stderr, "unknown msg %d\n", hdr.type); - exit(1); + do_exit(1); } } @@ -160,7 +173,7 @@ void handle_daemon_only_until_writable(s) if (select(s + 1, &rdset, &wrset, NULL, NULL) < 0) { perror("select"); - exit(1); + do_exit(1); } if (FD_ISSET(s, &rdset)) handle_daemon_data(s); @@ -183,7 +196,7 @@ void select_loop(int s) } if (select(max + 1, &select_set, NULL, NULL, NULL) < 0) { perror("select"); - exit(1); + do_exit(1); } if (FD_ISSET(s, &select_set)) handle_daemon_data(s); From 15bab70eaed146cc5675bdc6aa496f4c871ad739 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 16 Mar 2011 15:18:37 +0100 Subject: [PATCH 46/64] Handle pipe io in qrexec_agent properly Don't reopen pipe after each read - no need, and it could lose events. --- qrexec/qrexec_agent.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qrexec/qrexec_agent.c b/qrexec/qrexec_agent.c index 94cab149..3245d870 100644 --- a/qrexec/qrexec_agent.c +++ b/qrexec/qrexec_agent.c @@ -483,9 +483,12 @@ void handle_trigger_io() write_all_vchan_ext(&s_hdr, sizeof s_hdr); } } +// trigger_fd is nonblock - so no need to reopen +#if 0 close(trigger_fd); trigger_fd = open(QREXEC_AGENT_TRIGGER_PATH, O_RDONLY | O_NONBLOCK); +#endif } int main() From d40fb3a2e1ce10dce019fed54b4ab2748df5011a Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 16 Mar 2011 16:11:05 +0100 Subject: [PATCH 47/64] Fifo semantics is hard to get right. Finally: we need to close the command pipe at EOF. --- qrexec/qrexec_agent.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/qrexec/qrexec_agent.c b/qrexec/qrexec_agent.c index 3245d870..c40d7159 100644 --- a/qrexec/qrexec_agent.c +++ b/qrexec/qrexec_agent.c @@ -469,10 +469,11 @@ void handle_trigger_io() { struct server_header s_hdr; char buf[5]; + int ret; s_hdr.clid = 0; s_hdr.len = 0; - if (read(trigger_fd, buf, 4) == 4) { + if ((ret = read(trigger_fd, buf, 4)) == 4) { buf[4] = 0; if (!strcmp(buf, "FCPR")) s_hdr.clid = QREXEC_EXECUTE_FILE_COPY; @@ -484,11 +485,12 @@ void handle_trigger_io() } } // trigger_fd is nonblock - so no need to reopen -#if 0 - close(trigger_fd); - trigger_fd = - open(QREXEC_AGENT_TRIGGER_PATH, O_RDONLY | O_NONBLOCK); -#endif +// not really, need to reopen at EOF + if (ret <= 0) { + close(trigger_fd); + trigger_fd = + open(QREXEC_AGENT_TRIGGER_PATH, O_RDONLY | O_NONBLOCK); + } } int main() From e410ad52ba88c471086f49e053254a94f7b0e79f Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 16 Mar 2011 16:24:54 +0100 Subject: [PATCH 48/64] Bloody perror messes with errno; need to save errno. --- common/ioall.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/common/ioall.c b/common/ioall.c index a99a8aac..239f3333 100644 --- a/common/ioall.c +++ b/common/ioall.c @@ -25,6 +25,14 @@ #include #include +void perror_wrapper(char * msg) +{ + int prev=errno; + perror(msg); + errno=prev; +} + + int write_all(int fd, void *buf, int size) { int written = 0; @@ -34,7 +42,7 @@ int write_all(int fd, void *buf, int size) if (ret == -1 && errno == EINTR) continue; if (ret <= 0) { - perror("write"); + perror_wrapper("write"); return 0; } written += ret; @@ -57,7 +65,7 @@ int read_all(int fd, void *buf, int size) return 0; } if (ret < 0) { - perror("read"); + perror_wrapper("read"); return 0; } got_read += ret; @@ -77,11 +85,11 @@ int copy_fd_all(int fdout, int fdin) if (!ret) break; if (ret < 0) { - perror("read"); + perror_wrapper("read"); return 0; } if (!write_all(fdout, buf, ret)) { - perror("write"); + perror_wrapper("write"); return 0; } } From 4087b1d052b5a9c85c8ca69f13654d24ce0a720d Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 16 Mar 2011 16:47:32 +0100 Subject: [PATCH 49/64] Package qvm-copy-to-vm2*, too. --- rpm_spec/core-appvm.spec | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rpm_spec/core-appvm.spec b/rpm_spec/core-appvm.spec index f3c875d4..7821292f 100644 --- a/rpm_spec/core-appvm.spec +++ b/rpm_spec/core-appvm.spec @@ -73,8 +73,10 @@ cp qubes_core $RPM_BUILD_ROOT/etc/init.d/ mkdir -p $RPM_BUILD_ROOT/var/lib/qubes mkdir -p $RPM_BUILD_ROOT/usr/bin cp qubes_timestamp qvm-copy-to-vm qvm-open-in-dvm qvm-open-in-dvm2 $RPM_BUILD_ROOT/usr/bin +cp qvm-copy-to-vm2 $RPM_BUILD_ROOT/usr/bin mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes cp qubes_add_pendrive_script qubes_penctl qvm-copy-to-vm.kde $RPM_BUILD_ROOT/usr/lib/qubes +cp qvm-copy-to-vm2.kde $RPM_BUILD_ROOT/usr/lib/qubes cp ../qrexec/qrexec_agent $RPM_BUILD_ROOT/usr/lib/qubes cp dvm_file_editor qfile-agent qfile-agent-dvm qfile-unpacker $RPM_BUILD_ROOT/usr/lib/qubes ln -s /usr/bin/qvm-open-in-dvm $RPM_BUILD_ROOT/usr/lib/qubes/qvm-dvm-transfer @@ -205,7 +207,9 @@ rm -rf $RPM_BUILD_ROOT /etc/fstab /etc/init.d/qubes_core /usr/bin/qvm-copy-to-vm +/usr/bin/qvm-copy-to-vm2 /usr/lib/qubes/qvm-copy-to-vm.kde +/usr/lib/qubes/qvm-copy-to-vm2.kde %attr(4755,root,root) /usr/bin/qvm-open-in-dvm /usr/bin/qvm-open-in-dvm2 /usr/lib/qubes/qvm-dvm-transfer From af7fefa73f3127467379caf5f27988bd3e5617b5 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Thu, 17 Mar 2011 16:53:29 +0100 Subject: [PATCH 50/64] qrexec: handle buffered writes correctly In case when we have a buffered write, always append to the buffer, even if the pipe happens to be writable now. If not, in case of certain tight race we might end up writing buffered data in wrong order. --- qrexec/write_stdin.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qrexec/write_stdin.c b/qrexec/write_stdin.c index 9eec24be..9da24bed 100644 --- a/qrexec/write_stdin.c +++ b/qrexec/write_stdin.c @@ -60,6 +60,12 @@ int write_stdin(int fd, int clid, char *data, int len, struct buffer *buffer) { int ret; + + if (buffer_len(buffer)) { + buffer_append(buffer, data, len); + return WRITE_STDIN_BUFFERED; + } + ret = write(fd, data, len); if (ret == len) return WRITE_STDIN_OK; From fb71bf968c246f8c0a9d4e7b4b44022ead82c402 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Thu, 17 Mar 2011 17:37:35 +0100 Subject: [PATCH 51/64] qrexec_agent: when receiving close from daemon, check buffered data We need to wait for buffer flush, so that buffered data is not lost, and only then close pipe to the child. --- qrexec/qrexec_agent.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/qrexec/qrexec_agent.c b/qrexec/qrexec_agent.c index c40d7159..8bc6e014 100644 --- a/qrexec/qrexec_agent.c +++ b/qrexec/qrexec_agent.c @@ -53,6 +53,7 @@ struct _client_info { int pid; int is_blocked; + int is_close_after_flush_needed; struct buffer buffer; }; @@ -176,6 +177,7 @@ void handle_exec(int clid, int len) client_info[clid].stderr_fd = stderr_fd; client_info[clid].pid = pid; client_info[clid].is_blocked = 0; + client_info[clid].is_close_after_flush_needed = 0; buffer_init(&client_info[clid].buffer); fprintf(stderr, "executed %s pid %d\n", buf, pid); @@ -242,8 +244,12 @@ void handle_input(int clid, int len) return; if (len == 0) { - close(client_info[clid].stdin_fd); - client_info[clid].stdin_fd = -1; + if (client_info[clid].is_blocked) + client_info[clid].is_close_after_flush_needed = 1; + else { + close(client_info[clid].stdin_fd); + client_info[clid].stdin_fd = -1; + } return; } @@ -453,6 +459,11 @@ void flush_client_data_agent(int clid) switch (flush_client_data(info->stdin_fd, clid, &info->buffer)) { case WRITE_STDIN_OK: info->is_blocked = 0; + if (info->is_close_after_flush_needed) { + close(info->stdin_fd); + info->stdin_fd = -1; + info->is_close_after_flush_needed = 0; + } break; case WRITE_STDIN_ERROR: remove_process(clid, 128); From 53b517f6a57a2cd5ba60910f62b5461281429d76 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Thu, 17 Mar 2011 17:53:33 +0100 Subject: [PATCH 52/64] qrexec: move set_nonblock function to write_stdin It will be needed there. --- qrexec/glue.h | 1 + qrexec/qrexec_agent.c | 6 ------ qrexec/qrexec_daemon.c | 6 ------ qrexec/write_stdin.c | 7 +++++++ 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/qrexec/glue.h b/qrexec/glue.h index 81c6c9e5..03bb6c15 100644 --- a/qrexec/glue.h +++ b/qrexec/glue.h @@ -44,3 +44,4 @@ enum { int flush_client_data(int fd, int clid, struct buffer *buffer); int write_stdin(int fd, int clid, char *data, int len, struct buffer *buffer); +void set_nonblock(int fd); diff --git a/qrexec/qrexec_agent.c b/qrexec/qrexec_agent.c index 8bc6e014..bace294f 100644 --- a/qrexec/qrexec_agent.c +++ b/qrexec/qrexec_agent.c @@ -143,12 +143,6 @@ void handle_just_exec(int clid, int len) fprintf(stderr, "executed (nowait) %s pid %d\n", buf, pid); } -void set_nonblock(int fd) -{ - int fl = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, fl | O_NONBLOCK); -} - void handle_exec(int clid, int len) { char buf[len]; diff --git a/qrexec/qrexec_daemon.c b/qrexec/qrexec_daemon.c index 8507c90f..051103ca 100644 --- a/qrexec/qrexec_daemon.c +++ b/qrexec/qrexec_daemon.c @@ -148,12 +148,6 @@ void pass_to_agent(int fd, struct server_header *s_hdr) write_all_vchan_ext(buf, len); } -void set_nonblock(int fd) -{ - int fl = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, fl | O_NONBLOCK); -} - void handle_client_cmdline(int fd) { struct client_header hdr; diff --git a/qrexec/write_stdin.c b/qrexec/write_stdin.c index 9da24bed..bb5a40a6 100644 --- a/qrexec/write_stdin.c +++ b/qrexec/write_stdin.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "qrexec.h" #include "buffer.h" #include "glue.h" @@ -89,3 +90,9 @@ int write_stdin(int fd, int clid, char *data, int len, } } + +void set_nonblock(int fd) +{ + int fl = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, fl | O_NONBLOCK); +} From 1d24ef9d1a041247bc01af0dceeac76869d9566e Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Thu, 17 Mar 2011 18:15:04 +0100 Subject: [PATCH 53/64] qrexec: when forgetting about a client/process, flush buffered data We need to spawn a child to take care of buffered data flushing, if there is any. Expensive, but should be needed rarely. --- qrexec/Makefile | 4 ++-- qrexec/glue.h | 1 + qrexec/qrexec_agent.c | 5 ++++- qrexec/qrexec_daemon.c | 6 +++++- qrexec/write_stdin.c | 32 +++++++++++++++++++++++++++++++- 5 files changed, 43 insertions(+), 5 deletions(-) diff --git a/qrexec/Makefile b/qrexec/Makefile index d4a5c857..58e48865 100644 --- a/qrexec/Makefile +++ b/qrexec/Makefile @@ -6,8 +6,8 @@ COMMONIOALL=../common/ioall.o all: qrexec_daemon qrexec_agent qrexec_client qrexec_daemon: qrexec_daemon.o unix_server.o $(COMMONIOALL) txrx-vchan.o buffer.o write_stdin.o $(CC) -g -o qrexec_daemon qrexec_daemon.o unix_server.o $(COMMONIOALL) txrx-vchan.o write_stdin.o buffer.o $(XENLIBS) -qrexec_agent: qrexec_agent.o exec.o txrx-vchan.o write_stdin.o buffer.o - $(CC) -g -o qrexec_agent qrexec_agent.o exec.o txrx-vchan.o write_stdin.o buffer.o $(XENLIBS) +qrexec_agent: qrexec_agent.o exec.o txrx-vchan.o write_stdin.o buffer.o $(COMMONIOALL) + $(CC) -g -o qrexec_agent qrexec_agent.o exec.o txrx-vchan.o write_stdin.o buffer.o $(COMMONIOALL) $(XENLIBS) qrexec_client: qrexec_client.o $(COMMONIOALL) exec.o $(CC) -g -o qrexec_client qrexec_client.o $(COMMONIOALL) exec.o clean: diff --git a/qrexec/glue.h b/qrexec/glue.h index 03bb6c15..60d697ad 100644 --- a/qrexec/glue.h +++ b/qrexec/glue.h @@ -45,3 +45,4 @@ int flush_client_data(int fd, int clid, struct buffer *buffer); int write_stdin(int fd, int clid, char *data, int len, struct buffer *buffer); void set_nonblock(int fd); +int fork_and_flush_stdin(int fd, struct buffer *buffer); diff --git a/qrexec/qrexec_agent.c b/qrexec/qrexec_agent.c index bace294f..24683c71 100644 --- a/qrexec/qrexec_agent.c +++ b/qrexec/qrexec_agent.c @@ -206,8 +206,11 @@ void remove_process(int clid, int status) int i; if (!client_info[clid].pid) return; + fork_and_flush_stdin(client_info[clid].stdin_fd, &client_info[clid].buffer); +#if 0 +// let's let it die by itself, possibly after it has received buffered stdin kill(client_info[clid].pid, SIGKILL); - +#endif if (status != -1) send_exit_code(clid, status); diff --git a/qrexec/qrexec_daemon.c b/qrexec/qrexec_daemon.c index 051103ca..518ba4e7 100644 --- a/qrexec/qrexec_daemon.c +++ b/qrexec/qrexec_daemon.c @@ -118,10 +118,15 @@ void handle_new_client() max_client_fd = fd; } +int children_count; + void flush_client(int fd) { int i; struct server_header s_hdr; + + if (fork_and_flush_stdin(fd, &clients[fd].buffer)) + children_count++; close(fd); clients[fd].state = CLIENT_INVALID; buffer_free(&clients[fd].buffer); @@ -249,7 +254,6 @@ void pass_to_client(int clid, struct client_header *hdr) } } -int children_count; int child_exited; void sigchld_handler(int x) diff --git a/qrexec/write_stdin.c b/qrexec/write_stdin.c index bb5a40a6..8ac1b221 100644 --- a/qrexec/write_stdin.c +++ b/qrexec/write_stdin.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include "qrexec.h" #include "buffer.h" #include "glue.h" @@ -66,7 +68,7 @@ int write_stdin(int fd, int clid, char *data, int len, buffer_append(buffer, data, len); return WRITE_STDIN_BUFFERED; } - + ret = write(fd, data, len); if (ret == len) return WRITE_STDIN_OK; @@ -96,3 +98,31 @@ void set_nonblock(int fd) int fl = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, fl | O_NONBLOCK); } + +void set_block(int fd) +{ + int fl = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, fl & ~O_NONBLOCK); +} + +int fork_and_flush_stdin(int fd, struct buffer *buffer) +{ + int i; + if (!buffer_len(buffer)) + return 0; + switch (fork()) { + case -1: + perror("fork"); + exit(1); + case 0: + break; + default: + return 1; + } + for (i = 0; i < MAX_FDS; i++) + if (i != fd && i != 2) + close(i); + set_block(fd); + write_all(fd, buffer_data(buffer), buffer_len(buffer)); + exit(0); +} From 7f6a06c354339d01edee64f4aa711c88f88354e9 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Fri, 18 Mar 2011 11:16:05 +0100 Subject: [PATCH 54/64] qrexec: in write_stdin, remove dependency on write size Previous code could barf when write was partial; probably can happen only if we increase vchan buffer size, but it is better isolated now. --- qrexec/write_stdin.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/qrexec/write_stdin.c b/qrexec/write_stdin.c index 8ac1b221..f2bbae9e 100644 --- a/qrexec/write_stdin.c +++ b/qrexec/write_stdin.c @@ -58,24 +58,30 @@ int flush_client_data(int fd, int clid, struct buffer *buffer) } - int write_stdin(int fd, int clid, char *data, int len, struct buffer *buffer) { int ret; + int written = 0; if (buffer_len(buffer)) { buffer_append(buffer, data, len); return WRITE_STDIN_BUFFERED; } - - ret = write(fd, data, len); - if (ret == len) - return WRITE_STDIN_OK; - if (ret == -1) { - if (errno == EAGAIN) { + while (written < len) { + ret = write(fd, data + written, len - written); + if (ret == 0) { + perror("write_stdin: write returns 0 ???"); + exit(1); + } + if (ret == -1) { struct server_header s_hdr; - buffer_append(buffer, data, len); + + if (errno != EAGAIN) + return WRITE_STDIN_ERROR; + + buffer_append(buffer, data + written, + len - written); s_hdr.type = MSG_XOFF; s_hdr.clid = clid; @@ -83,13 +89,10 @@ int write_stdin(int fd, int clid, char *data, int len, write_all_vchan_ext(&s_hdr, sizeof s_hdr); return WRITE_STDIN_BUFFERED; - } else - return WRITE_STDIN_ERROR; - } else { - fprintf(stderr, - "writes < PIPE_BUF were supposed to be atomic ?\n"); - return WRITE_STDIN_ERROR; + } + written += ret; } + return WRITE_STDIN_OK; } From aa58bec1d9d5d1d5877ef7aa9f217b05ccd9d400 Mon Sep 17 00:00:00 2001 From: Tomasz Sterna Date: Fri, 18 Mar 2011 14:12:19 +0100 Subject: [PATCH 55/64] Fixed default policy handling in firewall rules --- dom0/qvm-core/qubes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dom0/qvm-core/qubes.py b/dom0/qvm-core/qubes.py index 176673dd..dd36bfd4 100755 --- a/dom0/qvm-core/qubes.py +++ b/dom0/qvm-core/qubes.py @@ -1189,12 +1189,12 @@ class QubesProxyVm(QubesNetVm): reject_action = "REJECT --reject-with icmp-host-prohibited" if conf["allow"]: - rules_action = accept_action - default_action = reject_action + default_action = accept_action + rules_action = reject_action iptables += "-A FORWARD -i vif{0}.0 -p icmp -j ACCEPT\n".format(xid) else: - rules_action = reject_action - default_action = accept_action + default_action = reject_action + rules_action = accept_action for rule in conf["rules"]: iptables += "-A FORWARD -i vif{0}.0 -d {1}".format(xid, rule["address"]) From 481e9871c45924fbf95078fe39a2c5572b1cb7a8 Mon Sep 17 00:00:00 2001 From: Tomasz Sterna Date: Mon, 21 Mar 2011 22:06:53 +0100 Subject: [PATCH 56/64] Implemented implicit rule to allow ICMP traffic in firewall --- dom0/qvm-core/qubes.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dom0/qvm-core/qubes.py b/dom0/qvm-core/qubes.py index dd36bfd4..e165e482 100755 --- a/dom0/qvm-core/qubes.py +++ b/dom0/qvm-core/qubes.py @@ -1184,14 +1184,12 @@ class QubesProxyVm(QubesNetVm): iptables += "# '{0}' VM:\n".format(vm.name) iptables += "-A FORWARD ! -s {0}/32 -i vif{1}.0 -j DROP\n".format(vm.ip, xid) - accept_action = "ACCEPT" reject_action = "REJECT --reject-with icmp-host-prohibited" if conf["allow"]: default_action = accept_action rules_action = reject_action - iptables += "-A FORWARD -i vif{0}.0 -p icmp -j ACCEPT\n".format(xid) else: default_action = reject_action rules_action = accept_action @@ -1212,6 +1210,8 @@ class QubesProxyVm(QubesNetVm): # PREROUTING does DNAT to NetVM DNSes, so we need self.netvm_vm. properties iptables += "-A FORWARD -i vif{0}.0 -p udp -d {1} --dport 53 -j ACCEPT\n".format(xid,self.netvm_vm.gateway) iptables += "-A FORWARD -i vif{0}.0 -p udp -d {1} --dport 53 -j ACCEPT\n".format(xid,self.netvm_vm.secondary_dns) + if conf["allowIcmp"]: + iptables += "-A FORWARD -i vif{0}.0 -p icmp -j ACCEPT\n".format(xid) iptables += "-A FORWARD -i vif{0}.0 -j {1}\n".format(xid, default_action) @@ -1397,7 +1397,8 @@ class QubesAppVm(QubesCowVm): root = xml.etree.ElementTree.Element( "QubesFirwallRules", policy = "allow" if conf["allow"] else "deny", - dns = "allow" if conf["allowDns"] else "deny" + dns = "allow" if conf["allowDns"] else "deny", + icmp = "allow" if conf["allowIcmp"] else "deny" ) for rule in conf["rules"]: @@ -1431,7 +1432,7 @@ class QubesAppVm(QubesCowVm): return True def get_firewall_conf(self): - conf = { "rules": list(), "allow": True, "allowDns": True } + conf = { "rules": list(), "allow": True, "allowDns": True, "allowIcmp": True } try: tree = xml.etree.ElementTree.parse(self.firewall_conf) @@ -1439,6 +1440,7 @@ class QubesAppVm(QubesCowVm): conf["allow"] = (root.get("policy") == "allow") conf["allowDns"] = (root.get("dns") == "allow") + conf["allowIcmp"] = (root.get("icmp") == "allow") for element in root: rule = {} From 4e78284e4fc0ddb29bb075720a19937cb643653f Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 23 Mar 2011 09:31:44 +0100 Subject: [PATCH 57/64] block.qubes: pass arguments correctly to other scripts --- dom0/restore/block.qubes | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dom0/restore/block.qubes b/dom0/restore/block.qubes index 435418a2..4aee7f31 100755 --- a/dom0/restore/block.qubes +++ b/dom0/restore/block.qubes @@ -28,7 +28,7 @@ hexnumber() process() { if ! [ "x""$1" = "xfile" ] ; then - exec /etc/xen/scripts/block "$@" + exec /etc/xen/scripts/block $ORIG_ARGS fi while true ; do dev=$(losetup -f --show $2) @@ -48,6 +48,8 @@ XENBUS_PATH="${XENBUS_PATH:?}" if ! [ "$1" = "add" ] || ! [ -f /var/run/qubes/fast_block_attach ] ; then exec /etc/xen/scripts/block "$@" fi + +ORIG_ARGS="$@" vars=$(xenstore-read "$XENBUS_PATH/type" "$XENBUS_PATH/params") process $vars From a814b522b9906cf69fde24fa1a9b540a387d8f06 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 23 Mar 2011 09:36:30 +0100 Subject: [PATCH 58/64] Fix permissions on the dvm template directory. Needed in case default_template-dvm VM was created in init scripts, and files are not writeble by group qubes. --- dom0/restore/qvm-create-default-dvm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dom0/restore/qvm-create-default-dvm b/dom0/restore/qvm-create-default-dvm index 0871227a..7692c6ee 100755 --- a/dom0/restore/qvm-create-default-dvm +++ b/dom0/restore/qvm-create-default-dvm @@ -28,8 +28,12 @@ if ! [ -d "/var/lib/qubes/vm-templates/$TEMPLATENAME" ] ; then exit 1 fi DVMTMPL="$TEMPLATENAME"-dvm -if ! [ -d "/var/lib/qubes/appvms/$DVMTMPL" ] ; then +DVMTMPLDIR="/var/lib/qubes/appvms/$DVMTMPL" +if ! [ -d "$DVMTMPLDIR" ] ; then if ! qvm-create -t "$TEMPLATENAME" -l gray "$DVMTMPL" ; then exit 1 ; fi + chgrp qubes "$DVMTMPLDIR" "$DVMTMPLDIR"/* + chmod 660 "$DVMTMPLDIR"/* + chmod 770 "$DVMTMPLDIR" fi if ! /usr/lib/qubes/qubes_prepare_saved_domain.sh \ "$DVMTMPL" "/var/lib/qubes/appvms/$DVMTMPL/dvm-savefile" $SCRIPTNAME ; then From dd9f1a6f7f34be161fd47320dc6c3741041f67a0 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 23 Mar 2011 11:34:01 +0100 Subject: [PATCH 59/64] Move execution of qrexec_agent to qubes_core Previously it was in both qubes_core_appvm and qubes_core_netvm; somehow counterintuitively, qubes_core_netvm executes on appvm, too. So move it to a common place. --- appvm/qubes_core_appvm | 1 - common/qubes_core | 2 ++ netvm/qubes_core_netvm | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appvm/qubes_core_appvm b/appvm/qubes_core_appvm index 25ff7f4c..ed250dd5 100755 --- a/appvm/qubes_core_appvm +++ b/appvm/qubes_core_appvm @@ -55,7 +55,6 @@ start() MEM_CHANGE_THRESHOLD_KB=30000 MEMINFO_DELAY_USEC=100000 /usr/lib/qubes/meminfo-writer $MEM_CHANGE_THRESHOLD_KB $MEMINFO_DELAY_USEC & - /usr/lib/qubes/qrexec_agent 2>/var/log/qubes/qrexec_agent.log & success echo "" diff --git a/common/qubes_core b/common/qubes_core index 26fe514d..c20f7d71 100755 --- a/common/qubes_core +++ b/common/qubes_core @@ -54,6 +54,8 @@ start() fi fi + /usr/lib/qubes/qrexec_agent 2>/var/log/qubes/qrexec_agent.log & + [ -x /rw/config/rc.local ] && /rw/config/rc.local success echo "" diff --git a/netvm/qubes_core_netvm b/netvm/qubes_core_netvm index 2268c018..c4d81dcb 100755 --- a/netvm/qubes_core_netvm +++ b/netvm/qubes_core_netvm @@ -32,7 +32,6 @@ start() /usr/lib/qubes/qubes_setup_dnat_to_ns echo "1" > /proc/sys/net/ipv4/ip_forward fi - /usr/lib/qubes/qrexec_agent 2>/var/log/qubes/qrexec_agent.log & success echo "" From 5350e5cc5bb1c21dbcb60a03185da42b2cf8896c Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 23 Mar 2011 11:46:53 +0100 Subject: [PATCH 60/64] move qrexec_agent out of core-netvm.spec It is already in core-appvm. --- rpm_spec/core-netvm.spec | 3 --- 1 file changed, 3 deletions(-) diff --git a/rpm_spec/core-netvm.spec b/rpm_spec/core-netvm.spec index c970acdb..222e496f 100644 --- a/rpm_spec/core-netvm.spec +++ b/rpm_spec/core-netvm.spec @@ -45,7 +45,6 @@ The Qubes core files for installation inside a Qubes NetVM. %pre %build -make -C ../qrexec make -C ../vchan make -C ../u2mfn @@ -56,7 +55,6 @@ mkdir -p $RPM_BUILD_ROOT/etc/init.d cp qubes_core_netvm $RPM_BUILD_ROOT/etc/init.d/ mkdir -p $RPM_BUILD_ROOT/var/lib/qubes mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes -cp ../qrexec/qrexec_agent $RPM_BUILD_ROOT/usr/lib/qubes cp ../common/qubes_setup_dnat_to_ns $RPM_BUILD_ROOT/usr/lib/qubes cp ../common/qubes_fix_nm_conf.sh $RPM_BUILD_ROOT/usr/lib/qubes mkdir -p $RPM_BUILD_ROOT/etc/dhclient.d @@ -91,7 +89,6 @@ rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) /etc/init.d/qubes_core_netvm -/usr/lib/qubes/qrexec_agent /usr/lib/qubes/qubes_setup_dnat_to_ns /usr/lib/qubes/qubes_fix_nm_conf.sh /etc/dhclient.d/qubes_setup_dnat_to_ns.sh From 0b208e8664d91881be50384a233a8c06d734a603 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 23 Mar 2011 11:48:06 +0100 Subject: [PATCH 61/64] Move libs and /var/run/qubes out of qubes-netvm They are already in core-appvm package. --- rpm_spec/core-netvm.spec | 8 -------- 1 file changed, 8 deletions(-) diff --git a/rpm_spec/core-netvm.spec b/rpm_spec/core-netvm.spec index 222e496f..026ea0fb 100644 --- a/rpm_spec/core-netvm.spec +++ b/rpm_spec/core-netvm.spec @@ -65,10 +65,6 @@ cp ../netvm/30-qubes_external_ip $RPM_BUILD_ROOT/etc/NetworkManager/dispatcher.d mkdir -p $RPM_BUILD_ROOT/var/run/qubes mkdir -p $RPM_BUILD_ROOT/etc/xen/scripts cp ../common/vif-route-qubes $RPM_BUILD_ROOT/etc/xen/scripts -install -D ../vchan/libvchan.so $RPM_BUILD_ROOT/%{_libdir}/libvchan.so -install -D ../u2mfn/libu2mfn.so $RPM_BUILD_ROOT/%{_libdir}/libu2mfn.so - -mkdir -p $RPM_BUILD_ROOT/var/run/qubes %post @@ -95,7 +91,3 @@ rm -rf $RPM_BUILD_ROOT /etc/NetworkManager/dispatcher.d/qubes_nmhook /etc/NetworkManager/dispatcher.d/30-qubes_external_ip /etc/xen/scripts/vif-route-qubes -%dir /var/run/qubes -%{_libdir}/libvchan.so -%{_libdir}/libu2mfn.so -%dir /var/run/qubes From a1f8cd907180c11713b1a066159c79019d6a6852 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 23 Mar 2011 13:26:39 +0100 Subject: [PATCH 62/64] When creating disposablevm object, pass non-None dirpath QubesVm constructor does not like it. --- dom0/qvm-core/qubes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dom0/qvm-core/qubes.py b/dom0/qvm-core/qubes.py index 1a596021..8d5d3eeb 100755 --- a/dom0/qvm-core/qubes.py +++ b/dom0/qvm-core/qubes.py @@ -1426,7 +1426,7 @@ class QubesDisposableVm(QubesVm): template_vm = kwargs.pop("template_vm") - super(QubesDisposableVm, self).__init__(dir_path=None, **kwargs) + super(QubesDisposableVm, self).__init__(dir_path="/nonexistent", **kwargs) qid = kwargs["qid"] assert template_vm is not None, "Missing template_vm for DisposableVM!" From f9b9b1ade6fc4bd60191c02d3c8e20ac34667b01 Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 23 Mar 2011 13:40:28 +0100 Subject: [PATCH 63/64] qvm-create-default-dvm: fix permissions after creating savefile So, savefile.img and netvm_id.txt are correctly owned as well. --- dom0/restore/qvm-create-default-dvm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dom0/restore/qvm-create-default-dvm b/dom0/restore/qvm-create-default-dvm index 7692c6ee..f1f5c939 100755 --- a/dom0/restore/qvm-create-default-dvm +++ b/dom0/restore/qvm-create-default-dvm @@ -31,9 +31,6 @@ DVMTMPL="$TEMPLATENAME"-dvm DVMTMPLDIR="/var/lib/qubes/appvms/$DVMTMPL" if ! [ -d "$DVMTMPLDIR" ] ; then if ! qvm-create -t "$TEMPLATENAME" -l gray "$DVMTMPL" ; then exit 1 ; fi - chgrp qubes "$DVMTMPLDIR" "$DVMTMPLDIR"/* - chmod 660 "$DVMTMPLDIR"/* - chmod 770 "$DVMTMPLDIR" fi if ! /usr/lib/qubes/qubes_prepare_saved_domain.sh \ "$DVMTMPL" "/var/lib/qubes/appvms/$DVMTMPL/dvm-savefile" $SCRIPTNAME ; then @@ -57,3 +54,7 @@ else chmod 660 $SHMCOPY ln -s $SHMCOPY $CURRENT fi + +chgrp qubes "$DVMTMPLDIR" "$DVMTMPLDIR"/* +chmod 660 "$DVMTMPLDIR"/* +chmod 770 "$DVMTMPLDIR" From 01b75b598758691ac41f51660aa9aac15635ba2c Mon Sep 17 00:00:00 2001 From: Rafal Wojtczuk Date: Wed, 23 Mar 2011 17:47:35 +0100 Subject: [PATCH 64/64] Enable build on non-appvm. --- qrexec/Makefile | 6 +++--- rpm_spec/core-appvm.spec | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qrexec/Makefile b/qrexec/Makefile index 58e48865..6ecd0711 100644 --- a/qrexec/Makefile +++ b/qrexec/Makefile @@ -1,13 +1,13 @@ CC=gcc CFLAGS+=-g -Wall -I../vchan -I../common -XENLIBS=-lvchan -lxenstore -lxenctrl +XENLIBS=-lvchan -lu2mfn -lxenstore -lxenctrl COMMONIOALL=../common/ioall.o all: qrexec_daemon qrexec_agent qrexec_client qrexec_daemon: qrexec_daemon.o unix_server.o $(COMMONIOALL) txrx-vchan.o buffer.o write_stdin.o - $(CC) -g -o qrexec_daemon qrexec_daemon.o unix_server.o $(COMMONIOALL) txrx-vchan.o write_stdin.o buffer.o $(XENLIBS) + $(CC) -L../vchan -L../u2mfn -g -o qrexec_daemon qrexec_daemon.o unix_server.o $(COMMONIOALL) txrx-vchan.o write_stdin.o buffer.o $(XENLIBS) qrexec_agent: qrexec_agent.o exec.o txrx-vchan.o write_stdin.o buffer.o $(COMMONIOALL) - $(CC) -g -o qrexec_agent qrexec_agent.o exec.o txrx-vchan.o write_stdin.o buffer.o $(COMMONIOALL) $(XENLIBS) + $(CC) -L../vchan -L../u2mfn -g -o qrexec_agent qrexec_agent.o exec.o txrx-vchan.o write_stdin.o buffer.o $(COMMONIOALL) $(XENLIBS) qrexec_client: qrexec_client.o $(COMMONIOALL) exec.o $(CC) -g -o qrexec_client qrexec_client.o $(COMMONIOALL) exec.o clean: diff --git a/rpm_spec/core-appvm.spec b/rpm_spec/core-appvm.spec index d36f6aff..82df6ee5 100644 --- a/rpm_spec/core-appvm.spec +++ b/rpm_spec/core-appvm.spec @@ -60,9 +60,9 @@ mkdir -p $RPM_BUILD_ROOT/var/lib/qubes %build make clean all make -C ../common -make -C ../qrexec make -C ../vchan make -C ../u2mfn +make -C ../qrexec %install