The Pedigree Project 0.1
xattr-contract-test/lifetime.c
1#define _GNU_SOURCE
2#include <fcntl.h>
3#include <signal.h>
4#include <string.h>
5#include <unistd.h>
6
7#include "contract.h"
8#include <sys/mman.h>
9#include <sys/socket.h>
10#include <sys/stat.h>
11#include <sys/xattr.h>
12
13static int aliases(int backend) {
14 int failed = 0, alias = -1, duplicate = -1, reused = -1;
15 pid_t child = -1;
16 struct xa_file file = {.fd = -1}, replacement = {.fd = -1};
17 struct xa_file retained = {.fd = -1};
18 char moved[224] = {0};
19 CHECK(!xa_create(&file, backend, 0));
20 CHECK(!fsetxattr(file.fd, "user.slot", "old", 3, 0));
21 CHECK((alias = xa_open_alias(&file)) >= 0);
22 CHECK((duplicate = dup(file.fd)) >= 0);
23 retained = file;
24 retained.fd = duplicate;
25 struct stat first, second;
26 CHECK(!fstat(file.fd, &first) && !fstat(alias, &second));
27 CHECK(first.st_ino == second.st_ino && first.st_dev == second.st_dev);
28 CHECK(!fsetxattr(alias, "user.slot", "alias", 5, XATTR_REPLACE));
29 CHECK(!xa_value(&retained, XA_FD, "user.slot", "alias", 5));
30 CHECK((child = fork()) >= 0);
31 if (!child) {
32 int result = xa_value(&retained, XA_FD, "user.slot", "alias", 5) ||
33 fsetxattr(duplicate, "user.slot", "child", 5, XATTR_REPLACE);
34 _exit(result ? 1 : 0);
35 }
36 int result = xa_reap(child, 8000);
37 child = -1;
38 CHECK(!result);
39 CHECK(!xa_value(&file, XA_FD, "user.slot", "child", 5));
40 if (file.path[0]) {
41 snprintf(moved, sizeof(moved), "%s.moved", file.path);
42 CHECK(!rename(file.path, moved));
43 file.path[0] = 0;
44 char value[5];
45 CHECK(getxattr(moved, "user.slot", value, sizeof(value)) == 5 && !memcmp(value, "child", 5));
46 CHECK(!unlink(moved));
47 moved[0] = 0;
48 }
49 CHECK(!xa_create(&replacement, XA_MEMFD, 0));
50 CHECK(!fsetxattr(replacement.fd, "user.slot", "new", 3, 0));
51 int number = file.fd;
52 CHECK(!close(file.fd));
53 file.fd = -1;
54 CHECK((reused = dup2(replacement.fd, number)) == number);
55 struct xa_file current = replacement;
56 current.fd = reused;
57 CHECK(!xa_value(&current, XA_FD, "user.slot", "new", 3));
58 CHECK(!xa_value(&retained, XA_FD, "user.slot", "child", 5));
59 CHECK(!close(alias));
60 alias = -1;
61 CHECK(!fsetxattr(duplicate, "user.slot", "last", 4, XATTR_REPLACE));
62 CHECK(!xa_value(&retained, XA_FD, "user.slot", "last", 4));
63out:
64 if (child > 0) {
65 kill(child, SIGKILL);
66 xa_reap(child, 1000);
67 }
68 if (reused >= 0)
69 close(reused);
70 if (duplicate >= 0)
71 close(duplicate);
72 if (alias >= 0)
73 close(alias);
74 if (moved[0])
75 unlink(moved);
76 xa_close(&replacement);
77 xa_close(&file);
78 return failed;
79}
80
81static int send_descriptor(int socket, int fd) {
82 char byte = 'f';
83 struct iovec vector = {&byte, 1};
84 union {
85 struct cmsghdr aligned;
86 unsigned char bytes[CMSG_SPACE(sizeof(int))];
87 } control = {0};
88 struct msghdr message = {.msg_iov = &vector,
89 .msg_iovlen = 1,
90 .msg_control = control.bytes,
91 .msg_controllen = sizeof(control.bytes)};
92 struct cmsghdr* header = CMSG_FIRSTHDR(&message);
93 header->cmsg_level = SOL_SOCKET;
94 header->cmsg_type = SCM_RIGHTS;
95 header->cmsg_len = CMSG_LEN(sizeof(int));
96 memcpy(CMSG_DATA(header), &fd, sizeof(fd));
97 return sendmsg(socket, &message, 0) == 1 ? 0 : -1;
98}
99
100static int receive_descriptor(int socket) {
101 char byte = 0;
102 struct iovec vector = {&byte, 1};
103 union {
104 struct cmsghdr aligned;
105 unsigned char bytes[CMSG_SPACE(sizeof(int))];
106 } control = {0};
107 struct msghdr message = {.msg_iov = &vector,
108 .msg_iovlen = 1,
109 .msg_control = control.bytes,
110 .msg_controllen = sizeof(control.bytes)};
111 if (recvmsg(socket, &message, MSG_CMSG_CLOEXEC) != 1 || byte != 'f' ||
112 (message.msg_flags & MSG_CTRUNC))
113 return -1;
114 struct cmsghdr* header = CMSG_FIRSTHDR(&message);
115 if (!header || header->cmsg_level != SOL_SOCKET || header->cmsg_type != SCM_RIGHTS ||
116 header->cmsg_len != CMSG_LEN(sizeof(int)))
117 return -1;
118 int fd;
119 memcpy(&fd, CMSG_DATA(header), sizeof(fd));
120 return fd;
121}
122
123static int queued_descriptor(int backend) {
124 int failed = 0, sockets[2] = {-1, -1}, ready[2] = {-1, -1}, gate[2] = {-1, -1};
125 pid_t child = -1;
126 struct xa_file file = {.fd = -1};
127 CHECK(!xa_create(&file, backend, 0));
128 CHECK(!fsetxattr(file.fd, "user.queued", "queued", 6, 0));
129 CHECK(!socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sockets));
130 CHECK(!pipe(ready) && !pipe(gate));
131 CHECK((child = fork()) >= 0);
132 if (!child) {
133 close(sockets[0]);
134 close(ready[0]);
135 close(gate[1]);
136 close(file.fd);
137 if (xa_send(ready[1], 'r') || xa_receive(gate[0], 'g'))
138 _exit(2);
139 file.fd = receive_descriptor(sockets[1]);
140 if (file.fd < 0 || xa_value(&file, XA_FD, "user.queued", "queued", 6) ||
141 fsetxattr(file.fd, "user.queued", "received", 8, XATTR_REPLACE) ||
142 xa_value(&file, XA_FD, "user.queued", "received", 8))
143 _exit(3);
144 close(file.fd);
145 _exit(0);
146 }
147 close(sockets[1]);
148 sockets[1] = -1;
149 close(ready[1]);
150 ready[1] = -1;
151 close(gate[0]);
152 gate[0] = -1;
153 CHECK(!xa_receive(ready[0], 'r'));
154 CHECK(!send_descriptor(sockets[0], file.fd));
155 xa_close(&file);
156 CHECK(!xa_send(gate[1], 'g'));
157 int result = xa_reap(child, 8000);
158 child = -1;
159 CHECK(!result);
160out:
161 if (child > 0) {
162 kill(child, SIGKILL);
163 xa_reap(child, 1000);
164 }
165 for (int n = 0; n < 2; ++n) {
166 if (sockets[n] >= 0)
167 close(sockets[n]);
168 if (ready[n] >= 0)
169 close(ready[n]);
170 if (gate[n] >= 0)
171 close(gate[n]);
172 }
173 xa_close(&file);
174 return failed;
175}
176
177static int rename_replacement(void) {
178 int failed = 0;
179 struct xa_file source = {.fd = -1}, victim = {.fd = -1};
180 CHECK(!xa_create(&source, XA_EXT2, 0));
181 CHECK(!xa_create(&victim, XA_EXT2, 0));
182 CHECK(!fsetxattr(source.fd, "user.inode", "source", 6, 0));
183 CHECK(!fsetxattr(victim.fd, "user.inode", "victim", 6, 0));
184 CHECK(!rename(source.path, victim.path));
185 source.path[0] = 0;
186 CHECK(!xa_value(&victim, XA_PATH, "user.inode", "source", 6));
187 CHECK(!xa_value(&victim, XA_FD, "user.inode", "victim", 6));
188 CHECK(!fremovexattr(victim.fd, "user.inode"));
189 CHECK(!xa_value(&source, XA_FD, "user.inode", "source", 6));
190out:
191 xa_close(&victim);
192 xa_close(&source);
193 return failed;
194}
195
196static int sealed_memfd(void) {
197 int failed = 0;
198 struct xa_file file = {.fd = -1};
199 const int seals = F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_FUTURE_WRITE;
200 CHECK(!xa_create(&file, XA_MEMFD, 0));
201 CHECK(write(file.fd, "content", 7) == 7);
202 CHECK(!fsetxattr(file.fd, "user.sealed", "before", 6, 0));
203 CHECK(!fcntl(file.fd, F_ADD_SEALS, seals));
204 CHECK(fcntl(file.fd, F_GET_SEALS) == seals);
205 CHECK(pwrite(file.fd, "x", 1, 0) == -1 && errno == EPERM);
206 CHECK(!fsetxattr(file.fd, "user.sealed", "after", 5, XATTR_REPLACE));
207 CHECK(!xa_value(&file, XA_FD, "user.sealed", "after", 5));
208 CHECK(!fsetxattr(file.fd, "user.new", NULL, 0, XATTR_CREATE));
209 CHECK(!fremovexattr(file.fd, "user.sealed"));
210 const char* names[] = {"user.new"};
211 CHECK(!xa_names(&file, XA_FD, names, 1));
212out:
213 xa_close(&file);
214 return failed;
215}
216
217int xa_lifetime(void) {
218 for (int backend = XA_MEMFD; backend <= XA_EXT2; ++backend)
219 if (aliases(backend) || queued_descriptor(backend))
220 return 1;
221 return rename_replacement() || sealed_memfd();
222}