eb11cf6989
POSIX requires that a read(2) which can be proved to occur after a
write() has returned returns the new data.
We want here only that other processes in the same VM will see the
file either fully written, or not see it at all. So ensuring that
linkat(2) is called after write is completed should be enough.
Fixes QubesOS/qubes-issues#1257
(cherry picked from commit c1d42f1602
)
228 lines
6.6 KiB
C
228 lines
6.6 KiB
C
#define _GNU_SOURCE /* For O_NOFOLLOW. */
|
|
#include <errno.h>
|
|
#include <ioall.h>
|
|
#include <fcntl.h>
|
|
#include <sys/time.h>
|
|
#include <sys/stat.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <limits.h>
|
|
#include "libqubes-rpc-filecopy.h"
|
|
#include "crc32.h"
|
|
|
|
char untrusted_namebuf[MAX_PATH_LENGTH];
|
|
unsigned long long bytes_limit = 0;
|
|
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
|
|
#ifndef O_TMPFILE
|
|
/* 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)
|
|
#endif
|
|
|
|
void do_exit(int code, const char *last_filename)
|
|
{
|
|
close(0);
|
|
send_status_and_crc(code, last_filename);
|
|
exit(code);
|
|
}
|
|
|
|
void set_size_limit(unsigned long long new_bytes_limit, unsigned long long new_files_limit)
|
|
{
|
|
bytes_limit = new_bytes_limit;
|
|
files_limit = new_files_limit;
|
|
}
|
|
|
|
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;
|
|
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, const char *last_filename) {
|
|
struct result_header hdr;
|
|
struct result_header_ext hdr_ext;
|
|
int saved_errno;
|
|
|
|
saved_errno = errno;
|
|
hdr.error_code = code;
|
|
hdr.crc32 = crc32_sum;
|
|
if (!write_all(1, &hdr, sizeof(hdr)))
|
|
perror("write status");
|
|
if (last_filename) {
|
|
hdr_ext.last_namelen = strlen(last_filename);
|
|
if (!write_all(1, &hdr_ext, sizeof(hdr_ext)))
|
|
perror("write status ext");
|
|
if (!write_all(1, last_filename, hdr_ext.last_namelen))
|
|
perror("write last_filename");
|
|
}
|
|
errno = saved_errno;
|
|
}
|
|
|
|
void fix_times_and_perms(struct file_header *untrusted_hdr,
|
|
const 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, untrusted_name);
|
|
if (utimes(untrusted_name, times)) /* as above */
|
|
do_exit(errno, untrusted_name);
|
|
}
|
|
|
|
|
|
|
|
void process_one_file_reg(struct file_header *untrusted_hdr,
|
|
const char *untrusted_name)
|
|
{
|
|
int ret;
|
|
int fdout = -1;
|
|
|
|
/* make the file inaccessible until fully written */
|
|
if (use_tmpfile) {
|
|
fdout = open(".", O_WRONLY | O_TMPFILE, 0700);
|
|
if (fdout < 0) {
|
|
if (errno==ENOENT)
|
|
/* if it fails, do not attempt further use - most likely kernel too old */
|
|
use_tmpfile = 0;
|
|
else
|
|
do_exit(errno, untrusted_name);
|
|
}
|
|
}
|
|
if (fdout < 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 */
|
|
if (untrusted_hdr->filelen > LLONG_MAX || (bytes_limit && untrusted_hdr->filelen > bytes_limit))
|
|
do_exit(EDQUOT, untrusted_name);
|
|
if (bytes_limit && total_bytes > bytes_limit - untrusted_hdr->filelen)
|
|
do_exit(EDQUOT, untrusted_name);
|
|
total_bytes += untrusted_hdr->filelen;
|
|
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, untrusted_name); // hopefully remote will produce error message
|
|
else
|
|
do_exit(errno, untrusted_name);
|
|
}
|
|
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);
|
|
}
|
|
|
|
|
|
void process_one_file_dir(struct file_header *untrusted_hdr,
|
|
const 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
|
|
struct stat buf;
|
|
if (!mkdir(untrusted_name, 0700)) /* safe because of chroot */
|
|
return;
|
|
if (errno != EEXIST)
|
|
do_exit(errno, untrusted_name);
|
|
if (stat(untrusted_name,&buf) < 0)
|
|
do_exit(errno, untrusted_name);
|
|
total_bytes += buf.st_size;
|
|
/* size accumulated after the fact, so don't check limit here */
|
|
fix_times_and_perms(untrusted_hdr, untrusted_name);
|
|
}
|
|
|
|
void process_one_file_link(struct file_header *untrusted_hdr,
|
|
const char *untrusted_name)
|
|
{
|
|
char untrusted_content[MAX_PATH_LENGTH];
|
|
unsigned int filelen;
|
|
if (untrusted_hdr->filelen > MAX_PATH_LENGTH - 1)
|
|
do_exit(ENAMETOOLONG, untrusted_name);
|
|
filelen = untrusted_hdr->filelen; /* sanitized above */
|
|
total_bytes += filelen;
|
|
if (bytes_limit && total_bytes > bytes_limit)
|
|
do_exit(EDQUOT, untrusted_name);
|
|
if (!read_all_with_crc(0, untrusted_content, filelen))
|
|
do_exit(LEGAL_EOF, untrusted_name); // hopefully remote has produced error message
|
|
untrusted_content[filelen] = 0;
|
|
if (symlink(untrusted_content, untrusted_name)) /* safe because of chroot */
|
|
do_exit(errno, untrusted_name);
|
|
|
|
}
|
|
|
|
void process_one_file(struct file_header *untrusted_hdr)
|
|
{
|
|
unsigned int namelen;
|
|
if (untrusted_hdr->namelen > MAX_PATH_LENGTH - 1)
|
|
do_exit(ENAMETOOLONG, NULL); /* filename too long so not received at all */
|
|
namelen = untrusted_hdr->namelen; /* sanitized above */
|
|
if (!read_all_with_crc(0, untrusted_namebuf, namelen))
|
|
do_exit(LEGAL_EOF, NULL); // 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, untrusted_namebuf);
|
|
if (verbose && !S_ISDIR(untrusted_hdr->mode))
|
|
fprintf(stderr, "%s\n", untrusted_namebuf);
|
|
}
|
|
|
|
int do_unpack(void)
|
|
{
|
|
struct file_header untrusted_hdr;
|
|
total_bytes = total_files = 0;
|
|
/* 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;
|
|
}
|
|
total_files++;
|
|
if (files_limit && total_files > files_limit)
|
|
do_exit(EDQUOT, untrusted_namebuf);
|
|
process_one_file(&untrusted_hdr);
|
|
}
|
|
send_status_and_crc(errno, untrusted_namebuf);
|
|
return errno;
|
|
}
|