1 |
/* |
2 |
* ccs-checkpolicy.c |
3 |
* |
4 |
* TOMOYO Linux's utilities. |
5 |
* |
6 |
* Copyright (C) 2005-2010 NTT DATA CORPORATION |
7 |
* |
8 |
* Version: 1.7.2+ 2010/04/06 |
9 |
* |
10 |
*/ |
11 |
#include "ccstools.h" |
12 |
|
13 |
#define CCS_KEYWORD_AGGREGATOR "aggregator " |
14 |
#define CCS_KEYWORD_ALLOW_CAPABILITY "allow_capability " |
15 |
#define CCS_KEYWORD_ALLOW_CHGRP "allow_chgrp " |
16 |
#define CCS_KEYWORD_ALLOW_CHMOD "allow_chmod " |
17 |
#define CCS_KEYWORD_ALLOW_CHOWN "allow_chown " |
18 |
#define CCS_KEYWORD_ALLOW_CHROOT "allow_chroot " |
19 |
#define CCS_KEYWORD_ALLOW_ENV "allow_env " |
20 |
#define CCS_KEYWORD_ALLOW_IOCTL "allow_ioctl " |
21 |
#define CCS_KEYWORD_ALLOW_MOUNT "allow_mount " |
22 |
#define CCS_KEYWORD_ALLOW_NETWORK "allow_network " |
23 |
#define CCS_KEYWORD_ALLOW_PIVOT_ROOT "allow_pivot_root " |
24 |
#define CCS_KEYWORD_ALLOW_SIGNAL "allow_signal " |
25 |
#define CCS_KEYWORD_ALLOW_UNMOUNT "allow_unmount " |
26 |
#define CCS_KEYWORD_DENY_AUTOBIND "deny_autobind " |
27 |
#define CCS_KEYWORD_DENY_REWRITE "deny_rewrite " |
28 |
#define CCS_KEYWORD_FILE_PATTERN "file_pattern " |
29 |
#define CCS_KEYWORD_MAC_FOR_CAPABILITY "MAC_FOR_CAPABILITY::" |
30 |
#define CCS_KEYWORD_SELECT "select " |
31 |
|
32 |
#define CCS_MAX_PATHNAME_LEN 4000 |
33 |
|
34 |
enum ccs_policy_type { |
35 |
CCS_POLICY_TYPE_UNKNOWN, |
36 |
CCS_POLICY_TYPE_DOMAIN_POLICY, |
37 |
CCS_POLICY_TYPE_EXCEPTION_POLICY, |
38 |
}; |
39 |
|
40 |
enum ccs_socket_operation_type { |
41 |
CCS_NETWORK_ACL_UDP_BIND, |
42 |
CCS_NETWORK_ACL_UDP_CONNECT, |
43 |
CCS_NETWORK_ACL_TCP_BIND, |
44 |
CCS_NETWORK_ACL_TCP_LISTEN, |
45 |
CCS_NETWORK_ACL_TCP_CONNECT, |
46 |
CCS_NETWORK_ACL_TCP_ACCEPT, |
47 |
CCS_NETWORK_ACL_RAW_BIND, |
48 |
CCS_NETWORK_ACL_RAW_CONNECT |
49 |
}; |
50 |
|
51 |
#define CCS_VALUE_TYPE_DECIMAL 1 |
52 |
#define CCS_VALUE_TYPE_OCTAL 2 |
53 |
#define CCS_VALUE_TYPE_HEXADECIMAL 3 |
54 |
|
55 |
static int ccs_parse_ulong(unsigned long *result, char **str) |
56 |
{ |
57 |
const char *cp = *str; |
58 |
char *ep; |
59 |
int base = 10; |
60 |
if (*cp == '0') { |
61 |
char c = *(cp + 1); |
62 |
if (c == 'x' || c == 'X') { |
63 |
base = 16; |
64 |
cp += 2; |
65 |
} else if (c >= '0' && c <= '7') { |
66 |
base = 8; |
67 |
cp++; |
68 |
} |
69 |
} |
70 |
*result = strtoul(cp, &ep, base); |
71 |
if (cp == ep) |
72 |
return 0; |
73 |
*str = ep; |
74 |
return base == 16 ? CCS_VALUE_TYPE_HEXADECIMAL : |
75 |
(base == 8 ? CCS_VALUE_TYPE_OCTAL : CCS_VALUE_TYPE_DECIMAL); |
76 |
} |
77 |
|
78 |
static char *ccs_find_condition_part(char *data) |
79 |
{ |
80 |
char *cp = strstr(data, " if "); |
81 |
if (!cp) |
82 |
cp = strstr(data, " ; set "); |
83 |
if (cp) |
84 |
*cp++ = '\0'; |
85 |
return cp; |
86 |
} |
87 |
|
88 |
static unsigned int ccs_line = 0; |
89 |
static unsigned int ccs_errors = 0; |
90 |
static unsigned int ccs_warnings = 0; |
91 |
|
92 |
static _Bool ccs_check_condition(char *condition) |
93 |
{ |
94 |
enum ccs_conditions_index { |
95 |
CCS_TASK_UID, /* current_uid() */ |
96 |
CCS_TASK_EUID, /* current_euid() */ |
97 |
CCS_TASK_SUID, /* current_suid() */ |
98 |
CCS_TASK_FSUID, /* current_fsuid() */ |
99 |
CCS_TASK_GID, /* current_gid() */ |
100 |
CCS_TASK_EGID, /* current_egid() */ |
101 |
CCS_TASK_SGID, /* current_sgid() */ |
102 |
CCS_TASK_FSGID, /* current_fsgid() */ |
103 |
CCS_TASK_PID, /* sys_getpid() */ |
104 |
CCS_TASK_PPID, /* sys_getppid() */ |
105 |
CCS_EXEC_ARGC, /* "struct linux_binprm *"->argc */ |
106 |
CCS_EXEC_ENVC, /* "struct linux_binprm *"->envc */ |
107 |
CCS_TASK_STATE_0, /* (u8) (current->ccs_flags >> 24) */ |
108 |
CCS_TASK_STATE_1, /* (u8) (current->ccs_flags >> 16) */ |
109 |
CCS_TASK_STATE_2, /* (u8) (task->ccs_flags >> 8) */ |
110 |
CCS_TYPE_IS_SOCKET, /* S_IFSOCK */ |
111 |
CCS_TYPE_IS_SYMLINK, /* S_IFLNK */ |
112 |
CCS_TYPE_IS_FILE, /* S_IFREG */ |
113 |
CCS_TYPE_IS_BLOCK_DEV, /* S_IFBLK */ |
114 |
CCS_TYPE_IS_DIRECTORY, /* S_IFDIR */ |
115 |
CCS_TYPE_IS_CHAR_DEV, /* S_IFCHR */ |
116 |
CCS_TYPE_IS_FIFO, /* S_IFIFO */ |
117 |
CCS_MODE_SETUID, /* S_ISUID */ |
118 |
CCS_MODE_SETGID, /* S_ISGID */ |
119 |
CCS_MODE_STICKY, /* S_ISVTX */ |
120 |
CCS_MODE_OWNER_READ, /* S_IRUSR */ |
121 |
CCS_MODE_OWNER_WRITE, /* S_IWUSR */ |
122 |
CCS_MODE_OWNER_EXECUTE, /* S_IXUSR */ |
123 |
CCS_MODE_GROUP_READ, /* S_IRGRP */ |
124 |
CCS_MODE_GROUP_WRITE, /* S_IWGRP */ |
125 |
CCS_MODE_GROUP_EXECUTE, /* S_IXGRP */ |
126 |
CCS_MODE_OTHERS_READ, /* S_IROTH */ |
127 |
CCS_MODE_OTHERS_WRITE, /* S_IWOTH */ |
128 |
CCS_MODE_OTHERS_EXECUTE, /* S_IXOTH */ |
129 |
CCS_TASK_TYPE, /* ((u8) task->ccs_flags) & |
130 |
CCS_TASK_IS_EXECUTE_HANDLER */ |
131 |
CCS_TASK_EXECUTE_HANDLER, /* CCS_TASK_IS_EXECUTE_HANDLER */ |
132 |
CCS_EXEC_REALPATH, |
133 |
CCS_SYMLINK_TARGET, |
134 |
CCS_PATH1_UID, |
135 |
CCS_PATH1_GID, |
136 |
CCS_PATH1_INO, |
137 |
CCS_PATH1_MAJOR, |
138 |
CCS_PATH1_MINOR, |
139 |
CCS_PATH1_PERM, |
140 |
CCS_PATH1_TYPE, |
141 |
CCS_PATH1_DEV_MAJOR, |
142 |
CCS_PATH1_DEV_MINOR, |
143 |
CCS_PATH2_UID, |
144 |
CCS_PATH2_GID, |
145 |
CCS_PATH2_INO, |
146 |
CCS_PATH2_MAJOR, |
147 |
CCS_PATH2_MINOR, |
148 |
CCS_PATH2_PERM, |
149 |
CCS_PATH2_TYPE, |
150 |
CCS_PATH2_DEV_MAJOR, |
151 |
CCS_PATH2_DEV_MINOR, |
152 |
CCS_PATH1_PARENT_UID, |
153 |
CCS_PATH1_PARENT_GID, |
154 |
CCS_PATH1_PARENT_INO, |
155 |
CCS_PATH1_PARENT_PERM, |
156 |
CCS_PATH2_PARENT_UID, |
157 |
CCS_PATH2_PARENT_GID, |
158 |
CCS_PATH2_PARENT_INO, |
159 |
CCS_PATH2_PARENT_PERM, |
160 |
CCS_MAX_CONDITION_KEYWORD, |
161 |
CCS_NUMBER_UNION, |
162 |
CCS_NAME_UNION, |
163 |
CCS_ARGV_ENTRY, |
164 |
CCS_ENVP_ENTRY |
165 |
}; |
166 |
static const char *ccs_condition_keyword[CCS_MAX_CONDITION_KEYWORD] = { |
167 |
[CCS_TASK_UID] = "task.uid", |
168 |
[CCS_TASK_EUID] = "task.euid", |
169 |
[CCS_TASK_SUID] = "task.suid", |
170 |
[CCS_TASK_FSUID] = "task.fsuid", |
171 |
[CCS_TASK_GID] = "task.gid", |
172 |
[CCS_TASK_EGID] = "task.egid", |
173 |
[CCS_TASK_SGID] = "task.sgid", |
174 |
[CCS_TASK_FSGID] = "task.fsgid", |
175 |
[CCS_TASK_PID] = "task.pid", |
176 |
[CCS_TASK_PPID] = "task.ppid", |
177 |
[CCS_EXEC_ARGC] = "exec.argc", |
178 |
[CCS_EXEC_ENVC] = "exec.envc", |
179 |
[CCS_TASK_STATE_0] = "task.state[0]", |
180 |
[CCS_TASK_STATE_1] = "task.state[1]", |
181 |
[CCS_TASK_STATE_2] = "task.state[2]", |
182 |
[CCS_TYPE_IS_SOCKET] = "socket", |
183 |
[CCS_TYPE_IS_SYMLINK] = "symlink", |
184 |
[CCS_TYPE_IS_FILE] = "file", |
185 |
[CCS_TYPE_IS_BLOCK_DEV] = "block", |
186 |
[CCS_TYPE_IS_DIRECTORY] = "directory", |
187 |
[CCS_TYPE_IS_CHAR_DEV] = "char", |
188 |
[CCS_TYPE_IS_FIFO] = "fifo", |
189 |
[CCS_MODE_SETUID] = "setuid", |
190 |
[CCS_MODE_SETGID] = "setgid", |
191 |
[CCS_MODE_STICKY] = "sticky", |
192 |
[CCS_MODE_OWNER_READ] = "owner_read", |
193 |
[CCS_MODE_OWNER_WRITE] = "owner_write", |
194 |
[CCS_MODE_OWNER_EXECUTE] = "owner_execute", |
195 |
[CCS_MODE_GROUP_READ] = "group_read", |
196 |
[CCS_MODE_GROUP_WRITE] = "group_write", |
197 |
[CCS_MODE_GROUP_EXECUTE] = "group_execute", |
198 |
[CCS_MODE_OTHERS_READ] = "others_read", |
199 |
[CCS_MODE_OTHERS_WRITE] = "others_write", |
200 |
[CCS_MODE_OTHERS_EXECUTE] = "others_execute", |
201 |
[CCS_TASK_TYPE] = "task.type", |
202 |
[CCS_TASK_EXECUTE_HANDLER] = "execute_handler", |
203 |
[CCS_EXEC_REALPATH] = "exec.realpath", |
204 |
[CCS_SYMLINK_TARGET] = "symlink.target", |
205 |
[CCS_PATH1_UID] = "path1.uid", |
206 |
[CCS_PATH1_GID] = "path1.gid", |
207 |
[CCS_PATH1_INO] = "path1.ino", |
208 |
[CCS_PATH1_MAJOR] = "path1.major", |
209 |
[CCS_PATH1_MINOR] = "path1.minor", |
210 |
[CCS_PATH1_PERM] = "path1.perm", |
211 |
[CCS_PATH1_TYPE] = "path1.type", |
212 |
[CCS_PATH1_DEV_MAJOR] = "path1.dev_major", |
213 |
[CCS_PATH1_DEV_MINOR] = "path1.dev_minor", |
214 |
[CCS_PATH2_UID] = "path2.uid", |
215 |
[CCS_PATH2_GID] = "path2.gid", |
216 |
[CCS_PATH2_INO] = "path2.ino", |
217 |
[CCS_PATH2_MAJOR] = "path2.major", |
218 |
[CCS_PATH2_MINOR] = "path2.minor", |
219 |
[CCS_PATH2_PERM] = "path2.perm", |
220 |
[CCS_PATH2_TYPE] = "path2.type", |
221 |
[CCS_PATH2_DEV_MAJOR] = "path2.dev_major", |
222 |
[CCS_PATH2_DEV_MINOR] = "path2.dev_minor", |
223 |
[CCS_PATH1_PARENT_UID] = "path1.parent.uid", |
224 |
[CCS_PATH1_PARENT_GID] = "path1.parent.gid", |
225 |
[CCS_PATH1_PARENT_INO] = "path1.parent.ino", |
226 |
[CCS_PATH1_PARENT_PERM] = "path1.parent.perm", |
227 |
[CCS_PATH2_PARENT_UID] = "path2.parent.uid", |
228 |
[CCS_PATH2_PARENT_GID] = "path2.parent.gid", |
229 |
[CCS_PATH2_PARENT_INO] = "path2.parent.ino", |
230 |
[CCS_PATH2_PARENT_PERM] = "path2.parent.perm", |
231 |
}; |
232 |
char *const start = condition; |
233 |
char *pos = condition; |
234 |
u8 left; |
235 |
u8 right; |
236 |
int i; |
237 |
unsigned long left_min = 0; |
238 |
unsigned long left_max = 0; |
239 |
unsigned long right_min = 0; |
240 |
unsigned long right_max = 0; |
241 |
u8 post_state[4] = { 0, 0, 0, 0 }; |
242 |
condition = strstr(condition, "; set "); |
243 |
if (condition) { |
244 |
*condition = '\0'; |
245 |
condition += 6; |
246 |
while (true) { |
247 |
while (*condition == ' ') |
248 |
condition++; |
249 |
if (!*condition) |
250 |
break; |
251 |
pos = condition; |
252 |
if (!strncmp(condition, "task.state[0]=", 14)) |
253 |
i = 0; |
254 |
else if (!strncmp(condition, "task.state[1]=", 14)) |
255 |
i = 1; |
256 |
else if (!strncmp(condition, "task.state[2]=", 14)) |
257 |
i = 2; |
258 |
else |
259 |
goto out; |
260 |
condition += 14; |
261 |
if (post_state[3] & (1 << i)) |
262 |
goto out; |
263 |
post_state[3] |= 1 << i; |
264 |
if (!ccs_parse_ulong(&right_min, &condition) || |
265 |
right_min > 255) |
266 |
goto out; |
267 |
post_state[i] = (u8) right_min; |
268 |
} |
269 |
} |
270 |
condition = start; |
271 |
if (*condition && condition[strlen(condition) - 1] == ' ') |
272 |
condition[strlen(condition) - 1] = '\0'; |
273 |
if (!*condition) |
274 |
return true; |
275 |
if (strncmp(condition, "if ", 3)) |
276 |
goto out; |
277 |
condition += 3; |
278 |
|
279 |
pos = condition; |
280 |
while (pos) { |
281 |
char *eq; |
282 |
char *next = strchr(pos, ' '); |
283 |
int r_len; |
284 |
if (next) |
285 |
*next++ = '\0'; |
286 |
if (!ccs_correct_word(pos)) |
287 |
goto out; |
288 |
eq = strchr(pos, '='); |
289 |
if (!eq) |
290 |
goto out; |
291 |
*eq = '\0'; |
292 |
if (eq > pos && *(eq - 1) == '!') |
293 |
*(eq - 1) = '\0'; |
294 |
r_len = strlen(eq + 1); |
295 |
if (!strncmp(pos, "exec.argv[", 10)) { |
296 |
pos += 10; |
297 |
if (!ccs_parse_ulong(&left_min, &pos) || strcmp(pos, "]")) |
298 |
goto out; |
299 |
pos = eq + 1; |
300 |
if (r_len < 2) |
301 |
goto out; |
302 |
if (pos[0] == '"' && pos[r_len - 1] == '"') |
303 |
goto next; |
304 |
goto out; |
305 |
} else if (!strncmp(pos, "exec.envp[\"", 11)) { |
306 |
if (strcmp(pos + strlen(pos) - 2, "\"]")) |
307 |
goto out; |
308 |
pos = eq + 1; |
309 |
if (!strcmp(pos, "NULL")) |
310 |
goto next; |
311 |
if (r_len < 2) |
312 |
goto out; |
313 |
if (pos[0] == '"' && pos[r_len - 1] == '"') |
314 |
goto next; |
315 |
goto out; |
316 |
} |
317 |
for (left = 0; left < CCS_MAX_CONDITION_KEYWORD; left++) { |
318 |
const char *keyword = ccs_condition_keyword[left]; |
319 |
if (strcmp(pos, keyword)) |
320 |
continue; |
321 |
break; |
322 |
} |
323 |
if (left == CCS_MAX_CONDITION_KEYWORD) { |
324 |
if (!ccs_parse_ulong(&left_min, &pos)) |
325 |
goto out; |
326 |
if (pos[0] == '-') { |
327 |
pos++; |
328 |
if (!ccs_parse_ulong(&left_max, &pos) || pos[0] || |
329 |
left_min > left_max) |
330 |
goto out; |
331 |
} else if (pos[0]) |
332 |
goto out; |
333 |
} |
334 |
pos = eq + 1; |
335 |
if (left == CCS_EXEC_REALPATH || left == CCS_SYMLINK_TARGET) { |
336 |
if (r_len < 2) |
337 |
goto out; |
338 |
if (pos[0] == '@') |
339 |
goto next; |
340 |
if (pos[0] == '"' && pos[r_len - 1] == '"') |
341 |
goto next; |
342 |
goto out; |
343 |
} |
344 |
for (right = 0; right < CCS_MAX_CONDITION_KEYWORD; right++) { |
345 |
const char *keyword = ccs_condition_keyword[right]; |
346 |
if (strcmp(pos, keyword)) |
347 |
continue; |
348 |
break; |
349 |
} |
350 |
if (right < CCS_MAX_CONDITION_KEYWORD) |
351 |
goto next; |
352 |
if (pos[0] == '@' && pos[1]) |
353 |
goto next; |
354 |
if (!ccs_parse_ulong(&right_min, &pos)) |
355 |
goto out; |
356 |
if (pos[0] == '-') { |
357 |
pos++; |
358 |
if (!ccs_parse_ulong(&right_max, &pos) || pos[0] || |
359 |
right_min > right_max) |
360 |
goto out; |
361 |
} else if (pos[0]) |
362 |
goto out; |
363 |
next: |
364 |
pos = next; |
365 |
} |
366 |
return true; |
367 |
out: |
368 |
printf("%u: ERROR: '%s' is an illegal condition.\n", ccs_line, pos); |
369 |
ccs_errors++; |
370 |
return false; |
371 |
} |
372 |
|
373 |
static void ccs_check_capability_policy(char *data) |
374 |
{ |
375 |
static const char *capability_keywords[] = { |
376 |
"inet_tcp_create", "inet_tcp_listen", "inet_tcp_connect", |
377 |
"use_inet_udp", "use_inet_ip", "use_route", "use_packet", |
378 |
"SYS_MOUNT", "SYS_UMOUNT", "SYS_REBOOT", "SYS_CHROOT", |
379 |
"SYS_KILL", "SYS_VHANGUP", "SYS_TIME", "SYS_NICE", |
380 |
"SYS_SETHOSTNAME", "use_kernel_module", "create_fifo", |
381 |
"create_block_dev", "create_char_dev", "create_unix_socket", |
382 |
"SYS_LINK", "SYS_SYMLINK", "SYS_RENAME", "SYS_UNLINK", |
383 |
"SYS_CHMOD", "SYS_CHOWN", "SYS_IOCTL", "SYS_KEXEC_LOAD", |
384 |
"SYS_PIVOT_ROOT", "SYS_PTRACE", "conceal_mount", NULL |
385 |
}; |
386 |
int i; |
387 |
for (i = 0; capability_keywords[i]; i++) { |
388 |
if (!strcmp(data, capability_keywords[i])) |
389 |
return; |
390 |
} |
391 |
printf("%u: ERROR: '%s' is a bad capability name.\n", ccs_line, data); |
392 |
ccs_errors++; |
393 |
} |
394 |
|
395 |
static void ccs_check_signal_policy(char *data) |
396 |
{ |
397 |
int sig; |
398 |
char *cp; |
399 |
cp = strchr(data, ' '); |
400 |
if (!cp) { |
401 |
printf("%u: ERROR: Too few parameters.\n", ccs_line); |
402 |
ccs_errors++; |
403 |
return; |
404 |
} |
405 |
*cp++ = '\0'; |
406 |
if (sscanf(data, "%d", &sig) != 1) { |
407 |
printf("%u: ERROR: '%s' is a bad signal number.\n", ccs_line, data); |
408 |
ccs_errors++; |
409 |
} |
410 |
if (!ccs_correct_domain(cp)) { |
411 |
printf("%u: ERROR: '%s' is a bad domainname.\n", ccs_line, cp); |
412 |
ccs_errors++; |
413 |
} |
414 |
} |
415 |
|
416 |
static void ccs_check_env_policy(char *data) |
417 |
{ |
418 |
if (!ccs_correct_word(data)) { |
419 |
printf("%u: ERROR: '%s' is a bad variable name.\n", ccs_line, data); |
420 |
ccs_errors++; |
421 |
} |
422 |
} |
423 |
|
424 |
static void ccs_check_network_policy(char *data) |
425 |
{ |
426 |
int sock_type; |
427 |
int operation; |
428 |
u16 min_address[8]; |
429 |
u16 max_address[8]; |
430 |
unsigned int min_port; |
431 |
unsigned int max_port; |
432 |
int count; |
433 |
char *cp1 = NULL; |
434 |
char *cp2 = NULL; |
435 |
cp1 = strchr(data, ' '); |
436 |
if (!cp1) |
437 |
goto out; |
438 |
cp1++; |
439 |
if (!strncmp(data, "TCP ", 4)) |
440 |
sock_type = SOCK_STREAM; |
441 |
else if (!strncmp(data, "UDP ", 4)) |
442 |
sock_type = SOCK_DGRAM; |
443 |
else if (!strncmp(data, "RAW ", 4)) |
444 |
sock_type = SOCK_RAW; |
445 |
else |
446 |
goto out; |
447 |
cp2 = strchr(cp1, ' '); |
448 |
if (!cp2) |
449 |
goto out; |
450 |
cp2++; |
451 |
if (!strncmp(cp1, "bind ", 5)) { |
452 |
operation = (sock_type == SOCK_STREAM) ? CCS_NETWORK_ACL_TCP_BIND : |
453 |
(sock_type == SOCK_DGRAM) ? CCS_NETWORK_ACL_UDP_BIND : |
454 |
CCS_NETWORK_ACL_RAW_BIND; |
455 |
} else if (!strncmp(cp1, "connect ", 8)) { |
456 |
operation = (sock_type == SOCK_STREAM) ? |
457 |
CCS_NETWORK_ACL_TCP_CONNECT : (sock_type == SOCK_DGRAM) ? |
458 |
CCS_NETWORK_ACL_UDP_CONNECT : CCS_NETWORK_ACL_RAW_CONNECT; |
459 |
} else if (sock_type == SOCK_STREAM && !strncmp(cp1, "listen ", 7)) { |
460 |
operation = CCS_NETWORK_ACL_TCP_LISTEN; |
461 |
} else if (sock_type == SOCK_STREAM && !strncmp(cp1, "accept ", 7)) { |
462 |
operation = CCS_NETWORK_ACL_TCP_ACCEPT; |
463 |
} else { |
464 |
goto out; |
465 |
} |
466 |
cp1 = strchr(cp2, ' '); |
467 |
if (!cp1) |
468 |
goto out; |
469 |
cp1++; |
470 |
count = sscanf(cp2, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx-" |
471 |
"%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx", |
472 |
&min_address[0], &min_address[1], &min_address[2], |
473 |
&min_address[3], &min_address[4], &min_address[5], |
474 |
&min_address[6], &min_address[7], &max_address[0], |
475 |
&max_address[1], &max_address[2], &max_address[3], |
476 |
&max_address[4], &max_address[5], &max_address[6], |
477 |
&max_address[7]); |
478 |
if (count == 8 || count == 16) { |
479 |
int i; |
480 |
for (i = 0; i < 8; i++) { |
481 |
min_address[i] = htons(min_address[i]); |
482 |
max_address[i] = htons(max_address[i]); |
483 |
} |
484 |
if (count == 8) |
485 |
memmove(max_address, min_address, sizeof(min_address)); |
486 |
goto next; |
487 |
} |
488 |
count = sscanf(cp2, "%hu.%hu.%hu.%hu-%hu.%hu.%hu.%hu", |
489 |
&min_address[0], &min_address[1], &min_address[2], |
490 |
&min_address[3], &max_address[0], &max_address[1], |
491 |
&max_address[2], &max_address[3]); |
492 |
if (count == 4 || count == 8) { |
493 |
u32 ip = htonl((((u8) min_address[0]) << 24) + |
494 |
(((u8) min_address[1]) << 16) + |
495 |
(((u8) min_address[2]) << 8) + |
496 |
(u8) min_address[3]); |
497 |
memmove(min_address, &ip, sizeof(ip)); |
498 |
if (count == 8) |
499 |
ip = htonl((((u8) max_address[0]) << 24) + |
500 |
(((u8) max_address[1]) << 16) + |
501 |
(((u8) max_address[2]) << 8) + |
502 |
(u8) max_address[3]); |
503 |
memmove(max_address, &ip, sizeof(ip)); |
504 |
goto next; |
505 |
} |
506 |
if (*cp2 != '@') /* Don't reject address_group. */ |
507 |
goto out; |
508 |
next: |
509 |
if (strchr(cp1, ' ')) |
510 |
goto out; |
511 |
count = sscanf(cp1, "%u-%u", &min_port, &max_port); |
512 |
if (count == 1 || count == 2) { |
513 |
if (count == 1) |
514 |
max_port = min_port; |
515 |
if (min_port <= max_port && max_port < 65536) |
516 |
return; |
517 |
} |
518 |
out: |
519 |
printf("%u: ERROR: Bad network address.\n", ccs_line); |
520 |
ccs_errors++; |
521 |
} |
522 |
|
523 |
static void ccs_check_file_policy(char *data) |
524 |
{ |
525 |
static const struct { |
526 |
const char * const keyword; |
527 |
const int paths; |
528 |
} acl_type_array[] = { |
529 |
{ "execute", 1 }, |
530 |
{ "transit", 1 }, |
531 |
{ "read/write", 1 }, |
532 |
{ "read", 1 }, |
533 |
{ "write", 1 }, |
534 |
{ "create", 2 }, |
535 |
{ "unlink", 1 }, |
536 |
{ "mkdir", 2 }, |
537 |
{ "rmdir", 1 }, |
538 |
{ "mkfifo", 2 }, |
539 |
{ "mksock", 2 }, |
540 |
{ "mkblock", 4 }, |
541 |
{ "mkchar", 4 }, |
542 |
{ "truncate", 1 }, |
543 |
{ "symlink", 1 }, |
544 |
{ "link", 2 }, |
545 |
{ "rename", 2 }, |
546 |
{ "rewrite", 1 }, |
547 |
{ "chmod", 2 }, |
548 |
{ "chown", 2 }, |
549 |
{ "chgrp", 2 }, |
550 |
{ "ioctl", 2 }, |
551 |
{ "mount", 4 }, |
552 |
{ "unmount", 1 }, |
553 |
{ "chroot", 1 }, |
554 |
{ "pivot_root", 2 }, |
555 |
{ NULL, 0 } |
556 |
}; |
557 |
char *filename = strchr(data, ' '); |
558 |
char *cp; |
559 |
int type; |
560 |
if (!filename) { |
561 |
printf("%u: ERROR: Unknown command '%s'\n", ccs_line, data); |
562 |
ccs_errors++; |
563 |
return; |
564 |
} |
565 |
*filename++ = '\0'; |
566 |
if (strncmp(data, "allow_", 6)) |
567 |
goto out; |
568 |
data += 6; |
569 |
for (type = 0; acl_type_array[type].keyword; type++) { |
570 |
if (strcmp(data, acl_type_array[type].keyword)) |
571 |
continue; |
572 |
if (acl_type_array[type].paths == 4) { |
573 |
cp = strrchr(filename, ' '); |
574 |
if (!cp) { |
575 |
printf("%u: ERROR: Too few arguments.\n", |
576 |
ccs_line); |
577 |
break; |
578 |
} |
579 |
if (!ccs_correct_word(cp + 1)) { |
580 |
printf("%u: ERROR: '%s' is a bad argument\n", |
581 |
ccs_line, cp + 1); |
582 |
break; |
583 |
} |
584 |
*cp = '\0'; |
585 |
cp = strrchr(filename, ' '); |
586 |
if (!cp) { |
587 |
printf("%u: ERROR: Too few arguments.\n", |
588 |
ccs_line); |
589 |
break; |
590 |
} |
591 |
if (!ccs_correct_word(cp + 1)) { |
592 |
printf("%u: ERROR: '%s' is a bad argument.\n", |
593 |
ccs_line, cp + 1); |
594 |
break; |
595 |
} |
596 |
*cp = '\0'; |
597 |
} |
598 |
if (acl_type_array[type].paths >= 2) { |
599 |
cp = strrchr(filename, ' '); |
600 |
if (!cp) { |
601 |
printf("%u: ERROR: Too few arguments.\n", |
602 |
ccs_line); |
603 |
break; |
604 |
} |
605 |
if (!ccs_correct_word(cp + 1)) { |
606 |
printf("%u: ERROR: '%s' is a bad argument.\n", |
607 |
ccs_line, cp + 1); |
608 |
break; |
609 |
} |
610 |
*cp = '\0'; |
611 |
} |
612 |
if (!ccs_correct_word(filename)) { |
613 |
printf("%u: ERROR: '%s' is a bad argument.\n", ccs_line, |
614 |
filename); |
615 |
break; |
616 |
} |
617 |
return; |
618 |
} |
619 |
if (!acl_type_array[type].keyword) |
620 |
goto out; |
621 |
ccs_errors++; |
622 |
return; |
623 |
out: |
624 |
printf("%u: ERROR: Invalid permission '%s %s'\n", ccs_line, data, filename); |
625 |
ccs_errors++; |
626 |
} |
627 |
|
628 |
static void ccs_check_reserved_port_policy(char *data) |
629 |
{ |
630 |
unsigned int from; |
631 |
unsigned int to; |
632 |
if (strchr(data, ' ')) |
633 |
goto out; |
634 |
if (sscanf(data, "%u-%u", &from, &to) == 2) { |
635 |
if (from <= to && to < 65536) |
636 |
return; |
637 |
} else if (sscanf(data, "%u", &from) == 1) { |
638 |
if (from < 65536) |
639 |
return; |
640 |
} else { |
641 |
printf("%u: ERROR: Too few parameters.\n", ccs_line); |
642 |
ccs_errors++; |
643 |
return; |
644 |
} |
645 |
out: |
646 |
printf("%u: ERROR: '%s' is a bad port number.\n", ccs_line, data); |
647 |
ccs_errors++; |
648 |
} |
649 |
|
650 |
static void ccs_check_domain_initializer_entry(const char *domainname, |
651 |
const char *program) |
652 |
{ |
653 |
if (!ccs_correct_path(program)) { |
654 |
printf("%u: ERROR: '%s' is a bad pathname.\n", ccs_line, program); |
655 |
ccs_errors++; |
656 |
} |
657 |
if (domainname && !ccs_correct_path(domainname) && |
658 |
!ccs_correct_domain(domainname)) { |
659 |
printf("%u: ERROR: '%s' is a bad domainname.\n", |
660 |
ccs_line, domainname); |
661 |
ccs_errors++; |
662 |
} |
663 |
} |
664 |
|
665 |
static void ccs_check_domain_initializer_policy(char *data) |
666 |
{ |
667 |
char *cp = strstr(data, " from "); |
668 |
if (cp) { |
669 |
*cp = '\0'; |
670 |
ccs_check_domain_initializer_entry(cp + 6, data); |
671 |
} else { |
672 |
ccs_check_domain_initializer_entry(NULL, data); |
673 |
} |
674 |
} |
675 |
|
676 |
static void ccs_check_domain_keeper_entry(const char *domainname, |
677 |
const char *program) |
678 |
{ |
679 |
if (!ccs_correct_path(domainname) && !ccs_correct_domain(domainname)) { |
680 |
printf("%u: ERROR: '%s' is a bad domainname.\n", |
681 |
ccs_line, domainname); |
682 |
ccs_errors++; |
683 |
} |
684 |
if (program && !ccs_correct_path(program)) { |
685 |
printf("%u: ERROR: '%s' is a bad pathname.\n", ccs_line, program); |
686 |
ccs_errors++; |
687 |
} |
688 |
} |
689 |
|
690 |
static void ccs_check_domain_keeper_policy(char *data) |
691 |
{ |
692 |
char *cp = strstr(data, " from "); |
693 |
if (cp) { |
694 |
*cp = '\0'; |
695 |
ccs_check_domain_keeper_entry(cp + 6, data); |
696 |
} else { |
697 |
ccs_check_domain_keeper_entry(data, NULL); |
698 |
} |
699 |
} |
700 |
|
701 |
static void ccs_check_path_group_policy(char *data) |
702 |
{ |
703 |
char *cp = strchr(data, ' '); |
704 |
if (!cp) { |
705 |
printf("%u: ERROR: Too few parameters.\n", ccs_line); |
706 |
ccs_errors++; |
707 |
return; |
708 |
} |
709 |
*cp++ = '\0'; |
710 |
if (!ccs_correct_word(data)) { |
711 |
printf("%u: ERROR: '%s' is a bad group name.\n", ccs_line, data); |
712 |
ccs_errors++; |
713 |
} |
714 |
if (!ccs_correct_word(cp)) { |
715 |
printf("%u: ERROR: '%s' is a bad pathname.\n", ccs_line, cp); |
716 |
ccs_errors++; |
717 |
} |
718 |
} |
719 |
|
720 |
static void ccs_check_number_group_policy(char *data) |
721 |
{ |
722 |
char *cp = strchr(data, ' '); |
723 |
unsigned long v; |
724 |
if (!cp) { |
725 |
printf("%u: ERROR: Too few parameters.\n", ccs_line); |
726 |
ccs_errors++; |
727 |
return; |
728 |
} |
729 |
*cp++ = '\0'; |
730 |
if (!ccs_correct_word(data)) { |
731 |
printf("%u: ERROR: '%s' is a bad group name.\n", ccs_line, data); |
732 |
ccs_errors++; |
733 |
} |
734 |
data = cp; |
735 |
cp = strchr(data, '-'); |
736 |
if (cp) |
737 |
*cp = '\0'; |
738 |
if (!ccs_parse_ulong(&v, &data) || *data) { |
739 |
printf("%u: ERROR: '%s' is a bad number.\n", ccs_line, data); |
740 |
ccs_errors++; |
741 |
} |
742 |
if (cp && !ccs_parse_ulong(&v, &cp)) { |
743 |
printf("%u: ERROR: '%s' is a bad number.\n", ccs_line, cp); |
744 |
ccs_errors++; |
745 |
} |
746 |
} |
747 |
|
748 |
static void ccs_check_address_group_policy(char *data) |
749 |
{ |
750 |
char *cp = strchr(data, ' '); |
751 |
u16 min_address[8]; |
752 |
u16 max_address[8]; |
753 |
int count; |
754 |
if (!cp) { |
755 |
printf("%u: ERROR: Too few parameters.\n", ccs_line); |
756 |
ccs_errors++; |
757 |
return; |
758 |
} |
759 |
*cp++ = '\0'; |
760 |
if (!ccs_correct_word(data)) { |
761 |
printf("%u: ERROR: '%s' is a bad group name.\n", ccs_line, data); |
762 |
ccs_errors++; |
763 |
} |
764 |
count = sscanf(cp, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx-" |
765 |
"%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx", |
766 |
&min_address[0], &min_address[1], &min_address[2], |
767 |
&min_address[3], &min_address[4], &min_address[5], |
768 |
&min_address[6], &min_address[7], &max_address[0], |
769 |
&max_address[1], &max_address[2], &max_address[3], |
770 |
&max_address[4], &max_address[5], &max_address[6], |
771 |
&max_address[7]); |
772 |
if (count == 8 || count == 16) |
773 |
return; |
774 |
count = sscanf(cp, "%hu.%hu.%hu.%hu-%hu.%hu.%hu.%hu", |
775 |
&min_address[0], &min_address[1], &min_address[2], |
776 |
&min_address[3], &max_address[0], &max_address[1], |
777 |
&max_address[2], &max_address[3]); |
778 |
if (count == 4 || count == 8) |
779 |
return; |
780 |
printf("%u: ERROR: '%s' is a bad address.\n", ccs_line, cp); |
781 |
ccs_errors++; |
782 |
} |
783 |
|
784 |
static void ccs_check_domain_policy(char *policy) |
785 |
{ |
786 |
static int domain = EOF; |
787 |
_Bool is_delete = false; |
788 |
_Bool is_select = false; |
789 |
if (ccs_str_starts(policy, CCS_KEYWORD_DELETE)) |
790 |
is_delete = true; |
791 |
else if (ccs_str_starts(policy, CCS_KEYWORD_SELECT)) |
792 |
is_select = true; |
793 |
if (!strncmp(policy, "<kernel>", 8)) { |
794 |
if (!ccs_correct_domain(policy) || |
795 |
strlen(policy) >= CCS_MAX_PATHNAME_LEN) { |
796 |
printf("%u: ERROR: '%s' is a bad domainname.\n", |
797 |
ccs_line, policy); |
798 |
ccs_errors++; |
799 |
} else { |
800 |
if (is_delete) |
801 |
domain = EOF; |
802 |
else |
803 |
domain = 0; |
804 |
} |
805 |
} else if (is_select) { |
806 |
printf("%u: ERROR: Command 'select' is valid for selecting " |
807 |
"domains only.\n", ccs_line); |
808 |
ccs_errors++; |
809 |
} else if (domain == EOF) { |
810 |
printf("%u: WARNING: '%s' is unprocessed because domain is not " |
811 |
"selected.\n", ccs_line, policy); |
812 |
ccs_warnings++; |
813 |
} else if (ccs_str_starts(policy, CCS_KEYWORD_USE_PROFILE)) { |
814 |
unsigned int profile; |
815 |
if (sscanf(policy, "%u", &profile) != 1 || |
816 |
profile >= 256) { |
817 |
printf("%u: ERROR: '%s' is a bad profile.\n", |
818 |
ccs_line, policy); |
819 |
ccs_errors++; |
820 |
} |
821 |
} else if (!strcmp(policy, "ignore_global_allow_read")) { |
822 |
/* Nothing to do. */ |
823 |
} else if (!strcmp(policy, "ignore_global_allow_env")) { |
824 |
/* Nothing to do. */ |
825 |
} else if (ccs_str_starts(policy, "execute_handler ") || |
826 |
ccs_str_starts(policy, "denied_execute_handler")) { |
827 |
if (!ccs_correct_path(policy)) { |
828 |
printf("%u: ERROR: '%s' is a bad pathname.\n", |
829 |
ccs_line, policy); |
830 |
ccs_errors++; |
831 |
} |
832 |
} else if (!strcmp(policy, "transition_failed")) { |
833 |
/* Nothing to do. */ |
834 |
} else if (!strcmp(policy, "quota_exceeded")) { |
835 |
/* Nothing to do. */ |
836 |
} else { |
837 |
char *cp = ccs_find_condition_part(policy); |
838 |
if (cp && !ccs_check_condition(cp)) |
839 |
return; |
840 |
if (ccs_str_starts(policy, CCS_KEYWORD_ALLOW_CAPABILITY)) |
841 |
ccs_check_capability_policy(policy); |
842 |
else if (ccs_str_starts(policy, CCS_KEYWORD_ALLOW_NETWORK)) |
843 |
ccs_check_network_policy(policy); |
844 |
else if (ccs_str_starts(policy, CCS_KEYWORD_ALLOW_SIGNAL)) |
845 |
ccs_check_signal_policy(policy); |
846 |
else if (ccs_str_starts(policy, CCS_KEYWORD_ALLOW_ENV)) |
847 |
ccs_check_env_policy(policy); |
848 |
else |
849 |
ccs_check_file_policy(policy); |
850 |
} |
851 |
} |
852 |
|
853 |
static void ccs_check_exception_policy(char *policy) |
854 |
{ |
855 |
ccs_str_starts(policy, CCS_KEYWORD_DELETE); |
856 |
if (ccs_str_starts(policy, CCS_KEYWORD_ALLOW_READ)) { |
857 |
if (!ccs_correct_path(policy)) { |
858 |
printf("%u: ERROR: '%s' is a bad pathname.\n", |
859 |
ccs_line, policy); |
860 |
ccs_errors++; |
861 |
} |
862 |
} else if (ccs_str_starts(policy, CCS_KEYWORD_INITIALIZE_DOMAIN)) { |
863 |
ccs_check_domain_initializer_policy(policy); |
864 |
} else if (ccs_str_starts(policy, CCS_KEYWORD_NO_INITIALIZE_DOMAIN)) { |
865 |
ccs_check_domain_initializer_policy(policy); |
866 |
} else if (ccs_str_starts(policy, CCS_KEYWORD_KEEP_DOMAIN)) { |
867 |
ccs_check_domain_keeper_policy(policy); |
868 |
} else if (ccs_str_starts(policy, CCS_KEYWORD_NO_KEEP_DOMAIN)) { |
869 |
ccs_check_domain_keeper_policy(policy); |
870 |
} else if (ccs_str_starts(policy, CCS_KEYWORD_PATH_GROUP)) { |
871 |
ccs_check_path_group_policy(policy); |
872 |
} else if (ccs_str_starts(policy, CCS_KEYWORD_NUMBER_GROUP)) { |
873 |
ccs_check_number_group_policy(policy); |
874 |
} else if (ccs_str_starts(policy, CCS_KEYWORD_ADDRESS_GROUP)) { |
875 |
ccs_check_address_group_policy(policy); |
876 |
} else if (ccs_str_starts(policy, CCS_KEYWORD_AGGREGATOR)) { |
877 |
char *cp = strchr(policy, ' '); |
878 |
if (!cp) { |
879 |
printf("%u: ERROR: Too few parameters.\n", ccs_line); |
880 |
ccs_errors++; |
881 |
} else { |
882 |
*cp++ = '\0'; |
883 |
if (!ccs_correct_path(policy)) { |
884 |
printf("%u: ERROR: '%s' is a bad pattern.\n", |
885 |
ccs_line, policy); |
886 |
ccs_errors++; |
887 |
} |
888 |
if (!ccs_correct_path(cp)) { |
889 |
printf("%u: ERROR: '%s' is a bad pathname.\n", |
890 |
ccs_line, cp); |
891 |
ccs_errors++; |
892 |
} |
893 |
} |
894 |
} else if (ccs_str_starts(policy, CCS_KEYWORD_FILE_PATTERN)) { |
895 |
if (!ccs_correct_word(policy)) { |
896 |
printf("%u: ERROR: '%s' is a bad pattern.\n", |
897 |
ccs_line, policy); |
898 |
ccs_errors++; |
899 |
} |
900 |
} else if (ccs_str_starts(policy, CCS_KEYWORD_DENY_REWRITE)) { |
901 |
if (!ccs_correct_word(policy)) { |
902 |
printf("%u: ERROR: '%s' is a bad pattern.\n", |
903 |
ccs_line, policy); |
904 |
ccs_errors++; |
905 |
} |
906 |
} else if (ccs_str_starts(policy, CCS_KEYWORD_ALLOW_ENV)) { |
907 |
if (!ccs_correct_word(policy)) { |
908 |
printf("%u: ERROR: '%s' is a bad variable name.\n", |
909 |
ccs_line, policy); |
910 |
ccs_errors++; |
911 |
} |
912 |
} else if (ccs_str_starts(policy, CCS_KEYWORD_DENY_AUTOBIND)) { |
913 |
ccs_check_reserved_port_policy(policy); |
914 |
} else { |
915 |
printf("%u: ERROR: Unknown command '%s'.\n", |
916 |
ccs_line, policy); |
917 |
ccs_errors++; |
918 |
} |
919 |
} |
920 |
|
921 |
int main(int argc, char *argv[]) |
922 |
{ |
923 |
char *policy = NULL; |
924 |
int policy_type = CCS_POLICY_TYPE_UNKNOWN; |
925 |
if (argc > 1) { |
926 |
switch (argv[1][0]) { |
927 |
case 'e': |
928 |
policy_type = CCS_POLICY_TYPE_EXCEPTION_POLICY; |
929 |
break; |
930 |
case 'd': |
931 |
policy_type = CCS_POLICY_TYPE_DOMAIN_POLICY; |
932 |
break; |
933 |
} |
934 |
} |
935 |
if (policy_type == CCS_POLICY_TYPE_UNKNOWN) { |
936 |
fprintf(stderr, "%s e|d < policy_to_check\n", argv[0]); |
937 |
return 0; |
938 |
} |
939 |
while (true) { |
940 |
_Bool badchar_warned = false; |
941 |
int pos = 0; |
942 |
ccs_line++; |
943 |
while (true) { |
944 |
static int max_policy_len = 0; |
945 |
int c = getchar(); |
946 |
if (c == EOF) |
947 |
goto out; |
948 |
if (pos == max_policy_len) { |
949 |
char *cp; |
950 |
max_policy_len += 4096; |
951 |
cp = realloc(policy, max_policy_len); |
952 |
if (!cp) |
953 |
ccs_out_of_memory(); |
954 |
policy = cp; |
955 |
} |
956 |
policy[pos++] = (char) c; |
957 |
if (c == '\n') { |
958 |
policy[--pos] = '\0'; |
959 |
break; |
960 |
} |
961 |
if (badchar_warned || |
962 |
c == '\t' || (c >= ' ' && c < 127)) |
963 |
continue; |
964 |
printf("%u: WARNING: Line contains illegal " |
965 |
"character (\\%03o).\n", ccs_line, c); |
966 |
ccs_warnings++; |
967 |
badchar_warned = true; |
968 |
} |
969 |
ccs_normalize_line(policy); |
970 |
if (!policy[0] || policy[0] == '#') |
971 |
continue; |
972 |
switch (policy_type) { |
973 |
case CCS_POLICY_TYPE_DOMAIN_POLICY: |
974 |
ccs_check_domain_policy(policy); |
975 |
break; |
976 |
case CCS_POLICY_TYPE_EXCEPTION_POLICY: |
977 |
ccs_check_exception_policy(policy); |
978 |
break; |
979 |
} |
980 |
} |
981 |
out: |
982 |
free(policy); |
983 |
policy = NULL; |
984 |
ccs_line--; |
985 |
printf("Total: %u Line%s %u Error%s %u Warning%s\n", |
986 |
ccs_line, ccs_line > 1 ? "s" : "", ccs_errors, ccs_errors > 1 ? "s" : "", |
987 |
ccs_warnings, ccs_warnings > 1 ? "s" : ""); |
988 |
return ccs_errors ? 2 : (ccs_warnings ? 1 : 0); |
989 |
} |