From 42e133b753b53005a1673b39a26b8b3221109702 Mon Sep 17 00:00:00 2001 From: Marek Marczykowski Date: Wed, 20 Mar 2013 06:27:32 +0100 Subject: [PATCH] Qrexec common code, qubes.Filecopy common code, udev scripts --- .gitignore | 3 + Makefile | 23 +++ Makefile.builder | 1 + qrexec-lib/Makefile | 22 +++ qrexec-lib/buffer.c | 99 +++++++++++++ qrexec-lib/copy-file.c | 44 ++++++ qrexec-lib/crc32.c | 146 +++++++++++++++++++ qrexec-lib/crc32.h | 7 + qrexec-lib/exec.c | 74 ++++++++++ qrexec-lib/filecopy.h | 32 +++++ qrexec-lib/ioall.c | 112 +++++++++++++++ qrexec-lib/ioall.h | 5 + qrexec-lib/libqrexec-utils.h | 66 +++++++++ qrexec-lib/libqubes-rpc-filecopy.h | 68 +++++++++ qrexec-lib/qrexec.h | 106 ++++++++++++++ qrexec-lib/txrx-vchan.c | 221 +++++++++++++++++++++++++++++ qrexec-lib/unix-server.c | 68 +++++++++ qrexec-lib/unpack.c | 157 ++++++++++++++++++++ qrexec-lib/write-stdin.c | 135 ++++++++++++++++++ rpm_spec/qubes-utils.spec | 54 +++++++ udev/Makefile | 13 ++ udev/udev-block-add-change | 61 ++++++++ udev/udev-block-cleanup | 8 ++ udev/udev-block-remove | 32 +++++ udev/udev-qubes-block.rules | 20 +++ udev/udev-qubes-usb.rules | 10 ++ udev/udev-usb-add-change | 40 ++++++ udev/udev-usb-remove | 9 ++ version | 1 + 29 files changed, 1637 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 Makefile.builder create mode 100644 qrexec-lib/Makefile create mode 100644 qrexec-lib/buffer.c create mode 100644 qrexec-lib/copy-file.c create mode 100644 qrexec-lib/crc32.c create mode 100644 qrexec-lib/crc32.h create mode 100644 qrexec-lib/exec.c create mode 100644 qrexec-lib/filecopy.h create mode 100644 qrexec-lib/ioall.c create mode 100644 qrexec-lib/ioall.h create mode 100644 qrexec-lib/libqrexec-utils.h create mode 100644 qrexec-lib/libqubes-rpc-filecopy.h create mode 100644 qrexec-lib/qrexec.h create mode 100644 qrexec-lib/txrx-vchan.c create mode 100644 qrexec-lib/unix-server.c create mode 100644 qrexec-lib/unpack.c create mode 100644 qrexec-lib/write-stdin.c create mode 100644 rpm_spec/qubes-utils.spec create mode 100644 udev/Makefile create mode 100755 udev/udev-block-add-change create mode 100755 udev/udev-block-cleanup create mode 100755 udev/udev-block-remove create mode 100644 udev/udev-qubes-block.rules create mode 100644 udev/udev-qubes-usb.rules create mode 100755 udev/udev-usb-add-change create mode 100755 udev/udev-usb-remove create mode 100644 version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86149c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +rpm/ +*~ +*.o diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5b3a0fd --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +ifeq ($(shell uname -m),x86_64) +LIBDIR = /usr/lib64 +else +LIBDIR = /usr/lib +endif +INCLUDEDIR = /usr/include + +export LIBDIR INCLUDEDIR + +help: + echo "Use rpmbuild to compile this pacakge" + exit 0 + + +rpms: + rpmbuild --define "_rpmdir rpm/" --define "_builddir ." -bb rpm_spec/qubes-utils.spec +all: + $(MAKE) -C qrexec-lib all + +install: + $(MAKE) -C udev install + $(MAKE) -C qrexec-lib install + diff --git a/Makefile.builder b/Makefile.builder new file mode 100644 index 0000000..7809cf3 --- /dev/null +++ b/Makefile.builder @@ -0,0 +1 @@ +RPM_SPEC_FILES := rpm_spec/qubes-utils.spec diff --git a/qrexec-lib/Makefile b/qrexec-lib/Makefile new file mode 100644 index 0000000..3fc7bb5 --- /dev/null +++ b/qrexec-lib/Makefile @@ -0,0 +1,22 @@ +CC=gcc +CFLAGS+=-I. -g -Wall -pie -fPIC -Wall +COMMONIOALL=ioall.o + +all: libqrexec-utils.a libqubes-rpc-filecopy.a +libqrexec-utils.a: unix-server.o ioall.o buffer.o write-stdin.o exec.o txrx-vchan.o +libqubes-rpc-filecopy.a: ioall.o copy-file.o crc32.o unpack.o + +%.a: + $(AR) rcs $@ $^ +clean: + rm -f *.o *~ *.a + +install: + mkdir -p $(DESTDIR)$(LIBDIR) + cp libqrexec-utils.a $(DESTDIR)$(LIBDIR) + cp libqubes-rpc-filecopy.a $(DESTDIR)$(LIBDIR) + mkdir -p $(DESTDIR)$(INCLUDEDIR) + cp libqrexec-utils.h $(DESTDIR)$(INCLUDEDIR) + cp libqubes-rpc-filecopy.h $(DESTDIR)$(INCLUDEDIR) + cp qrexec.h $(DESTDIR)$(INCLUDEDIR) + diff --git a/qrexec-lib/buffer.c b/qrexec-lib/buffer.c new file mode 100644 index 0000000..3680e2f --- /dev/null +++ b/qrexec-lib/buffer.c @@ -0,0 +1,99 @@ +/* + * 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 "libqrexec-utils.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); +} + +/* +The following two functions can be made much more efficient. +Yet the profiling output show they are not significant CPU hogs, so +we keep them so simple to make them obviously correct. +*/ + +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-lib/copy-file.c b/qrexec-lib/copy-file.c new file mode 100644 index 0000000..d58a14c --- /dev/null +++ b/qrexec-lib/copy-file.c @@ -0,0 +1,44 @@ +#include +#include +#include "filecopy.h" +#include "crc32.h" + +extern void notify_progress(int, int); + +int copy_file(int outfd, int infd, long long size, unsigned long *crc32) +{ + 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 COPY_FILE_READ_EOF; + if (ret < 0) + return COPY_FILE_READ_ERROR; + /* acumulate crc32 if requested */ + if (crc32) + *crc32 = Crc32_ComputeBuf(*crc32, buf, ret); + if (!write_all(outfd, buf, ret)) + return COPY_FILE_WRITE_ERROR; + notify_progress(ret, 0); + written += ret; + } + return COPY_FILE_OK; +} + +char * copy_file_status_to_str(int status) +{ + switch (status) { + case COPY_FILE_OK: return "OK"; + case COPY_FILE_READ_EOF: return "Unexpected end of data while reading"; + case COPY_FILE_READ_ERROR: return "Error reading"; + case COPY_FILE_WRITE_ERROR: return "Error writing"; + default: return "????????"; + } +} diff --git a/qrexec-lib/crc32.c b/qrexec-lib/crc32.c new file mode 100644 index 0000000..88b0627 --- /dev/null +++ b/qrexec-lib/crc32.c @@ -0,0 +1,146 @@ +/*----------------------------------------------------------------------------*\ + * CRC-32 version 2.0.0 by Craig Bruce, 2006-04-29. + * + * This program generates the CRC-32 values for the files named in the + * command-line arguments. These are the same CRC-32 values used by GZIP, + * PKZIP, and ZMODEM. The Crc32_ComputeBuf() can also be detached and + * used independently. + * + * THIS PROGRAM IS PUBLIC-DOMAIN SOFTWARE. + * + * Based on the byte-oriented implementation "File Verification Using CRC" + * by Mark R. Nelson in Dr. Dobb's Journal, May 1992, pp. 64-67. + * + * v1.0.0: original release. + * v1.0.1: fixed printf formats. + * v1.0.2: fixed something else. + * v1.0.3: replaced CRC constant table by generator function. + * v1.0.4: reformatted code, made ANSI C. 1994-12-05. + * v2.0.0: rewrote to use memory buffer & static table, 2006-04-29. +\*----------------------------------------------------------------------------*/ + +#include +#include + +/*----------------------------------------------------------------------------*\ + * Local functions +\*----------------------------------------------------------------------------*/ + +unsigned long Crc32_ComputeBuf( unsigned long inCrc32, const void *buf, + size_t bufLen ); + +/*----------------------------------------------------------------------------*\ + * NAME: + * Crc32_ComputeFile() - compute CRC-32 value for a file + * DESCRIPTION: + * Computes the CRC-32 value for an opened file. + * ARGUMENTS: + * file - file pointer + * outCrc32 - (out) result CRC-32 value + * RETURNS: + * err - 0 on success or -1 on error + * ERRORS: + * - file errors +\*----------------------------------------------------------------------------*/ + +int Crc32_ComputeFile( FILE *file, unsigned long *outCrc32 ) +{ +# define CRC_BUFFER_SIZE 8192 + unsigned char buf[CRC_BUFFER_SIZE]; + size_t bufLen; + + /** accumulate crc32 from file **/ + *outCrc32 = 0; + while (1) { + bufLen = fread( buf, 1, CRC_BUFFER_SIZE, file ); + if (bufLen == 0) { + if (ferror(file)) { + fprintf( stderr, "error reading file\n" ); + goto ERR_EXIT; + } + break; + } + *outCrc32 = Crc32_ComputeBuf( *outCrc32, buf, bufLen ); + } + return( 0 ); + + /** error exit **/ +ERR_EXIT: + return( -1 ); +} + +/*----------------------------------------------------------------------------*\ + * NAME: + * Crc32_ComputeBuf() - computes the CRC-32 value of a memory buffer + * DESCRIPTION: + * Computes or accumulates the CRC-32 value for a memory buffer. + * The 'inCrc32' gives a previously accumulated CRC-32 value to allow + * a CRC to be generated for multiple sequential buffer-fuls of data. + * The 'inCrc32' for the first buffer must be zero. + * ARGUMENTS: + * inCrc32 - accumulated CRC-32 value, must be 0 on first call + * buf - buffer to compute CRC-32 value for + * bufLen - number of bytes in buffer + * RETURNS: + * crc32 - computed CRC-32 value + * ERRORS: + * (no errors are possible) +\*----------------------------------------------------------------------------*/ + +unsigned long Crc32_ComputeBuf( unsigned long inCrc32, const void *buf, + size_t bufLen ) +{ + static const unsigned long crcTable[256] = { + 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535, + 0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD, + 0xE7B82D07,0x90BF1D91,0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D, + 0x6DDDE4EB,0xF4D4B551,0x83D385C7,0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC, + 0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,0x3B6E20C8,0x4C69105E,0xD56041E4, + 0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C, + 0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,0x26D930AC, + 0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F, + 0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB, + 0xB6662D3D,0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F, + 0x9FBFE4A5,0xE8B8D433,0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB, + 0x086D3D2D,0x91646C97,0xE6635C01,0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E, + 0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,0x65B0D9C6,0x12B7E950,0x8BBEB8EA, + 0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,0x4DB26158,0x3AB551CE, + 0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,0x4369E96A, + 0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9, + 0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409, + 0xCE61E49F,0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81, + 0xB7BD5C3B,0xC0BA6CAD,0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739, + 0x9DD277AF,0x04DB2615,0x73DC1683,0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8, + 0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,0xF00F9344,0x8708A3D2,0x1E01F268, + 0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,0xFED41B76,0x89D32BE0, + 0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,0xD6D6A3E8, + 0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B, + 0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF, + 0x4669BE79,0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703, + 0x220216B9,0x5505262F,0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7, + 0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A, + 0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,0x95BF4A82,0xE2B87A14,0x7BB12BAE, + 0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,0x86D3D2D4,0xF1D4E242, + 0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6, + 0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45, + 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D, + 0x3E6E77DB,0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5, + 0x47B2CF7F,0x30B5FFE9,0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605, + 0xCDD70693,0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94, + 0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D }; + unsigned long crc32; + unsigned char *byteBuf; + size_t i; + + /** accumulate crc32 for buffer **/ + crc32 = inCrc32 ^ 0xFFFFFFFF; + byteBuf = (unsigned char*) buf; + for (i=0; i < bufLen; i++) { + crc32 = (crc32 >> 8) ^ crcTable[ (crc32 ^ byteBuf[i]) & 0xFF ]; + } + return( crc32 ^ 0xFFFFFFFF ); +} + +/*----------------------------------------------------------------------------*\ + * END OF MODULE: crc32.c +\*----------------------------------------------------------------------------*/ diff --git a/qrexec-lib/crc32.h b/qrexec-lib/crc32.h new file mode 100644 index 0000000..0aa1846 --- /dev/null +++ b/qrexec-lib/crc32.h @@ -0,0 +1,7 @@ +#ifndef _CRC32_H +#define _CRC32_H + +extern unsigned long Crc32_ComputeBuf( unsigned long inCrc32, const void *buf, + size_t bufLen ); + +#endif /* _CRC32_H */ diff --git a/qrexec-lib/exec.c b/qrexec-lib/exec.c new file mode 100644 index 0000000..a87f505 --- /dev/null +++ b/qrexec-lib/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-lib/filecopy.h b/qrexec-lib/filecopy.h new file mode 100644 index 0000000..c51d68e --- /dev/null +++ b/qrexec-lib/filecopy.h @@ -0,0 +1,32 @@ +#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; +}; + +struct result_header { + unsigned int error_code; + unsigned long crc32; +}; + +enum { + COPY_FILE_OK, + COPY_FILE_READ_EOF, + COPY_FILE_READ_ERROR, + COPY_FILE_WRITE_ERROR +}; + +int copy_file(int outfd, int infd, long long size, unsigned long *crc32); +char *copy_file_status_to_str(int status); +void set_size_limit(long long new_bytes_limit, long long new_files_limit); diff --git a/qrexec-lib/ioall.c b/qrexec-lib/ioall.c new file mode 100644 index 0000000..2a81df4 --- /dev/null +++ b/qrexec-lib/ioall.c @@ -0,0 +1,112 @@ +/* + * 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 + +void perror_wrapper(char * msg) +{ + int prev=errno; + perror(msg); + errno=prev; +} + +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 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 == -1 && errno == EINTR) + continue; + if (ret <= 0) { + 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 == -1 && errno == EINTR) + continue; + if (ret == 0) { + errno = 0; + fprintf(stderr, "EOF\n"); + return 0; + } + if (ret < 0) { + if (errno != EAGAIN) + perror_wrapper("read"); + return 0; + } + if (got_read == 0) { + // force blocking operation on further reads + set_block(fd); + } + got_read += ret; + } +// 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 == -1 && errno == EINTR) + continue; + if (!ret) + break; + if (ret < 0) { + perror_wrapper("read"); + return 0; + } + if (!write_all(fdout, buf, ret)) { + perror_wrapper("write"); + return 0; + } + } + return 1; +} diff --git a/qrexec-lib/ioall.h b/qrexec-lib/ioall.h new file mode 100644 index 0000000..c9913e8 --- /dev/null +++ b/qrexec-lib/ioall.h @@ -0,0 +1,5 @@ +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); +void set_nonblock(int fd); +void set_block(int fd); diff --git a/qrexec-lib/libqrexec-utils.h b/qrexec-lib/libqrexec-utils.h new file mode 100644 index 0000000..96095cf --- /dev/null +++ b/qrexec-lib/libqrexec-utils.h @@ -0,0 +1,66 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * Copyright (C) 2013 Marek Marczykowski + * + * 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 + +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); + + +void do_fork_exec(char *cmdline, int *pid, int *stdin_fd, int *stdout_fd, + int *stderr_fd); +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(int fd, void *buf, int size); +int read_all_vchan_ext(void *buf, int size); +int write_all(int fd, 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); +void set_nonblock(int fd); +void set_block(int fd); + +int get_server_socket(char *); +int do_accept(int s); + +enum { + WRITE_STDIN_OK = 0x200, + WRITE_STDIN_BUFFERED, + WRITE_STDIN_ERROR +}; + +int flush_client_data(int fd, int client_id, struct buffer *buffer); +int write_stdin(int fd, int client_id, 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-lib/libqubes-rpc-filecopy.h b/qrexec-lib/libqubes-rpc-filecopy.h new file mode 100644 index 0000000..9f9fbae --- /dev/null +++ b/qrexec-lib/libqubes-rpc-filecopy.h @@ -0,0 +1,68 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * Copyright (C) 2013 Marek Marczykowski + * + * 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. + * + */ + +#ifndef _LIBQUBES_RPC_FILECOPY_H +#define _LIBQUBES_RPC_FILECOPY_H + +#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; +}; + +struct result_header { + unsigned int error_code; + unsigned long crc32; +}; + +enum { + COPY_FILE_OK, + COPY_FILE_READ_EOF, + COPY_FILE_READ_ERROR, + COPY_FILE_WRITE_ERROR +}; + +int copy_file(int outfd, int infd, long long size, unsigned long *crc32); +char *copy_file_status_to_str(int status); +void set_size_limit(long long new_bytes_limit, long long new_files_limit); +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); +void set_nonblock(int fd); +void set_block(int fd); + +extern unsigned long Crc32_ComputeBuf( unsigned long inCrc32, const void *buf, + size_t bufLen ); + +extern int do_unpack(); + +#endif /* _LIBQUBES_RPC_FILECOPY_H */ diff --git a/qrexec-lib/qrexec.h b/qrexec-lib/qrexec.h new file mode 100644 index 0000000..a2d02d5 --- /dev/null +++ b/qrexec-lib/qrexec.h @@ -0,0 +1,106 @@ +/* + * 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. + * + */ + +/* See also http://wiki.qubes-os.org/trac/wiki/Qrexec */ + +#define QREXEC_DAEMON_SOCKET_DIR "/var/run/qubes" +#define MAX_FDS 256 +#define MAX_DATA_CHUNK 4096 + +#define REXEC_PORT 512 + +#define QREXEC_AGENT_TRIGGER_PATH "/var/run/qubes/qrexec-agent" +#define QREXEC_AGENT_FDPASS_PATH "/var/run/qubes/qrexec-agent-fdpass" +#define MEMINFO_WRITER_PIDFILE "/var/run/meminfo-writer.pid" +#define QUBES_RPC_MULTIPLEXER_PATH "/usr/lib/qubes/qubes-rpc-multiplexer" + +#define QUBES_RPC_MAGIC_CMD "QUBESRPC" + +enum { + /* messages from qrexec_client to qrexec_daemon (both in dom0) */ + /* start process in VM and pass its stdin/out/err to dom0 */ + MSG_CLIENT_TO_SERVER_EXEC_CMDLINE = 0x100, + /* start process in VM discarding its stdin/out/err (connect to /dev/null) */ + MSG_CLIENT_TO_SERVER_JUST_EXEC, + /* connect to existing process in VM to receive its stdin/out/err + * struct connect_existing_params passed as data */ + MSG_CLIENT_TO_SERVER_CONNECT_EXISTING, + + /* messages qrexec_daemon(dom0)->qrexec_agent(VM) */ + /* same as MSG_CLIENT_TO_SERVER_CONNECT_EXISTING */ + MSG_SERVER_TO_AGENT_CONNECT_EXISTING, + /* same as MSG_CLIENT_TO_SERVER_EXEC_CMDLINE */ + MSG_SERVER_TO_AGENT_EXEC_CMDLINE, + /* same as MSG_CLIENT_TO_SERVER_JUST_EXEC */ + MSG_SERVER_TO_AGENT_JUST_EXEC, + /* pass data to process stdin */ + MSG_SERVER_TO_AGENT_INPUT, + /* detach from process; qrexec_agent should close pipes to process + * stdin/out/err; it's up to the VM child process if it cause its termination */ + MSG_SERVER_TO_AGENT_CLIENT_END, + + /* flow control, qrexec_daemon->qrexec_agent */ + /* suspend reading of named fd from child process */ + MSG_XOFF, + /* resume reading of named fd from child process */ + MSG_XON, + + /* messages qrexec_agent(VM)->qrexec_daemon(dom0) */ + /* pass data from process stdout */ + MSG_AGENT_TO_SERVER_STDOUT, + /* pass data from process stderr */ + MSG_AGENT_TO_SERVER_STDERR, + /* inform that process terminated and pass its exit code; this should be + * send after all data from stdout/err are send */ + MSG_AGENT_TO_SERVER_EXIT_CODE, + /* call Qubes RPC service + * struct trigger_connect_params passed as data */ + MSG_AGENT_TO_SERVER_TRIGGER_CONNECT_EXISTING, + + /* messages qrexec_daemon->qrexec_client (both in dom0) */ + /* same as MSG_AGENT_TO_SERVER_STDOUT */ + MSG_SERVER_TO_CLIENT_STDOUT, + /* same as MSG_AGENT_TO_SERVER_STDERR */ + MSG_SERVER_TO_CLIENT_STDERR, + /* same as MSG_AGENT_TO_SERVER_EXIT_CODE */ + MSG_SERVER_TO_CLIENT_EXIT_CODE +}; + +struct server_header { + unsigned int type; + unsigned int client_id; + unsigned int len; +}; + +struct client_header { + unsigned int type; + unsigned int len; +}; + +struct connect_existing_params { + char ident[32]; +}; + +struct trigger_connect_params { + char exec_index[64]; + char target_vmname[32]; + struct connect_existing_params process_fds; +}; diff --git a/qrexec-lib/txrx-vchan.c b/qrexec-lib/txrx-vchan.c new file mode 100644 index 0000000..61b04f8 --- /dev/null +++ b/qrexec-lib/txrx-vchan.c @@ -0,0 +1,221 @@ +/* + * 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 + +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 + +#ifdef XENCTRL_HAS_XC_INTERFACE +static xc_interface *xc_handle = NULL; +#else +static int xc_handle = -1; +#endif +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 timespec tv = { 1, 100000000 }; + sigset_t empty_set; + + sigemptyset(&empty_set); + + vfd = libvchan_fd_for_select(ctrl); + FD_SET(vfd, rdset); + if (vfd > max) + max = vfd; + max++; + ret = pselect(max, rdset, wrset, NULL, &tv, &empty_set); + 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))); + +#ifdef XENCTRL_HAS_XC_INTERFACE + xc_handle = xc_interface_open(NULL, 0, 0); + if (!xc_handle) { +#else + xc_handle = xc_interface_open(); + if (xc_handle < 0) { +#endif + perror("xc_interface_open"); + exit(1); + } + return name; +} diff --git a/qrexec-lib/unix-server.c b/qrexec-lib/unix-server.c new file mode 100644 index 0000000..d4a1518 --- /dev/null +++ b/qrexec-lib/unix-server.c @@ -0,0 +1,68 @@ +/* + * 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(char *socket_address) +{ + struct sockaddr_un sockname; + int s; + + 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-lib/unpack.c b/qrexec-lib/unpack.c new file mode 100644 index 0000000..3a8ce3b --- /dev/null +++ b/qrexec-lib/unpack.c @@ -0,0 +1,157 @@ +#define _GNU_SOURCE /* For O_NOFOLLOW. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "filecopy.h" +#include "crc32.h" + +char untrusted_namebuf[MAX_PATH_LENGTH]; +long long bytes_limit = 0; +long long files_limit = 0; +long long total_bytes = 0; +long long total_files = 0; + +void set_size_limit(long long new_bytes_limit, long long new_files_limit) +{ + bytes_limit = new_bytes_limit; + files_limit = new_files_limit; +} + +unsigned long crc32_sum = 0; +int read_all_with_crc(int fd, void *buf, int size) { + int ret; + ret = read_all(fd, buf, size); + if (ret) + crc32_sum = Crc32_ComputeBuf(crc32_sum, buf, size); + return ret; +} + +void send_status_and_crc(int code) { + struct result_header hdr; + int saved_errno; + + saved_errno = errno; + hdr.error_code = code; + hdr.crc32 = crc32_sum; + if (!write_all(1, &hdr, sizeof(hdr))) + perror("write status"); + errno = saved_errno; +} + +void do_exit(int code) +{ + close(0); + send_status_and_crc(code); + exit(code); +} + +void fix_times_and_perms(struct file_header *untrusted_hdr, + char *untrusted_name) +{ + struct timeval times[2] = + { {untrusted_hdr->atime, untrusted_hdr->atime_nsec / 1000}, + {untrusted_hdr->mtime, + untrusted_hdr->mtime_nsec / 1000} + }; + if (chmod(untrusted_name, untrusted_hdr->mode & 07777)) /* safe because of chroot */ + do_exit(errno); + if (utimes(untrusted_name, times)) /* as above */ + do_exit(errno); +} + + + +void process_one_file_reg(struct file_header *untrusted_hdr, + char *untrusted_name) +{ + int ret; + int fdout = open(untrusted_name, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0700); /* safe because of chroot */ + if (fdout < 0) + do_exit(errno); + total_bytes += untrusted_hdr->filelen; + if (bytes_limit && total_bytes > bytes_limit) + do_exit(EDQUOT); + ret = copy_file(fdout, 0, untrusted_hdr->filelen, &crc32_sum); + if (ret != COPY_FILE_OK) { + if (ret == COPY_FILE_READ_EOF + || ret == COPY_FILE_READ_ERROR) + do_exit(LEGAL_EOF); // hopefully remote will produce error message + else + do_exit(errno); + } + close(fdout); + fix_times_and_perms(untrusted_hdr, untrusted_name); +} + + +void process_one_file_dir(struct file_header *untrusted_hdr, + char *untrusted_name) +{ +// 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(untrusted_name, 0700)) /* safe because of chroot */ + return; + if (errno != EEXIST) + do_exit(errno); + fix_times_and_perms(untrusted_hdr, untrusted_name); +} + +void process_one_file_link(struct file_header *untrusted_hdr, + char *untrusted_name) +{ + char untrusted_content[MAX_PATH_LENGTH]; + unsigned int filelen; + if (untrusted_hdr->filelen > MAX_PATH_LENGTH - 1) + do_exit(ENAMETOOLONG); + filelen = untrusted_hdr->filelen; /* sanitized above */ + if (!read_all_with_crc(0, untrusted_content, filelen)) + do_exit(LEGAL_EOF); // hopefully remote has produced error message + untrusted_content[filelen] = 0; + if (symlink(untrusted_content, untrusted_name)) /* safe because of chroot */ + do_exit(errno); + +} + +void process_one_file(struct file_header *untrusted_hdr) +{ + unsigned int namelen; + if (untrusted_hdr->namelen > MAX_PATH_LENGTH - 1) + do_exit(ENAMETOOLONG); + namelen = untrusted_hdr->namelen; /* sanitized above */ + if (!read_all_with_crc(0, untrusted_namebuf, namelen)) + do_exit(LEGAL_EOF); // hopefully remote has produced error message + untrusted_namebuf[namelen] = 0; + if (S_ISREG(untrusted_hdr->mode)) + process_one_file_reg(untrusted_hdr, untrusted_namebuf); + else if (S_ISLNK(untrusted_hdr->mode)) + process_one_file_link(untrusted_hdr, untrusted_namebuf); + else if (S_ISDIR(untrusted_hdr->mode)) + process_one_file_dir(untrusted_hdr, untrusted_namebuf); + else + do_exit(EINVAL); +} + +int do_unpack() +{ + struct file_header untrusted_hdr; + /* initialize checksum */ + crc32_sum = 0; + while (read_all_with_crc(0, &untrusted_hdr, sizeof untrusted_hdr)) { + /* check for end of transfer marker */ + if (untrusted_hdr.namelen == 0) { + errno = 0; + break; + } + process_one_file(&untrusted_hdr); + total_files++; + if (files_limit && total_files > files_limit) + do_exit(EDQUOT); + } + send_status_and_crc(errno); + return errno; +} diff --git a/qrexec-lib/write-stdin.c b/qrexec-lib/write-stdin.c new file mode 100644 index 0000000..8447de8 --- /dev/null +++ b/qrexec-lib/write-stdin.c @@ -0,0 +1,135 @@ +/* + * 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 "qrexec.h" +#include "libqrexec-utils.h" + +/* +There is buffered data in "buffer" for client id "client_id", and select() +reports that "fd" is writable. Write as much as possible to fd, if all sent, +notify the peer that this client's pipe is no longer full. +*/ +int flush_client_data(int fd, int client_id, 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; + } + // we previously called buffer_remove(buffer, len) + // it will be wrong if we change MAX_DATA_CHUNK to something large + // as pipes writes are atomic only to PIPE_MAX limit + buffer_remove(buffer, ret); + len = buffer_len(buffer); + if (!len) { + struct server_header s_hdr; + s_hdr.type = MSG_XON; + s_hdr.client_id = client_id; + s_hdr.len = 0; + write_all_vchan_ext(&s_hdr, sizeof s_hdr); + return WRITE_STDIN_OK; + } + } + +} + +/* +Write "len" bytes from "data" to "fd". If not all written, buffer the rest +to "buffer", and notify the peer that the client "client_id" pipe is full via +MSG_XOFF message. +*/ +int write_stdin(int fd, int client_id, 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; + } + 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; + + if (errno != EAGAIN) + return WRITE_STDIN_ERROR; + + buffer_append(buffer, data + written, + len - written); + + s_hdr.type = MSG_XOFF; + s_hdr.client_id = client_id; + s_hdr.len = 0; + write_all_vchan_ext(&s_hdr, sizeof s_hdr); + + return WRITE_STDIN_BUFFERED; + } + written += ret; + } + return WRITE_STDIN_OK; + +} + +/* +Data feed process has exited, so we need to clear all control structures for +the client. However, if we have buffered data for the client (which is rare btw), +fire&forget a separate process to flush them. +*/ +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); +} diff --git a/rpm_spec/qubes-utils.spec b/rpm_spec/qubes-utils.spec new file mode 100644 index 0000000..e5cff41 --- /dev/null +++ b/rpm_spec/qubes-utils.spec @@ -0,0 +1,54 @@ +%define version %(cat version) +%if 0%{?qubes_builder} +%define _builddir %(pwd) +%endif + +Name: qubes-utils +Version: %{version} +Release: 1%{?dist} +Summary: Common Linux files for Qubes Dom0 and VM + +Group: Qubes +License: GPL +URL: http://www.qubes-os.org + +Requires: udev + +%description +Common Linux files for Qubes Dom0 and VM + +%package devel +Summary: Development headers for qubes-utils +Release: 1%{?dist} + +%description devel +Development header and files for qubes-utils + +%prep + + +%build +make all + +%install +make install DESTDIR=%{buildroot} + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root,-) +/etc/udev/rules.d/99-qubes-*.rules +/usr/libexec/qubes/udev-* + + +%files devel +%defattr(-,root,root,-) +/usr/include/libqrexec-utils.h +/usr/include/libqubes-rpc-filecopy.h +/usr/include/qrexec.h +%{_libdir}/libqrexec-utils.a +%{_libdir}/libqubes-rpc-filecopy.a + +%changelog + diff --git a/udev/Makefile b/udev/Makefile new file mode 100644 index 0000000..af956bf --- /dev/null +++ b/udev/Makefile @@ -0,0 +1,13 @@ +all: + +install: + mkdir -p $(DESTDIR)/etc/udev/rules.d + cp udev-qubes-block.rules $(DESTDIR)/etc/udev/rules.d/99-qubes-block.rules + cp udev-qubes-usb.rules $(DESTDIR)/etc/udev/rules.d/99-qubes-usb.rules + + mkdir -p $(DESTDIR)/usr/libexec/qubes + cp udev-block-add-change $(DESTDIR)/usr/libexec/qubes/ + cp udev-block-remove $(DESTDIR)/usr/libexec/qubes/ + cp udev-block-cleanup $(DESTDIR)/usr/libexec/qubes/ + cp udev-usb-add-change $(DESTDIR)/usr/libexec/qubes/ + cp udev-usb-remove $(DESTDIR)/usr/libexec/qubes/ diff --git a/udev/udev-block-add-change b/udev/udev-block-add-change new file mode 100755 index 0000000..7b65a2d --- /dev/null +++ b/udev/udev-block-add-change @@ -0,0 +1,61 @@ +#!/bin/bash + +NAME=${DEVNAME#/dev/} +DESC="${ID_MODEL} (${ID_FS_LABEL})" +SIZE=$[ $(cat /sys/$DEVPATH/size) * 512 ] +MODE=w +XS_KEY="qubes-block-devices/$NAME" + +xs_remove() { + if [ "$QUBES_EXPOSED" == "1" ]; then + xenstore-rm "$XS_KEY" + fi + echo QUBES_EXPOSED=0 +} + +# Ignore mounted... +if fgrep -q $DEVNAME /proc/mounts; then + xs_remove + exit 0 +fi +# ... and used by device-mapper +if [ -n "`ls -A /sys/$DEVPATH/holders 2> /dev/null`" ]; then + xs_remove + exit 0 +fi +# ... and "empty" loop devices +if [ "$MAJOR" -eq 7 -a ! -d /sys/$DEVPATH/loop ]; then + xs_remove + exit 0 +fi + +# Special case for CD +if [ "$ID_TYPE" = "cd" ]; then + if [ "$ID_CDROM_MEDIA" != "1" ]; then + # Hide empty cdrom drive + xs_remove + exit 0 + fi + MODE=r +fi + +# Special description for loop devices +if [ -d /sys/$DEVPATH/loop ]; then + DESC=$(cat /sys/$DEVPATH/loop/backing_file) +fi + +# Get lock only in dom0 - there are so many block devices so it causes xenstore +# deadlocks sometimes. +if [ -f /etc/qubes-release ]; then + # Skip xenstore-write if cannot obtain lock. This can mean very early system startup + # stage without /run mounted (or populated). Devices will be rediscovered later + # by qubes-core startup script. + exec 9>>/var/run/qubes/block-xenstore.lock || exit 0 + flock 9 +fi + +xenstore-write "$XS_KEY/desc" "$DESC" "$XS_KEY/size" "$SIZE" "$XS_KEY/mode" "$MODE" +echo QUBES_EXPOSED=1 + +# Make sure that block backend is loaded +/sbin/modprobe xen-blkback 2> /dev/null || /sbin/modprobe blkbk diff --git a/udev/udev-block-cleanup b/udev/udev-block-cleanup new file mode 100755 index 0000000..bc4535f --- /dev/null +++ b/udev/udev-block-cleanup @@ -0,0 +1,8 @@ +#!/bin/sh + +DEVID=$[ $MAJOR * 256 + $MINOR ] + +XS_PATH="device/vbd/$DEVID" + +# Double check that DEVID is not empty +[ -n "$DEVID" ] && xenstore-rm $XS_PATH diff --git a/udev/udev-block-remove b/udev/udev-block-remove new file mode 100755 index 0000000..21502c3 --- /dev/null +++ b/udev/udev-block-remove @@ -0,0 +1,32 @@ +#!/bin/sh + +NAME=${DEVNAME#/dev/} +XS_KEY="qubes-block-devices/$NAME" +xenstore-rm "$XS_KEY" + +# If device was connected to some VM - detach it +# Notice: this can be run also in VM, so we cannot use xl... + +device_detach() { + xs_path=$1 + + xenstore-write $xs_path/online 0 $xs_path/state 5 + + # Wait for backend to finish dev shutdown + try=30 + # -lt will break loop also when 'state' will be empty + while [ "`xenstore-read $xs_path/state 2> /dev/null`" -lt 6 ]; do + try=$[ $try - 1 ] + [ "$try" -le 0 ] && break + sleep 0.1 + done + xenstore-rm $xs_path +} + +for XS_DEV_PATH in `xenstore-ls -f backend/vbd | grep 'backend/vbd/[0-9]*/[0-9]* ' | cut -f 1 -d ' '`; do + CUR_DEVICE=`xenstore-read "$XS_DEV_PATH/params"` + if [ "$CUR_DEVICE" == "$DEVNAME" ]; then + device_detach "$XS_DEV_PATH" + exit 0 + fi +done diff --git a/udev/udev-qubes-block.rules b/udev/udev-qubes-block.rules new file mode 100644 index 0000000..52c4c26 --- /dev/null +++ b/udev/udev-qubes-block.rules @@ -0,0 +1,20 @@ +# Expose all (except xen-frontend) block devices via xenstore + +# Only block devices are interesting +SUBSYSTEM!="block", GOTO="qubes_block_end" + +# Skip xen-blkfront devices +ENV{MAJOR}=="202", GOTO="qubes_block_end" + +# Skip device-mapper devices +ENV{MAJOR}=="253", GOTO="qubes_block_end" + +IMPORT{db}="QUBES_EXPOSED" +ACTION=="add", IMPORT{program}="/usr/libexec/qubes/udev-block-add-change" +ACTION=="change", IMPORT{program}="/usr/libexec/qubes/udev-block-add-change" +ACTION=="remove", RUN+="/usr/libexec/qubes/udev-block-remove" + +LABEL="qubes_block_end" + +# Cleanup disconnected frontend from xenstore +ACTION=="remove", SUBSYSTEM=="block", ENV{MAJOR}=="202", RUN+="/usr/libexec/qubes/udev-block-cleanup" diff --git a/udev/udev-qubes-usb.rules b/udev/udev-qubes-usb.rules new file mode 100644 index 0000000..cd4e038 --- /dev/null +++ b/udev/udev-qubes-usb.rules @@ -0,0 +1,10 @@ +# Expose all USB devices (except block) via xenstore + +# Handle only USB devices +SUBSYSTEM!="usb", GOTO="qubes_usb_end" + +ACTION=="add", IMPORT{program}="/usr/libexec/qubes/udev-usb-add-change" +ACTION=="change", IMPORT{program}="/usr/libexec/qubes/udev-usb-add-change" +ACTION=="remove", RUN+="/usr/libexec/qubes/udev-usb-remove" + +LABEL="qubes_usb_end" diff --git a/udev/udev-usb-add-change b/udev/udev-usb-add-change new file mode 100755 index 0000000..fe966e0 --- /dev/null +++ b/udev/udev-usb-add-change @@ -0,0 +1,40 @@ +#!/bin/sh + +## +## This script is invoked by udev rules whenever USB device appears or +## changes. This happens in usbvm domain (or dom0 if USB controller +## drivers are in dom0). The script records information about available +## USB devices into XS directory, making it available to qvm-usb tool +## running in dom0. +## + +# FIXME: Ignore USB hubs and other wierd devices (see also in udev-usb-remove). +[ "`echo $TYPE | cut -f1 -d/`" = "9" ] && exit 0 +[ "$DEVTYPE" != "usb_device" ] && exit 0 + +# xenstore doesn't allow dot in key name +XSNAME=`basename ${DEVPATH} | tr . _` + +# FIXME: For some devices (my Cherry keyboard) ID_SERIAL does not +# contain proper human-readable name, should find better method to +# build devide description. +#DESC=`python -c "dev='%d-%d' % (int('${BUSNUM}'.lstrip('0')), (int('${DEVNUM}'.lstrip('0'))-1)); from xen.util import vusb_util; print vusb_util.get_usbdevice_info(dev);"` +DESC="${ID_VENDOR_ID}:${ID_MODEL_ID} ${ID_SERIAL}" + +VERSION=`cat /sys/$DEVPATH/version` +if [ "${VERSION}" = " 1.00" -o "${VERSION}" = " 1.10" ] ; then + VERSION=1 +elif [ "${VERSION}" = " 2.00" ] ; then + VERSION=2 +else + # FIXME: silently ignoring devices with unexpected USB version + exit 0 +fi + +XS_KEY="qubes-usb-devices/$XSNAME" + +xenstore-write "$XS_KEY/desc" "$DESC" +xenstore-write "$XS_KEY/usb-ver" "$VERSION" + +# Make sure PVUSB backend driver is loaded. +/sbin/modprobe xen-usbback 2> /dev/null || /sbin/modprobe usbbk diff --git a/udev/udev-usb-remove b/udev/udev-usb-remove new file mode 100755 index 0000000..aa562fa --- /dev/null +++ b/udev/udev-usb-remove @@ -0,0 +1,9 @@ +#!/bin/sh + +# FIXME: Ignore USB hubs. +[ "`echo $TYPE | cut -f1 -d/`" = "9" ] && exit 0 + +NAME=`basename ${DEVPATH} | tr . _` +XS_KEY="qubes-usb-devices/$NAME" + +xenstore-rm "$XS_KEY" diff --git a/version b/version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/version @@ -0,0 +1 @@ +1.0