The Pedigree Project 0.1
xattr-contract-test/permissions.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/stat.h>
9#include <sys/xattr.h>
10
11static int owner_checks(struct xa_file* files, int read_fd) {
12 int failed = 0;
13 const char* names[] = {"user.keep"};
14 CHECK(!xa_unprivileged(60001, 60001, NULL, 0));
15 CHECK(fgetxattr(read_fd, "user.keep", NULL, 0) == -1 && errno == EACCES);
16 struct xa_file readonly = files[0];
17 readonly.fd = read_fd;
18 CHECK(!xa_names(&readonly, XA_FD, names, 1));
19 CHECK(!fsetxattr(read_fd, "user.keep", "w", 1, XATTR_REPLACE));
20 CHECK(!fremovexattr(read_fd, "user.keep"));
21 CHECK(!setxattr(files[0].path, "user.path", "p", 1, 0));
22 CHECK(!lremovexattr(files[0].path, "user.path"));
23 CHECK(!xa_value(&files[1], XA_FD, "user.keep", "k", 1));
24 CHECK(fsetxattr(files[1].fd, "user.keep", "x", 1, 0) == -1 && errno == EACCES);
25 CHECK(removexattr(files[1].path, "user.keep") == -1 && errno == EACCES);
26 CHECK(fgetxattr(files[2].fd, "user.keep", NULL, 0) == -1 && errno == EACCES);
27 CHECK(fsetxattr(files[2].fd, "user.keep", "x", 1, 0) == -1 && errno == EACCES);
28 CHECK(!xa_names(&files[2], XA_FD, names, 1));
29 CHECK(!xa_names(&files[2], XA_PATH, names, 1));
30out:
31 return failed;
32}
33
34static int current_permissions(int backend) {
35 int failed = 0, read_fd = -1;
36 pid_t child = -1;
37 struct xa_file files[3] = {{.fd = -1}, {.fd = -1}, {.fd = -1}};
38 const mode_t modes[] = {0200, 0400, 0000};
39 for (int n = 0; n < 3; ++n) {
40 CHECK(!xa_create(&files[n], backend, 0));
41 CHECK(!fsetxattr(files[n].fd, "user.keep", "k", 1, 0));
42 if (!n)
43 CHECK((read_fd = open(files[n].path, O_RDONLY | O_CLOEXEC)) >= 0);
44 CHECK(!fchown(files[n].fd, 60001, 60001));
45 CHECK(!fchmod(files[n].fd, modes[n]));
46 struct stat status;
47 CHECK(!fstat(files[n].fd, &status));
48 CHECK(status.st_uid == 60001 && status.st_gid == 60001 && (status.st_mode & 0777) == modes[n]);
49 }
50 CHECK((child = fork()) >= 0);
51 if (!child)
52 _exit(owner_checks(files, read_fd));
53 int result = xa_reap(child, 8000);
54 child = -1;
55 CHECK(!result);
56 CHECK(fgetxattr(files[0].fd, "user.keep", NULL, 0) == -1 && errno == ENODATA);
57 CHECK(!xa_value(&files[1], XA_FD, "user.keep", "k", 1));
58out:
59 if (child > 0) {
60 kill(child, SIGKILL);
61 xa_reap(child, 1000);
62 }
63 if (read_fd >= 0)
64 close(read_fd);
65 for (int n = 0; n < 3; ++n)
66 xa_close(&files[n]);
67 return failed;
68}
69
70static int group_checks(struct xa_file* file, int member) {
71 int failed = 0;
72 const gid_t group = 60003;
73 CHECK(!xa_unprivileged(60001, 60001, member ? &group : NULL, member ? 1 : 0));
74 if (member) {
75 CHECK(!xa_value(file, XA_FD, "user.group", "g", 1));
76 CHECK(!setxattr(file->path, "user.group", "G", 1, XATTR_REPLACE));
77 } else {
78 CHECK(fgetxattr(file->fd, "user.group", NULL, 0) == -1 && errno == EACCES);
79 CHECK(fsetxattr(file->fd, "user.group", "x", 1, 0) == -1 && errno == EACCES);
80 CHECK(fremovexattr(file->fd, "user.group") == -1 && errno == EACCES);
81 }
82out:
83 return failed;
84}
85
86static int supplementary_group(int backend) {
87 int failed = 0;
88 pid_t child = -1;
89 struct xa_file file = {.fd = -1};
90 CHECK(!xa_create(&file, backend, 0));
91 CHECK(!fsetxattr(file.fd, "user.group", "g", 1, 0));
92 CHECK(!fchown(file.fd, 60002, 60003));
93 CHECK(!fchmod(file.fd, 0660));
94 for (int member = 0; member < 2; ++member) {
95 CHECK((child = fork()) >= 0);
96 if (!child)
97 _exit(group_checks(&file, member));
98 int result = xa_reap(child, 8000);
99 child = -1;
100 CHECK(!result);
101 }
102 CHECK(!xa_value(&file, XA_FD, "user.group", "G", 1));
103out:
104 if (child > 0) {
105 kill(child, SIGKILL);
106 xa_reap(child, 1000);
107 }
108 xa_close(&file);
109 return failed;
110}
111
112static int directory_child(struct xa_file* sticky, struct xa_file* owned, struct xa_file* hidden) {
113 int failed = 0;
114 const char* names[] = {"user.directory"};
115 CHECK(!xa_unprivileged(60001, 60001, NULL, 0));
116 CHECK(!xa_value(sticky, XA_PATH, "user.directory", "d", 1));
117 CHECK(!xa_names(sticky, XA_LINK, names, 1));
118 CHECK(fsetxattr(sticky->fd, "user.directory", "x", 1, 0) == -1 && errno == EPERM);
119 CHECK(lremovexattr(sticky->path, "user.directory") == -1 && errno == EPERM);
120 CHECK(!fsetxattr(owned->fd, "user.owner", "o", 1, 0));
121 CHECK(!xa_value(owned, XA_PATH, "user.owner", "o", 1));
122 CHECK(!lremovexattr(owned->path, "user.owner"));
123 CHECK(getxattr(hidden->path, "user.hidden", NULL, 0) == -1 && errno == EACCES);
124 CHECK(llistxattr(hidden->path, NULL, 0) == -1 && errno == EACCES);
125 CHECK(!xa_value(hidden, XA_FD, "user.hidden", "h", 1));
126out:
127 return failed;
128}
129
130static int directories(int backend) {
131 int failed = 0;
132 pid_t child = -1;
133 struct xa_file sticky = {.fd = -1}, owned = {.fd = -1}, parent = {.fd = -1};
134 struct xa_file hidden = {.fd = -1};
135 CHECK(!xa_create(&sticky, backend, 1));
136 CHECK(!xa_create(&owned, backend, 1));
137 CHECK(!xa_create(&parent, backend, 1));
138 for (int how = XA_PATH; how <= XA_FD; ++how) {
139 CHECK(!xa_set(&sticky, how, "user.directory", "d", 1, XATTR_CREATE));
140 CHECK(!xa_value(&sticky, how, "user.directory", "d", 1));
141 CHECK(!xa_remove(&sticky, how, "user.directory"));
142 }
143 CHECK(!fsetxattr(sticky.fd, "user.directory", "d", 1, 0));
144 CHECK(!fchmod(sticky.fd, 01777));
145 CHECK(!fchown(owned.fd, 60001, 60001));
146 CHECK(!fchmod(owned.fd, 01777));
147 hidden.backend = backend;
148 CHECK(snprintf(hidden.path, sizeof(hidden.path), "%s/child", parent.path) <
149 (int)sizeof(hidden.path));
150 CHECK((hidden.fd = open(hidden.path, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600)) >= 0);
151 CHECK(!fchmod(hidden.fd, 0666));
152 CHECK(!fsetxattr(hidden.fd, "user.hidden", "h", 1, 0));
153 CHECK((child = fork()) >= 0);
154 if (!child)
155 _exit(directory_child(&sticky, &owned, &hidden));
156 int result = xa_reap(child, 8000);
157 child = -1;
158 CHECK(!result);
159out:
160 if (child > 0) {
161 kill(child, SIGKILL);
162 xa_reap(child, 1000);
163 }
164 xa_close(&hidden);
165 xa_close(&parent);
166 xa_close(&owned);
167 xa_close(&sticky);
168 return failed;
169}
170
171static int fifo_policy(void) {
172 int failed = 0;
173 char path[96];
174 snprintf(path, sizeof(path), "/tmp/xattr-fifo-%ld", (long)getpid());
175 CHECK(!mkfifo(path, 0600));
176 CHECK(getxattr(path, "user.fifo", NULL, 0) == -1 && errno == ENODATA);
177 CHECK(setxattr(path, "user.fifo", "f", 1, 0) == -1 && errno == EPERM);
178 CHECK(lremovexattr(path, "user.fifo") == -1 && errno == EPERM);
179out:
180 unlink(path);
181 return failed;
182}
183
184int xa_permissions(void) {
185 for (int backend = XA_RAMFS; backend <= XA_EXT2; ++backend)
186 if (current_permissions(backend) || supplementary_group(backend) || directories(backend))
187 return 1;
188 return fifo_policy();
189}