diff --git a/qrexec-lib/libqubes-rpc-filecopy.h b/qrexec-lib/libqubes-rpc-filecopy.h index d566345..6cc9d79 100644 --- a/qrexec-lib/libqubes-rpc-filecopy.h +++ b/qrexec-lib/libqubes-rpc-filecopy.h @@ -66,6 +66,8 @@ int copy_file(int outfd, int infd, long long size, unsigned long *crc32); const char *copy_file_status_to_str(int status); void set_size_limit(unsigned long long new_bytes_limit, unsigned long long new_files_limit); void set_verbose(int value); +/* register open fd to /proc/PID/fd of this process */ +void set_procfs_fd(int value); int write_all(int fd, const void *buf, int size); int read_all(int fd, void *buf, int size); int copy_fd_all(int fdout, int fdin); diff --git a/qrexec-lib/unpack.c b/qrexec-lib/unpack.c index d7787d1..9a44e72 100644 --- a/qrexec-lib/unpack.c +++ b/qrexec-lib/unpack.c @@ -18,9 +18,19 @@ unsigned long long files_limit = 0; unsigned long long total_bytes = 0; unsigned long long total_files = 0; int verbose = 0; +int use_tmpfile = 0; +int procdir_fd = -1; void send_status_and_crc(int code, const char *last_filename); +/* copy from asm-generic/fcntl.h */ +#ifndef __O_TMPFILE +#define __O_TMPFILE 020000000 +#endif +/* a horrid kludge trying to make sure that this will fail on old kernels */ +#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY) +#define O_TMPFILE_MASK (__O_TMPFILE | O_DIRECTORY | O_CREAT) + void do_exit(int code, const char *last_filename) { close(0); @@ -39,6 +49,12 @@ void set_verbose(int value) verbose = value; } +void set_procfs_fd(int value) +{ + procdir_fd = value; + use_tmpfile = 1; +} + unsigned long crc32_sum = 0; int read_all_with_crc(int fd, void *buf, int size) { int ret; @@ -88,7 +104,15 @@ void process_one_file_reg(struct file_header *untrusted_hdr, const char *untrusted_name) { int ret; - int fdout = open(untrusted_name, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0700); /* safe because of chroot */ + int fdout; + + /* make the file inaccessible until fully written */ + fdout = open(".", O_WRONLY | O_TMPFILE, 0700); + if (fdout < 0 && errno==ENOENT) { + /* if it fails, do not attempt further use - most likely kernel too old */ + use_tmpfile = 0; + fdout = open(untrusted_name, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0000); /* safe because of chroot */ + } if (fdout < 0) do_exit(errno, untrusted_name); /* sizes are signed elsewhere */ @@ -105,6 +129,13 @@ void process_one_file_reg(struct file_header *untrusted_hdr, else do_exit(errno, untrusted_name); } + fdatasync(fdout); + if (use_tmpfile) { + char fd_str[7]; + snprintf(fd_str, sizeof(fd_str), "%d", fdout); + if (linkat(procdir_fd, fd_str, AT_FDCWD, untrusted_name, AT_SYMLINK_FOLLOW) < 0) + do_exit(errno, untrusted_name); + } close(fdout); fix_times_and_perms(untrusted_hdr, untrusted_name); }