The Pedigree Project 0.1
quota-contract-test/main.c
1#define _GNU_SOURCE
2#include <dirent.h>
3#include <errno.h>
4#include <fcntl.h>
5#include <stdint.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include <unistd.h>
10
11#include <sys/mman.h>
12#include <sys/quota.h>
13#include <sys/stat.h>
14#include <sys/wait.h>
15#include <sys/xattr.h>
16
17#define CHECK(condition) \
18 do { \
19 if (!(condition)) { \
20 fprintf(stderr, "QUOTA-CONTRACT: line=%d errno=%d\n", __LINE__, errno); \
21 goto fail; \
22 } \
23 } while (0)
24
25enum { TestUser = 4100, TestGroup = 4200 };
26static char device[512];
27
28static int control(unsigned operation, int type, int id, void* address) {
29 return quotactl((int)((operation << 8) | (unsigned)type), device, id, address);
30}
31
32static int get(int type, int id, struct dqblk* record) {
33 memset(record, 0, sizeof(*record));
34 return control(Q_GETQUOTA, type, id, record);
35}
36
37static int limits(int type, int id, uint64_t blocks, uint64_t inodes) {
38 struct dqblk record = {0};
39 record.dqb_valid = QIF_LIMITS;
40 record.dqb_bhardlimit = blocks;
41 record.dqb_ihardlimit = inodes;
42 return control(Q_SETQUOTA, type, id, &record);
43}
44
45static int find_device(dev_t mounted) {
46 DIR* directory = opendir("/dev/block");
47 if (!directory)
48 return -1;
49 int result = -1;
50 struct dirent* entry;
51 while ((entry = readdir(directory))) {
52 struct stat attributes;
53 if (snprintf(device, sizeof(device), "/dev/block/%s", entry->d_name) >= sizeof(device))
54 continue;
55 if (!stat(device, &attributes) && S_ISBLK(attributes.st_mode) &&
56 attributes.st_rdev == mounted) {
57 result = 0;
58 break;
59 }
60 }
61 closedir(directory);
62 return result;
63}
64
65static int create_quota(const char* path) {
66 const uint32_t old_record[8] = {0};
67 int fd = open(path, O_CREAT | O_EXCL | O_RDWR, 0600);
68 if (fd < 0)
69 return -1;
70 const int result = write(fd, old_record, sizeof(old_record)) == sizeof(old_record) ? 0 : -1;
71 close(fd);
72 return result;
73}
74
75static int child_permissions(const char* creation) {
76 struct dqblk record;
77 if (setgid(TestGroup) || setuid(TestUser))
78 return 1;
79 if (get(USRQUOTA, TestUser, &record) || get(GRPQUOTA, TestGroup, &record))
80 return 2;
81 errno = 0;
82 if (get(USRQUOTA, TestUser + 1, &record) != -1 || errno != EPERM)
83 return 3;
84 errno = 0;
85 if (limits(USRQUOTA, TestUser, 0, 0) != -1 || errno != EPERM)
86 return 4;
87 errno = 0;
88 int fd = open(creation, O_CREAT | O_EXCL | O_WRONLY, 0600);
89 if (fd >= 0) {
90 close(fd);
91 return 5;
92 }
93 return errno == EDQUOT ? 0 : 6;
94}
95
96int main(int argc, char** argv) {
97 if (argc != 2) {
98 fprintf(stderr, "usage: quota-contract-test EXT2-SCRATCH-DIRECTORY\n");
99 return 2;
100 }
101 char directory[512], users[544], groups[544], first[544], second[544], creation[544];
102 int fd = -1, other = -1, quota_fd = -1, result = 1;
103 int user_enabled = 0, group_enabled = 0;
104 struct stat attributes;
105 struct dqblk user_base, group_base, record, destination;
106 uint32_t format = 0;
107 const long page = sysconf(_SC_PAGESIZE);
108 void* denied = MAP_FAILED;
109 snprintf(directory, sizeof(directory), "%s/quota-%ld", argv[1], (long)getpid());
110 snprintf(users, sizeof(users), "%s/quota.user", directory);
111 snprintf(groups, sizeof(groups), "%s/quota.group", directory);
112 snprintf(first, sizeof(first), "%s/first", directory);
113 snprintf(second, sizeof(second), "%s/second", directory);
114 snprintf(creation, sizeof(creation), "%s/child", directory);
115 CHECK(geteuid() == 0 && page > 0);
116 CHECK(mkdir(directory, 0777) == 0 && chmod(directory, 0777) == 0);
117 CHECK(stat(directory, &attributes) == 0 && find_device(attributes.st_dev) == 0);
118 errno = 0;
119 CHECK(control(Q_GETFMT, USRQUOTA, 0, &format) == -1 && errno == ESRCH);
120 errno = 0;
121 CHECK(control(Q_GETFMT, GRPQUOTA, 0, &format) == -1 && errno == ESRCH);
122 CHECK(create_quota(users) == 0 && create_quota(groups) == 0);
123 fd = open(first, O_CREAT | O_EXCL | O_RDWR, 0600);
124 other = open(second, O_CREAT | O_EXCL | O_RDWR, 0600);
125 CHECK(fd >= 0 && other >= 0);
126 CHECK(fchown(fd, TestUser, TestGroup) == 0 && fchown(other, TestUser, TestGroup) == 0);
127 CHECK(fstat(fd, &attributes) == 0 && attributes.st_blksize >= 1024);
128 const uint64_t block = attributes.st_blksize;
129 CHECK(control(Q_QUOTAON, USRQUOTA, QFMT_VFS_OLD, users) == 0);
130 user_enabled = 1;
131 CHECK(control(Q_QUOTAON, GRPQUOTA, QFMT_VFS_OLD, groups) == 0);
132 group_enabled = 1;
133 CHECK(control(Q_GETFMT, USRQUOTA, 0, &format) == 0 && format == QFMT_VFS_OLD);
134 CHECK(get(USRQUOTA, TestUser, &user_base) == 0 && get(GRPQUOTA, TestGroup, &group_base) == 0);
135 CHECK(user_base.dqb_curinodes >= 2 && group_base.dqb_curinodes >= 2);
136 CHECK(limits(USRQUOTA, TestUser, user_base.dqb_curspace / 1024 + 14 * block / 1024,
137 user_base.dqb_curinodes) == 0);
138 CHECK(limits(GRPQUOTA, TestGroup, group_base.dqb_curspace / 1024 + 14 * block / 1024,
139 group_base.dqb_curinodes) == 0);
140 CHECK(fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 13 * block) == 0);
141 CHECK(fstat(fd, &attributes) == 0 && attributes.st_size == 0 &&
142 attributes.st_blocks == 14 * block / 512);
143 CHECK(get(USRQUOTA, TestUser, &record) == 0 &&
144 record.dqb_curspace == user_base.dqb_curspace + 14 * block);
145 errno = 0;
146 CHECK(fallocate(other, FALLOC_FL_KEEP_SIZE, 0, block) == -1 && errno == EDQUOT);
147 errno = 0;
148 CHECK(fsetxattr(other, "user.quota", "x", 1, 0) == -1 && errno == EDQUOT);
149 CHECK(ftruncate(fd, 0) == 0);
150 CHECK(get(USRQUOTA, TestUser, &record) == 0 && record.dqb_curspace == user_base.dqb_curspace);
151 CHECK(fsetxattr(other, "user.quota", "x", 1, 0) == 0);
152 CHECK(get(GRPQUOTA, TestGroup, &record) == 0 &&
153 record.dqb_curspace == group_base.dqb_curspace + block);
154 CHECK(fremovexattr(other, "user.quota") == 0);
155
156 pid_t child = fork();
157 CHECK(child >= 0);
158 if (!child)
159 _exit(child_permissions(creation));
160 int status = 0;
161 CHECK(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0);
162
163 quota_fd = open(users, O_RDWR);
164 CHECK(quota_fd >= 0);
165 errno = 0;
166 CHECK(pwrite(quota_fd, "x", 1, 0) == -1 && errno == EPERM);
167 errno = 0;
168 CHECK(ftruncate(quota_fd, 0) == -1 && errno == EPERM);
169 errno = 0;
170 CHECK(unlink(users) == -1 && errno == EPERM);
171 close(quota_fd);
172 quota_fd = -1;
173 denied = mmap(NULL, page, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
174 CHECK(denied != MAP_FAILED);
175 errno = 0;
176 CHECK(control(Q_SETQUOTA, USRQUOTA, TestUser, denied) == -1 && errno == EFAULT);
177 errno = 0;
178 CHECK(control(Q_GETQUOTA, USRQUOTA, TestUser, denied) == -1 && errno == EFAULT);
179 struct dqblk unsupported = {0};
180 unsupported.dqb_valid = QIF_BLIMITS;
181 unsupported.dqb_bsoftlimit = 1;
182 errno = 0;
183 CHECK(control(Q_SETQUOTA, USRQUOTA, TestUser, &unsupported) == -1 && errno == EOPNOTSUPP);
184 errno = 0;
185 CHECK(quotactl((int)((unsigned)Q_SYNC << 8), first, 0, NULL) == -1 && errno == ENOTBLK);
186
187 CHECK(fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 2 * block) == 0);
188 CHECK(get(USRQUOTA, TestUser + 1, &destination) == 0);
189 CHECK(limits(USRQUOTA, TestUser + 1, destination.dqb_curspace / 1024 + block / 1024,
190 destination.dqb_curinodes + 1) == 0);
191 errno = 0;
192 CHECK(fchown(fd, TestUser + 1, -1) == -1 && errno == EDQUOT);
193 CHECK(fstat(fd, &attributes) == 0 && attributes.st_uid == TestUser);
194 CHECK(unlink(first) == 0);
195 CHECK(get(USRQUOTA, TestUser, &record) == 0 &&
196 record.dqb_curspace == user_base.dqb_curspace + 2 * block);
197 CHECK(close(fd) == 0);
198 fd = -1;
199 CHECK(get(USRQUOTA, TestUser, &record) == 0 && record.dqb_curspace == user_base.dqb_curspace &&
200 record.dqb_curinodes == user_base.dqb_curinodes - 1);
201 CHECK(control(Q_SYNC, USRQUOTA, 0, NULL) == 0);
202 CHECK(control(Q_QUOTAOFF, GRPQUOTA, 0, NULL) == 0);
203 group_enabled = 0;
204 CHECK(control(Q_QUOTAOFF, USRQUOTA, 0, NULL) == 0);
205 user_enabled = 0;
206 CHECK(control(Q_QUOTAON, USRQUOTA, QFMT_VFS_OLD, users) == 0);
207 user_enabled = 1;
208 CHECK(get(USRQUOTA, TestUser, &record) == 0 &&
209 record.dqb_bhardlimit == user_base.dqb_curspace / 1024 + 14 * block / 1024 &&
210 record.dqb_curinodes == user_base.dqb_curinodes - 1);
211 CHECK(control(Q_QUOTAOFF, USRQUOTA, 0, NULL) == 0);
212 user_enabled = 0;
213 result = 0;
214fail:
215 if (denied != MAP_FAILED)
216 munmap(denied, page);
217 if (group_enabled && control(Q_QUOTAOFF, GRPQUOTA, 0, NULL))
218 result = 1;
219 if (user_enabled && control(Q_QUOTAOFF, USRQUOTA, 0, NULL))
220 result = 1;
221 if (quota_fd >= 0)
222 close(quota_fd);
223 if (fd >= 0)
224 close(fd);
225 if (other >= 0)
226 close(other);
227 unlink(first);
228 unlink(second);
229 unlink(creation);
230 unlink(users);
231 unlink(groups);
232 rmdir(directory);
233 puts(result ? "QUOTA-CONTRACT: FAIL" : "QUOTA-CONTRACT: PASS");
234 return result;
235}