The Pedigree Project 0.1
file-copy.c
1#define _GNU_SOURCE
2#include <unistd.h>
3
4#include "contract.h"
5#include <sys/mman.h>
6#include <sys/stat.h>
7
8static int ordinary(int kind) {
9 int failed = 0;
10 struct tf_file input = {.fd = -1}, output = {.fd = -1};
11 const size_t length = 3 * TF_CHUNK + 37;
12 struct stat st;
13 CHECK(!tf_create(&input, "/tmp", length, 11) && !tf_create(&output, "/tmp", 0, 0));
14 CHECK(tf_copy(kind, input.fd, NULL, output.fd, NULL, length + 99) == (ssize_t)length);
15 CHECK(!tf_verify(output.fd, 0, length, 0, 11) && !tf_verify(input.fd, 0, length, 0, 11));
16 CHECK(lseek(input.fd, 0, SEEK_CUR) == (off_t)length &&
17 lseek(output.fd, 0, SEEK_CUR) == (off_t)length);
18 CHECK(!fstat(output.fd, &st) && st.st_size == (off_t)length);
19 CHECK(tf_copy(kind, input.fd, NULL, output.fd, NULL, 17) == 0);
20 CHECK(tf_copy(kind, input.fd, NULL, output.fd, NULL, 0) == 0);
21 CHECK(lseek(input.fd, 0, SEEK_CUR) == (off_t)length &&
22 lseek(output.fd, 0, SEEK_CUR) == (off_t)length);
23 CHECK(lseek(input.fd, length - 7, SEEK_SET) == (off_t)length - 7);
24 CHECK(lseek(output.fd, 0, SEEK_SET) == 0);
25 CHECK(tf_copy(kind, input.fd, NULL, output.fd, NULL, 0x80000000ULL) == 7);
26 CHECK(!tf_verify(output.fd, 0, 7, length - 7, 11));
27out:
28 tf_destroy(&output);
29 tf_destroy(&input);
30 return failed;
31}
32static int access_and_seals(int kind) {
33 int failed = 0, readonly = -1, writeonly = -1, sealed = -1;
34 struct tf_file input = {.fd = -1}, output = {.fd = -1};
35 CHECK(!tf_create(&input, "/tmp", 64, 13) && !tf_create(&output, "/tmp", 64, 29));
36 CHECK((readonly = open(output.path, O_RDONLY)) >= 0 &&
37 (writeonly = open(input.path, O_WRONLY)) >= 0);
38 CHECK(tf_copy(kind, writeonly, NULL, output.fd, NULL, 1) == -1 && errno == EBADF);
39 CHECK(tf_copy(kind, input.fd, NULL, readonly, NULL, 1) == -1 && errno == EBADF);
40 CHECK(tf_copy(kind, -1, NULL, output.fd, NULL, 0) == -1 && errno == EBADF);
41 CHECK(!fcntl(output.fd, F_SETFL, O_APPEND));
42 CHECK(tf_copy(kind, input.fd, NULL, output.fd, NULL, 1) == -1 &&
43 errno == (kind == TF_SENDFILE ? EINVAL : EBADF));
44 CHECK(tf_copy(kind, input.fd, NULL, output.fd, NULL, 0) == -1 &&
45 errno == (kind == TF_SENDFILE ? EINVAL : EBADF));
46 CHECK(!tf_verify(output.fd, 0, 64, 0, 29));
47 CHECK(lseek(input.fd, 0, SEEK_CUR) == 0 && lseek(output.fd, 0, SEEK_CUR) == 0);
48 CHECK(!fcntl(output.fd, F_SETFL, 0) && !fcntl(input.fd, F_SETFL, O_APPEND));
49 CHECK(tf_copy(kind, input.fd, NULL, output.fd, NULL, 4) == 4);
50 CHECK(!tf_verify(output.fd, 0, 4, 0, 13));
51 /* Both memfds share the private filesystem; CFR must reach write policy. */
52 sealed = memfd_create("transfer-output", MFD_ALLOW_SEALING);
53 CHECK(sealed >= 0 && !ftruncate(sealed, 64));
54 int source = memfd_create("transfer-input", MFD_ALLOW_SEALING);
55 CHECK(source >= 0);
56 close(writeonly);
57 writeonly = source;
58 CHECK(write(source, "seal", 4) == 4 && lseek(source, 0, SEEK_SET) == 0);
59 CHECK(!fcntl(sealed, F_ADD_SEALS, F_SEAL_WRITE));
60 CHECK(tf_copy(kind, source, NULL, sealed, NULL, 4) == -1 && errno == EPERM);
61 CHECK(lseek(source, 0, SEEK_CUR) == 0 && lseek(sealed, 0, SEEK_CUR) == 0);
62 unsigned char byte = 1;
63 CHECK(pread(sealed, &byte, 1, 0) == 1 && byte == 0);
64out:
65 if (sealed >= 0)
66 close(sealed);
67 if (writeonly >= 0)
68 close(writeonly);
69 if (readonly >= 0)
70 close(readonly);
71 tf_destroy(&output);
72 tf_destroy(&input);
73 return failed;
74}
75static int cross_filesystem(void) {
76 int failed = 0;
77 struct tf_file input = {.fd = -1}, output = {.fd = -1};
78 struct stat left, right;
79 CHECK(!tf_create(&input, "/tmp", 32, 3) && !tf_create(&output, "", 32, 7));
80 CHECK(!fstat(input.fd, &left) && !fstat(output.fd, &right) && left.st_dev != right.st_dev);
81 CHECK(copy_file_range(input.fd, NULL, output.fd, NULL, 4, 0) == -1 && errno == EXDEV);
82 CHECK(!tf_verify(output.fd, 0, 32, 0, 7));
83 CHECK(lseek(input.fd, 0, SEEK_CUR) == 0 && lseek(output.fd, 0, SEEK_CUR) == 0);
84out:
85 tf_destroy(&output);
86 tf_destroy(&input);
87 return failed;
88}
89static int partial_backend(int kind) {
90 int failed = 0, input = -1, output = -1;
91 unsigned char buffer[4096];
92 struct stat st;
93 CHECK((input = memfd_create("partial-input", MFD_ALLOW_SEALING)) >= 0);
94 CHECK((output = memfd_create("partial-output", MFD_ALLOW_SEALING)) >= 0);
95 for (size_t offset = 0; offset < TF_CHUNK + 17;) {
96 size_t count = TF_CHUNK + 17 - offset;
97 if (count > sizeof(buffer))
98 count = sizeof(buffer);
99 for (size_t n = 0; n < count; ++n)
100 buffer[n] = tf_pattern(offset + n, 59);
101 CHECK(pwrite(input, buffer, count, offset) == (ssize_t)count);
102 offset += count;
103 }
104 CHECK(!ftruncate(output, TF_CHUNK) && !fcntl(output, F_ADD_SEALS, F_SEAL_GROW));
105 /* The first complete bounce fits, while the next write cannot extend EOF. */
106 CHECK(tf_copy(kind, input, NULL, output, NULL, TF_CHUNK + 17) == TF_CHUNK);
107 CHECK(lseek(input, 0, SEEK_CUR) == TF_CHUNK && lseek(output, 0, SEEK_CUR) == TF_CHUNK);
108 CHECK(!tf_verify(output, 0, TF_CHUNK, 0, 59));
109 CHECK(!fstat(output, &st) && st.st_size == TF_CHUNK);
110 CHECK(tf_copy(kind, input, NULL, output, NULL, 17) == -1 && errno == EPERM);
111 CHECK(lseek(input, 0, SEEK_CUR) == TF_CHUNK && lseek(output, 0, SEEK_CUR) == TF_CHUNK);
112out:
113 if (output >= 0)
114 close(output);
115 if (input >= 0)
116 close(input);
117 return failed;
118}
119int transfer_file_copy(void) {
120 return ordinary(TF_SENDFILE) || ordinary(TF_COPY_RANGE) || access_and_seals(TF_SENDFILE) ||
121 access_and_seals(TF_COPY_RANGE) || cross_filesystem() || partial_backend(TF_SENDFILE) ||
122 partial_backend(TF_COPY_RANGE);
123}