The Pedigree Project 0.1
xattr-contract-test/fixture.c
1#define _GNU_SOURCE
2#include <fcntl.h>
3#include <grp.h>
4#include <poll.h>
5#include <signal.h>
6#include <stdlib.h>
7#include <string.h>
8#include <time.h>
9#include <unistd.h>
10
11#include "contract.h"
12#include <sys/mman.h>
13#include <sys/stat.h>
14#include <sys/wait.h>
15#include <sys/xattr.h>
16
17int64_t xa_now(void) {
18 struct timespec time;
19 return clock_gettime(CLOCK_MONOTONIC, &time) ? -1
20 : (int64_t)time.tv_sec * 1000000000 + time.tv_nsec;
21}
22
23int xa_reap(pid_t child, int milliseconds) {
24 int64_t now = xa_now(), deadline = now + (int64_t)milliseconds * 1000000;
25 while (now >= 0 && (now = xa_now()) >= 0 && now < deadline) {
26 int status;
27 pid_t result = waitpid(child, &status, WNOHANG);
28 if (result == child)
29 return WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status);
30 if (result < 0 && errno != EINTR)
31 return -1;
32 struct timespec pause = {0, 5000000};
33 nanosleep(&pause, NULL);
34 }
35 kill(child, SIGKILL);
36 while (waitpid(child, NULL, 0) < 0 && errno == EINTR) {
37 }
38 return -1;
39}
40
41int xa_write_all(int fd, const void* buffer, size_t length) {
42 const unsigned char* bytes = buffer;
43 while (length) {
44 ssize_t result = write(fd, bytes, length);
45 if (result < 0 && errno == EINTR)
46 continue;
47 if (result <= 0)
48 return -1;
49 bytes += result;
50 length -= result;
51 }
52 return 0;
53}
54
55int xa_read_all(int fd, void* buffer, size_t length) {
56 unsigned char* bytes = buffer;
57 while (length) {
58 ssize_t result = read(fd, bytes, length);
59 if (result < 0 && errno == EINTR)
60 continue;
61 if (result <= 0)
62 return -1;
63 bytes += result;
64 length -= result;
65 }
66 return 0;
67}
68
69int xa_send(int fd, char byte) {
70 return xa_write_all(fd, &byte, 1);
71}
72
73int xa_receive(int fd, char expected) {
74 int64_t now = xa_now(), deadline = now + 5000000000;
75 while (now >= 0 && (now = xa_now()) >= 0 && now < deadline) {
76 struct pollfd watch = {.fd = fd, .events = POLLIN};
77 int result = poll(&watch, 1, 100);
78 if (result < 0 && errno == EINTR)
79 continue;
80 if (result < 0)
81 return -1;
82 if (!result)
83 continue;
84 char byte;
85 if (!xa_read_all(fd, &byte, 1) && byte == expected)
86 return 0;
87 errno = EIO;
88 return -1;
89 }
90 errno = ETIMEDOUT;
91 return -1;
92}
93
94int xa_create(struct xa_file* file, int backend, int directory) {
95 static unsigned sequence;
96 memset(file, 0, sizeof(*file));
97 file->fd = -1;
98 file->backend = backend;
99 file->directory = directory;
100 if (backend == XA_MEMFD) {
101 if (directory) {
102 errno = EINVAL;
103 return -1;
104 }
105 file->fd = memfd_create("xattr-contract", MFD_CLOEXEC | MFD_ALLOW_SEALING);
106 } else {
107 snprintf(file->path, sizeof(file->path), "%s/xattr-%ld-%u", backend == XA_RAMFS ? "/tmp" : "",
108 (long)getpid(), ++sequence);
109 if (directory && mkdir(file->path, 0700)) {
110 file->path[0] = 0;
111 return -1;
112 }
113 file->fd =
114 open(file->path,
115 directory ? O_RDONLY | O_DIRECTORY | O_CLOEXEC : O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC,
116 0600);
117 if (file->fd < 0) {
118 int saved = errno;
119 if (directory)
120 rmdir(file->path);
121 file->path[0] = 0;
122 errno = saved;
123 }
124 }
125 if (file->fd < 0)
126 fprintf(stderr, "XATTR-CONTRACT: create backend=%d directory=%d errno=%d\n", backend, directory,
127 errno);
128 return file->fd < 0 ? -1 : 0;
129}
130
131int xa_open_alias(const struct xa_file* file) {
132 if (file->directory)
133 return open(file->path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
134 if (file->backend == XA_EXT2) {
135 char alias[224];
136 snprintf(alias, sizeof(alias), "%s.alias", file->path);
137 if (link(file->path, alias))
138 return -1;
139 int fd = open(alias, O_RDWR | O_CLOEXEC), saved = errno;
140 if (unlink(alias)) {
141 saved = errno;
142 if (fd >= 0)
143 close(fd);
144 fd = -1;
145 }
146 errno = saved;
147 return fd;
148 }
149 return file->path[0] ? open(file->path, O_RDWR | O_CLOEXEC) : dup(file->fd);
150}
151
152void xa_close(struct xa_file* file) {
153 if (file->fd >= 0)
154 close(file->fd);
155 if (file->path[0]) {
156 if (file->directory)
157 rmdir(file->path);
158 else
159 unlink(file->path);
160 }
161 file->fd = -1;
162 file->path[0] = 0;
163}
164
165int xa_set(const struct xa_file* file, int how, const char* name, const void* value, size_t size,
166 int flags) {
167 if (how == XA_PATH)
168 return setxattr(file->path, name, value, size, flags);
169 if (how == XA_LINK)
170 return lsetxattr(file->path, name, value, size, flags);
171 return fsetxattr(file->fd, name, value, size, flags);
172}
173
174ssize_t xa_get(const struct xa_file* file, int how, const char* name, void* value, size_t size) {
175 if (how == XA_PATH)
176 return getxattr(file->path, name, value, size);
177 if (how == XA_LINK)
178 return lgetxattr(file->path, name, value, size);
179 return fgetxattr(file->fd, name, value, size);
180}
181
182ssize_t xa_list(const struct xa_file* file, int how, char* names, size_t size) {
183 if (how == XA_PATH)
184 return listxattr(file->path, names, size);
185 if (how == XA_LINK)
186 return llistxattr(file->path, names, size);
187 return flistxattr(file->fd, names, size);
188}
189
190int xa_remove(const struct xa_file* file, int how, const char* name) {
191 if (how == XA_PATH)
192 return removexattr(file->path, name);
193 if (how == XA_LINK)
194 return lremovexattr(file->path, name);
195 return fremovexattr(file->fd, name);
196}
197
198int xa_value(const struct xa_file* file, int how, const char* name, const void* value,
199 size_t size) {
200 unsigned char* actual = malloc(size + 1);
201 if (!actual)
202 return -1;
203 memset(actual, 0xa5, size + 1);
204 int failed = xa_get(file, how, name, NULL, 0) != (ssize_t)size ||
205 xa_get(file, how, name, actual, size + 1) != (ssize_t)size ||
206 (size && memcmp(actual, value, size)) || actual[size] != 0xa5;
207 if (failed)
208 fprintf(stderr, "XATTR-CONTRACT: value %s backend=%d how=%d size=%zu errno=%d\n", name,
209 file->backend, how, size, errno);
210 free(actual);
211 return failed ? -1 : 0;
212}
213
214int xa_names(const struct xa_file* file, int how, const char* const* names, size_t count) {
215 ssize_t length = xa_list(file, how, NULL, 0);
216 if (length < 0 || length > 65536 || count > 128)
217 return -1;
218 char* bytes = malloc((size_t)length + 1);
219 if (!bytes)
220 return -1;
221 unsigned char seen[128] = {0};
222 bytes[length] = (char)0xa5;
223 int failed = xa_list(file, how, bytes, length) != length || (unsigned char)bytes[length] != 0xa5;
224 size_t found = 0;
225 for (size_t offset = 0; !failed && offset < (size_t)length;) {
226 char* end = memchr(bytes + offset, 0, (size_t)length - offset);
227 if (!end || end == bytes + offset) {
228 failed = 1;
229 break;
230 }
231 size_t index = 0;
232 while (index < count && strcmp(bytes + offset, names[index]))
233 ++index;
234 if (index == count || seen[index]) {
235 failed = 1;
236 break;
237 }
238 seen[index] = 1;
239 ++found;
240 offset = (size_t)(end - bytes) + 1;
241 }
242 failed |= found != count;
243 if (failed)
244 fprintf(stderr, "XATTR-CONTRACT: list backend=%d how=%d bytes=%zd names=%zu expected=%zu\n",
245 file->backend, how, length, found, count);
246 free(bytes);
247 return failed ? -1 : 0;
248}
249
250int xa_unprivileged(uid_t uid, gid_t gid, const gid_t* groups, size_t count) {
251 if (setgroups(count, groups) || setgid(gid) || setuid(uid))
252 return -1;
253 if (geteuid() == uid && getegid() == gid)
254 return 0;
255 errno = EPERM;
256 return -1;
257}