15#include <sys/syscall.h>
16#include <sys/sysmacros.h>
19#define CHECK(expression) \
21 if (!(expression)) { \
22 fprintf(stderr, "METADATA-CONTRACT: line=%d errno=%d\n", __LINE__, errno); \
26#define ERROR(expression, expected) \
29 CHECK((expression) == -1 && errno == (expected)); \
32_Static_assert(SYS_truncate == 76 && SYS_lchown == 94 && SYS_mknodat == 259 &&
33 SYS_fchmodat == 268 && SYS_utimensat == 280 && SYS_statx == 332 &&
35 "Linux amd64 metadata routes");
36_Static_assert(
sizeof(
struct statx) == 256,
"statx output ABI");
38static int make_file(
int directory,
const char* name, mode_t mode) {
39 int fd = openat(directory, name, O_CREAT | O_EXCL | O_RDWR, mode);
42 return close(fd) == 0;
45static int truncate_cases(
int directory,
const char* absolute) {
46 unsigned char written[8192], readback[8192];
48 int fd = openat(directory,
"data", O_RDWR);
50 memset(written, 0xa7,
sizeof(written));
51 CHECK(write(fd, written,
sizeof(written)) ==
sizeof(written));
52 CHECK(lseek(fd, 5000, SEEK_SET) == 5000);
53 CHECK(truncate(absolute, 4111) == 0);
54 CHECK(fstat(fd, &st) == 0 && st.st_size == 4111 && lseek(fd, 0, SEEK_CUR) == 5000);
55 CHECK(syscall(SYS_truncate, absolute, (off_t)
sizeof(written)) == 0);
56 CHECK(pread(fd, readback,
sizeof(readback), 0) ==
sizeof(readback));
57 for (
size_t i = 0; i <
sizeof(readback); ++i)
58 CHECK(readback[i] == (i < 4111 ? 0xa7 : 0));
59 CHECK(lseek(fd, 0, SEEK_CUR) == 5000);
60 ERROR(truncate(absolute, -1), EINVAL);
61 ERROR(syscall(SYS_truncate, (
const char*)1, (off_t)1), EFAULT);
62 char absent[PATH_MAX];
63 snprintf(absent,
sizeof(absent),
"%s-missing", absolute);
64 ERROR(truncate(absent, 0), ENOENT);
65 CHECK(close(fd) == 0);
69static int mode_cases(
int directory,
int object,
int link,
const char* absolute) {
71 CHECK(syscall(SYS_fchmodat, directory,
"data", 0620, 0x40000000) == 0);
72 CHECK(fstat(
object, &st) == 0 && (st.st_mode & 07777) == 0620);
73 CHECK(syscall(SYS_fchmodat2, directory,
"data", 0640, 0) == 0);
74 CHECK(fchmodat(directory,
"data", 0604, AT_SYMLINK_NOFOLLOW) == 0);
75 CHECK(fstat(
object, &st) == 0 && (st.st_mode & 07777) == 0604);
76 CHECK(syscall(SYS_fchmodat2,
object,
"", 0600, AT_EMPTY_PATH) == 0);
77 CHECK(syscall(SYS_fchmodat2, directory,
"link", 0660, 0) == 0);
78 CHECK(fstat(
object, &st) == 0 && (st.st_mode & 07777) == 0660);
79 CHECK(syscall(SYS_fchmodat2, -123, absolute, 0640, 0) == 0);
80 ERROR(syscall(SYS_fchmodat2, directory,
"data", 0700, 0x40000000), EINVAL);
81 ERROR(syscall(SYS_fchmodat2, -123,
"data", 0700, 0), EBADF);
82 ERROR(syscall(SYS_fchmodat2,
object,
"data", 0700, 0), ENOTDIR);
83 ERROR(syscall(SYS_fchmodat2,
object,
"", 0700, 0), ENOENT);
84 ERROR(syscall(SYS_fchmodat2,
object, (
const char*)1, 0700, 0), EFAULT);
85 ERROR(fchmodat(directory,
"link", 0600, AT_SYMLINK_NOFOLLOW), EOPNOTSUPP);
86 ERROR(syscall(SYS_fchmodat2, link,
"", 0600, AT_EMPTY_PATH), EOPNOTSUPP);
87 ERROR(syscall(SYS_fchmodat2,
object,
"", S_ISUID | 0700, AT_EMPTY_PATH), EOPNOTSUPP);
88 ERROR(syscall(SYS_fchmodat2,
object,
"", S_ISGID | 0700, AT_EMPTY_PATH), EOPNOTSUPP);
89 CHECK(fstat(
object, &st) == 0 && (st.st_mode & 07777) == 0640);
93static int timestamp_cases(
int directory,
int object,
int link,
const char* absolute) {
94 struct timespec times[2] = {{1700000000, 123456789}, {1700000300, 987654321}};
95 struct stat st, originalLink;
96 CHECK(utimensat(directory,
"data", times, 0) == 0);
97 CHECK(fstat(
object, &st) == 0 && st.st_atime == times[0].tv_sec &&
98 st.st_mtime == times[1].tv_sec && st.st_atim.tv_nsec == 0 && st.st_mtim.tv_nsec == 0);
99 times[0] = (
struct timespec){-1, UTIME_OMIT};
100 times[1] = (
struct timespec){1700000400, 0};
101 CHECK(syscall(SYS_utimensat,
object,
"", times, AT_EMPTY_PATH) == 0);
102 CHECK(fstat(
object, &st) == 0 && st.st_atime == 1700000000 && st.st_mtime == 1700000400);
103 times[0].tv_nsec = 1000000000;
104 ERROR(utimensat(directory,
"data", times, 0), EINVAL);
105 times[0].tv_nsec = -1;
106 ERROR(syscall(SYS_utimensat, directory,
"data", times, 0), EINVAL);
107 times[0] = (
struct timespec){-1, 0};
108 ERROR(utimensat(directory,
"data", times, 0), EOVERFLOW);
109 CHECK(fstat(
object, &st) == 0 && st.st_atime == 1700000000 && st.st_mtime == 1700000400);
110 times[0].tv_nsec = times[1].tv_nsec = UTIME_OMIT;
111 CHECK(syscall(SYS_utimensat, -123, (
const char*)1, times, 0x40000000) == 0);
112 times[0] = (
struct timespec){1700000500, 0};
113 times[1] = (
struct timespec){1700000600, 0};
114 CHECK(utimensat(-123, absolute, times, 0) == 0);
115 ERROR(futimens(
object, times), EBADF);
116 int ordinary = openat(directory,
"data", O_RDONLY);
117 CHECK(ordinary >= 0 && futimens(ordinary, times) == 0 && close(ordinary) == 0);
118 CHECK(fstatat(directory,
"link", &originalLink, AT_SYMLINK_NOFOLLOW) == 0);
119 times[0].tv_nsec = UTIME_OMIT;
120 times[1].tv_sec = 1700000700;
121 CHECK(utimensat(directory,
"link", times, AT_SYMLINK_NOFOLLOW) == 0);
122 CHECK(fstat(link, &st) == 0 && st.st_mtime == 1700000700 && st.st_atime == originalLink.st_atime);
123 CHECK(fstat(
object, &st) == 0 && st.st_mtime == 1700000600);
124 times[0] = (
struct timespec){1700000800, 0};
125 times[1].tv_nsec = UTIME_OMIT;
126 CHECK(syscall(SYS_utimensat, link,
"", times, AT_EMPTY_PATH) == 0);
127 CHECK(fstat(link, &st) == 0 && st.st_atime == 1700000800 && st.st_mtime == 1700000700);
128 times[0].tv_nsec = times[1].tv_nsec = UTIME_NOW;
129 const time_t before = time(NULL);
130 CHECK(utimensat(directory,
"data", times, 0) == 0);
131 const time_t after = time(NULL);
132 CHECK(fstat(
object, &st) == 0 && st.st_atime >= before && st.st_atime <= after &&
133 st.st_mtime >= before && st.st_mtime <= after);
134 ERROR(syscall(SYS_utimensat, directory,
"data", (
const void*)1, 0), EFAULT);
135 ERROR(syscall(SYS_utimensat, directory,
"data", NULL, 0x40000000), EINVAL);
139static int ownership_cases(
int directory,
int object,
const char* linkPath) {
141 CHECK(lchown(linkPath, 34567, 34568) == 0);
142 CHECK(fstatat(directory,
"link", &st, AT_SYMLINK_NOFOLLOW) == 0 && S_ISLNK(st.st_mode) &&
143 st.st_uid == 34567 && st.st_gid == 34568);
144 CHECK(fstat(
object, &st) == 0 && st.st_uid == 0 && st.st_gid == 0);
145 CHECK(syscall(SYS_lchown, linkPath, (uid_t)-1, (gid_t)0) == 0);
146 CHECK(fstatat(directory,
"link", &st, AT_SYMLINK_NOFOLLOW) == 0 && st.st_uid == 34567 &&
148 const struct stat original = st;
149 for (
unsigned attempt = 0; time(NULL) <= original.st_ctime && attempt < 4; ++attempt) {
150 const struct timespec delay = {1, 0};
151 CHECK(nanosleep(&delay, NULL) == 0);
153 CHECK(time(NULL) > original.st_ctime);
154 CHECK(lchown(linkPath, (uid_t)-1, (gid_t)-1) == 0);
155 CHECK(fstatat(directory,
"link", &st, AT_SYMLINK_NOFOLLOW) == 0 &&
156 st.st_ctime > original.st_ctime && st.st_uid == original.st_uid &&
157 st.st_gid == original.st_gid && st.st_atime == original.st_atime &&
158 st.st_mtime == original.st_mtime);
159 ERROR(syscall(SYS_lchown, (
const char*)1, (uid_t)0, (gid_t)0), EFAULT);
163static int statx_cases(
int directory,
int object,
int link,
const char* absolute) {
166 unsigned char tail[16];
169 CHECK(fstat(
object, &st) == 0);
170 memset(&output, 0xa5,
sizeof(output));
171 CHECK(syscall(SYS_statx, directory,
"data", 0, STATX_ALL | STATX_MNT_ID, &output.value) == 0);
172 struct statx* sx = &output.value;
173 CHECK((sx->stx_mask & STATX_BASIC_STATS) == STATX_BASIC_STATS);
174 CHECK(!(sx->stx_mask & (STATX_BTIME | STATX_MNT_ID_UNIQUE | STATX_DIOALIGN | STATX_SUBVOL |
175 STATX_WRITE_ATOMIC)));
176 CHECK(sx->stx_mode == st.st_mode && sx->stx_ino == st.st_ino && sx->stx_size == st.st_size &&
177 sx->stx_blocks == st.st_blocks && sx->stx_nlink == st.st_nlink &&
178 sx->stx_uid == st.st_uid && sx->stx_gid == st.st_gid);
179 CHECK(sx->stx_dev_major == major(st.st_dev) && sx->stx_dev_minor == minor(st.st_dev) &&
180 sx->stx_rdev_major == 0 && sx->stx_rdev_minor == 0);
181 CHECK(sx->stx_attributes == 0 && sx->stx_attributes_mask == 0);
182 CHECK(sx->stx_btime.tv_sec == 0 && sx->stx_btime.tv_nsec == 0);
183 CHECK(sx->stx_atime.tv_sec == st.st_atime && sx->stx_mtime.tv_sec == st.st_mtime &&
184 sx->stx_ctime.tv_sec == st.st_ctime);
185 CHECK(sx->stx_atime.tv_nsec == 0 && sx->stx_mtime.tv_nsec == 0 && sx->stx_ctime.tv_nsec == 0);
186 for (
size_t i = 152; i <
sizeof(*sx); ++i)
187 CHECK(((
const unsigned char*)sx)[i] == 0);
188 for (
size_t i = 0; i <
sizeof(output.tail); ++i)
189 CHECK(output.tail[i] == 0xa5);
190 CHECK(statx(-123, absolute, AT_STATX_DONT_SYNC, 0, sx) == 0 && S_ISREG(sx->stx_mode));
191 CHECK(statx(
object,
"", AT_EMPTY_PATH, STATX_BASIC_STATS, sx) == 0 && sx->stx_ino == st.st_ino);
192 CHECK(syscall(SYS_statx,
object, NULL, AT_EMPTY_PATH, STATX_BASIC_STATS, sx) == 0 &&
193 sx->stx_ino == st.st_ino);
194 CHECK(syscall(SYS_statx, AT_FDCWD, NULL, AT_EMPTY_PATH, STATX_BASIC_STATS, sx) == 0 &&
195 S_ISDIR(sx->stx_mode));
196 CHECK(statx(link,
"", AT_EMPTY_PATH, STATX_TYPE, sx) == 0 && S_ISLNK(sx->stx_mode));
197 CHECK(statx(directory,
"link", 0, STATX_TYPE, sx) == 0 && S_ISREG(sx->stx_mode));
198 CHECK(statx(directory,
"link", AT_SYMLINK_NOFOLLOW, STATX_TYPE, sx) == 0 &&
199 S_ISLNK(sx->stx_mode));
200 CHECK(statx(directory,
"null", 0, STATX_TYPE, sx) == 0 && S_ISREG(sx->stx_mode));
201 ERROR(syscall(SYS_statx, directory,
"data", 0, 0x80000000U, sx), EINVAL);
202 ERROR(syscall(SYS_statx, directory,
"data", 0x6000, 0, sx), EINVAL);
203 ERROR(syscall(SYS_statx, directory,
"data", 0x40000000, 0, sx), EINVAL);
204 ERROR(syscall(SYS_statx, -123,
"data", 0, 0, sx), EBADF);
205 ERROR(syscall(SYS_statx,
object,
"data", 0, 0, sx), ENOTDIR);
206 ERROR(syscall(SYS_statx,
object,
"", 0, 0, sx), ENOENT);
207 ERROR(syscall(SYS_statx, directory, NULL, 0, 0, sx), EFAULT);
208 ERROR(syscall(SYS_statx, directory,
"data", 0, 0, (
void*)1), EFAULT);
209 const long page = sysconf(_SC_PAGESIZE);
210 void* boundary = mmap(NULL, 2 * page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
211 CHECK(boundary != MAP_FAILED && page >= 256);
212 CHECK(mprotect((
char*)boundary + page, page, PROT_NONE) == 0);
213 CHECK(syscall(SYS_statx, directory,
"data", 0, 0, (
char*)boundary + page - 256) == 0);
214 ERROR(syscall(SYS_statx, directory,
"data", 0, 0, (
char*)boundary + page - 255), EFAULT);
215 CHECK(munmap(boundary, 2 * page) == 0);
219static int mknod_cases(
int directory,
int object,
const char* absolute) {
221 mode_t previous = umask(0027);
222 CHECK(mknodat(directory,
"regular", S_IFREG | 0666, makedev(1, 3)) == 0);
223 CHECK(syscall(SYS_mknodat, directory,
"plain", 0666, 0) == 0);
224 CHECK(mkfifoat(directory,
"fifo", 0666) == 0);
225 CHECK(syscall(SYS_mknodat, directory,
"raw-fifo", S_IFIFO | 0666, 0) == 0);
227 CHECK(fstatat(directory,
"regular", &st, 0) == 0 && S_ISREG(st.st_mode) &&
228 (st.st_mode & 0777) == 0640 && st.st_rdev == 0 && st.st_size == 0);
229 CHECK(fstatat(directory,
"fifo", &st, 0) == 0 && S_ISFIFO(st.st_mode) &&
230 (st.st_mode & 0777) == 0640);
231 int fd = openat(directory,
"fifo", O_RDWR | O_NONBLOCK);
233 CHECK(fd >= 0 && write(fd,
"q", 1) == 1 && read(fd, &value, 1) == 1 && value ==
'q');
234 CHECK(close(fd) == 0);
235 CHECK(mknodat(-123, absolute, S_IFREG | 0600, 0) == 0);
236 CHECK(mkdirat(directory,
"subdir", 0700) == 0);
237 int retained = openat(directory,
"subdir", O_PATH | O_DIRECTORY);
238 CHECK(retained >= 0);
239 CHECK(renameat(directory,
"subdir", directory,
"moved") == 0);
240 CHECK(mknodat(retained,
"inside", S_IFREG | 0600, 0) == 0);
241 CHECK(fstatat(directory,
"moved/inside", &st, 0) == 0 && S_ISREG(st.st_mode));
242 CHECK(unlinkat(retained,
"inside", 0) == 0 && close(retained) == 0);
243 CHECK(unlinkat(directory,
"moved", AT_REMOVEDIR) == 0);
244 ERROR(mknodat(directory,
"regular", S_IFREG | 0600, 0), EEXIST);
245 ERROR(mknodat(directory,
"link", S_IFREG | 0600, 0), EEXIST);
246 ERROR(mknodat(-123,
"new", S_IFREG | 0600, 0), EBADF);
247 ERROR(mknodat(
object,
"new", S_IFREG | 0600, 0), ENOTDIR);
248 ERROR(mknodat(directory,
"", S_IFREG | 0600, 0), ENOENT);
249 ERROR(mknodat(directory,
".", S_IFIFO | 0600, 0), EEXIST);
250 ERROR(mknodat(directory,
"trailing/", S_IFREG | 0600, 0), ENOENT);
251 ERROR(mknodat(directory,
"bad-directory", S_IFDIR | 0700, 0), EPERM);
252 ERROR(mknodat(directory,
"device", S_IFCHR | 0600, makedev(1, 3)), EOPNOTSUPP);
253 ERROR(mknodat(directory,
"setid", S_IFREG | S_ISUID | 0700, 0), EOPNOTSUPP);
255 memset(longName,
'x', 256);
257 ERROR(mknodat(directory, longName, S_IFIFO | 0600, 0), ENAMETOOLONG);
258 ERROR(syscall(SYS_mknodat, directory, (
const char*)1, S_IFREG | 0600, 0), EFAULT);
262static int denied_cases(
int directory,
const char* absolute,
const char* linkPath) {
263 struct timespec explicit[2] = {{1700000000, 0}, {1700000000, 0}};
264 CHECK(setgroups(0, NULL) == 0 && setgid(42424) == 0 && setuid(42424) == 0);
265 ERROR(truncate(absolute, 0), EACCES);
266 ERROR(syscall(SYS_fchmodat2, directory,
"data", 0777, 0), EPERM);
267 ERROR(lchown(linkPath, 42424, (gid_t)-1), EPERM);
268 ERROR(utimensat(directory,
"data",
explicit, 0), EPERM);
269 ERROR(utimensat(directory,
"data", NULL, 0), EACCES);
270 CHECK(utimensat(directory,
"writable", NULL, 0) == 0);
271 ERROR(utimensat(directory,
"writable",
explicit, 0), EPERM);
272 ERROR(mknodat(directory,
"denied", S_IFIFO | 0600, 0), EACCES);
274 CHECK(statx(directory,
"data", 0, STATX_BASIC_STATS, &st) == 0);
278int main(
int argc,
char** argv) {
279 const char* base = argc > 1 ? argv[1] :
"/tmp";
280 char directoryPath[PATH_MAX], absolute[PATH_MAX], linkPath[PATH_MAX], created[PATH_MAX];
281 snprintf(directoryPath,
sizeof(directoryPath),
"%s/metadata-contract-%ld", base, (
long)getpid());
282 snprintf(absolute,
sizeof(absolute),
"%s/data", directoryPath);
283 snprintf(linkPath,
sizeof(linkPath),
"%s/link", directoryPath);
284 snprintf(created,
sizeof(created),
"%s/absolute", directoryPath);
285 int directory = -1,
object = -1, link = -1, success = 0, createdDirectory = 0;
286 const mode_t originalMask = umask(0);
287 if (geteuid() != 0 || mkdir(directoryPath, 0755))
289 createdDirectory = 1;
290 directory = open(directoryPath, O_PATH | O_DIRECTORY);
291 if (directory < 0 || !make_file(directory,
"data", 0600) || !make_file(directory,
"null", 0600) ||
292 !make_file(directory,
"writable", 0666) || symlinkat(
"data", directory,
"link"))
294 object = openat(directory,
"data", O_PATH);
295 link = openat(directory,
"link", O_PATH | O_NOFOLLOW);
296 if (
object < 0 || link < 0 || !truncate_cases(directory, absolute) ||
297 !mode_cases(directory,
object, link, absolute) ||
298 !timestamp_cases(directory,
object, link, absolute) ||
299 !ownership_cases(directory,
object, linkPath) ||
300 !statx_cases(directory,
object, link, absolute) || !mknod_cases(directory,
object, created))
302 if (syscall(SYS_fchmodat2,
object,
"", 0600, AT_EMPTY_PATH))
304 pid_t child = fork();
306 _exit(denied_cases(directory, absolute, linkPath) ? 0 : 1);
308 if (child < 0 || waitpid(child, &status, 0) != child || !WIFEXITED(status) || WEXITSTATUS(status))
313 fprintf(stderr,
"METADATA-CONTRACT: setup or case failure errno=%d\n", errno);
319 if (directory >= 0) {
320 const char* names[] = {
"data",
"link",
"null",
"writable",
"regular",
"plain",
"fifo",
321 "raw-fifo",
"absolute",
"denied",
"trailing",
"device",
"setid"};
322 for (
size_t i = 0; i <
sizeof(names) /
sizeof(names[0]); ++i)
323 unlinkat(directory, names[i], 0);
324 unlinkat(directory,
"subdir/inside", 0);
325 unlinkat(directory,
"moved/inside", 0);
326 unlinkat(directory,
"subdir", AT_REMOVEDIR);
327 unlinkat(directory,
"moved", AT_REMOVEDIR);
330 if (createdDirectory)
331 rmdir(directoryPath);
332 printf(
"METADATA-CONTRACT: %s base=%s\n", success ?
"PASS" :
"FAIL", base);
333 return success ? 0 : 1;