1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 // A simple file permissions checker. See associated README.
18
19 #define _GNU_SOURCE
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdarg.h>
24 #include <string.h>
25 #include <ctype.h>
26 #include <sys/types.h>
27 #include <dirent.h>
28 #include <errno.h>
29
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <time.h>
33
34 #include <pwd.h>
35 #include <grp.h>
36
37 #include <linux/kdev_t.h>
38
39 #define DEFAULT_CONFIG_FILE "/data/local/perm_checker.conf"
40
41 #define PERMS(M) (M & ~S_IFMT)
42 #define MAX_NAME_LEN 4096
43 #define MAX_UID_LEN 256
44 #define MAX_GID_LEN MAX_UID_LEN
45
46 static char *config_file;
47 static char *executable_file;
48
49 enum perm_rule_type {EXACT_FILE = 0, EXACT_DIR, WILDCARD, RECURSIVE,
50 NUM_PR_TYPES};
51
52 struct perm_rule {
53 char *rule_text;
54 int rule_line;
55 char *spec;
56 mode_t min_mode;
57 mode_t max_mode;
58 uid_t min_uid;
59 uid_t max_uid;
60 gid_t min_gid;
61 gid_t max_gid;
62 enum perm_rule_type type;
63 struct perm_rule *next;
64 };
65
66 typedef struct perm_rule perm_rule_t;
67
68 static perm_rule_t *rules[NUM_PR_TYPES];
69
str2uid(char * str,int line_num)70 static uid_t str2uid(char *str, int line_num)
71 {
72 struct passwd *pw;
73
74 if (isdigit(str[0]))
75 return (uid_t) atol(str);
76
77 if (!(pw = getpwnam(str))) {
78 printf("# ERROR # Invalid uid '%s' reading line %d\n", str, line_num);
79 exit(255);
80 }
81 return pw->pw_uid;
82 }
83
str2gid(char * str,int line_num)84 static gid_t str2gid(char *str, int line_num)
85 {
86 struct group *gr;
87
88 if (isdigit(str[0]))
89 return (uid_t) atol(str);
90
91 if (!(gr = getgrnam(str))) {
92 printf("# ERROR # Invalid gid '%s' reading line %d\n", str, line_num);
93 exit(255);
94 }
95 return gr->gr_gid;
96 }
97
add_rule(int line_num,char * spec,unsigned long min_mode,unsigned long max_mode,char * min_uid_buf,char * max_uid_buf,char * min_gid_buf,char * max_gid_buf)98 static void add_rule(int line_num, char *spec,
99 unsigned long min_mode, unsigned long max_mode,
100 char *min_uid_buf, char *max_uid_buf,
101 char *min_gid_buf, char *max_gid_buf) {
102
103 char rule_text_buf[MAX_NAME_LEN + 2*MAX_UID_LEN + 2*MAX_GID_LEN + 9];
104 perm_rule_t *pr = malloc(sizeof(perm_rule_t));
105 if (!pr) {
106 printf("Out of memory.\n");
107 exit(255);
108 }
109 if (snprintf(rule_text_buf, sizeof(rule_text_buf),
110 "%s %lo %lo %s %s %s %s", spec, min_mode, max_mode,
111 min_uid_buf, max_uid_buf, min_gid_buf, max_gid_buf)
112 >= (long int) sizeof(rule_text_buf)) {
113 // This should never happen, but just in case...
114 printf("# ERROR # Maximum length limits exceeded on line %d\n",
115 line_num);
116 exit(255);
117 }
118 pr->rule_text = strndup(rule_text_buf, sizeof(rule_text_buf));
119 pr->rule_line = line_num;
120 if (strstr(spec, "/...")) {
121 pr->spec = strndup(spec, strlen(spec) - 3);
122 pr->type = RECURSIVE;
123 } else if (spec[strlen(spec) - 1] == '*') {
124 pr->spec = strndup(spec, strlen(spec) - 1);
125 pr->type = WILDCARD;
126 } else if (spec[strlen(spec) - 1] == '/') {
127 pr->spec = strdup(spec);
128 pr->type = EXACT_DIR;
129 } else {
130 pr->spec = strdup(spec);
131 pr->type = EXACT_FILE;
132 }
133 if ((pr->spec == NULL) || (pr->rule_text == NULL)) {
134 printf("Out of memory.\n");
135 exit(255);
136 }
137 pr->min_mode = min_mode;
138 pr->max_mode = max_mode;
139 pr->min_uid = str2uid(min_uid_buf, line_num);
140 pr->max_uid = str2uid(max_uid_buf, line_num);
141 pr->min_gid = str2gid(min_gid_buf, line_num);
142 pr->max_gid = str2gid(max_gid_buf, line_num);
143
144 // Add the rule to the appropriate set
145 pr->next = rules[pr->type];
146 rules[pr->type] = pr;
147 #if 0 // Useful for debugging
148 printf("rule #%d: type = %d spec = %s min_mode = %o max_mode = %o "
149 "min_uid = %d max_uid = %d min_gid = %d max_gid = %d\n",
150 num_rules, pr->type, pr->spec, pr->min_mode, pr->max_mode,
151 pr->min_uid, pr->max_uid, pr->min_gid, pr->max_gid);
152 #endif
153 }
154
read_rules(FILE * fp)155 static int read_rules(FILE *fp)
156 {
157 char spec[MAX_NAME_LEN + 5]; // Allows for "/..." suffix + terminator
158 char min_uid_buf[MAX_UID_LEN + 1], max_uid_buf[MAX_UID_LEN + 1];
159 char min_gid_buf[MAX_GID_LEN + 1], max_gid_buf[MAX_GID_LEN + 1];
160 unsigned long min_mode, max_mode;
161 int res;
162 int num_rules = 0, num_lines = 0;
163
164 // Note: Use of an unsafe C function here is OK, since this is a test
165 while ((res = fscanf(fp, "%s %lo %lo %s %s %s %s\n", spec,
166 &min_mode, &max_mode, min_uid_buf, max_uid_buf,
167 min_gid_buf, max_gid_buf)) != EOF) {
168 num_lines++;
169 if (res < 7) {
170 printf("# WARNING # Invalid rule on line number %d\n", num_lines);
171 continue;
172 }
173 add_rule(num_lines, spec,
174 min_mode, max_mode,
175 min_uid_buf, max_uid_buf,
176 min_gid_buf, max_gid_buf);
177 num_rules++;
178 }
179
180 // Automatically add a rule to match this executable itself
181 add_rule(-1, executable_file,
182 000, 0777,
183 "root", "shell",
184 "root", "shell");
185
186 // Automatically add a rule to match the configuration file
187 add_rule(-1, config_file,
188 000, 0777,
189 "root", "shell",
190 "root", "shell");
191
192 return num_lines - num_rules;
193 }
194
print_failed_rule(const perm_rule_t * pr)195 static void print_failed_rule(const perm_rule_t *pr)
196 {
197 printf("# INFO # Failed rule #%d: %s\n", pr->rule_line, pr->rule_text);
198 }
199
print_new_rule(const char * name,mode_t mode,uid_t uid,gid_t gid)200 static void print_new_rule(const char *name, mode_t mode, uid_t uid, gid_t gid)
201 {
202 struct passwd *pw;
203 struct group *gr;
204 gr = getgrgid(gid);
205 pw = getpwuid(uid);
206 printf("%s %4o %4o %s %d %s %d\n", name, mode, mode, pw->pw_name, uid,
207 gr->gr_name, gid);
208 }
209
210 // Returns 1 if the rule passes, prints the failure and returns 0 if not
pass_rule(const perm_rule_t * pr,mode_t mode,uid_t uid,gid_t gid)211 static int pass_rule(const perm_rule_t *pr, mode_t mode, uid_t uid, gid_t gid)
212 {
213 if (((pr->min_mode & mode) == pr->min_mode) &&
214 ((pr->max_mode | mode) == pr->max_mode) &&
215 (pr->min_gid <= gid) && (pr->max_gid >= gid) &&
216 (pr->min_uid <= uid) && (pr->max_uid >= uid))
217 return 1;
218 print_failed_rule(pr);
219 return 0;
220 }
221
222 // Returns 0 on success
validate_file(const char * name,mode_t mode,uid_t uid,gid_t gid)223 static int validate_file(const char *name, mode_t mode, uid_t uid, gid_t gid)
224 {
225 perm_rule_t *pr;
226 int rules_matched = 0;
227 int retval = 0;
228
229 pr = rules[EXACT_FILE];
230 while (pr != NULL) {
231 if (strcmp(name, pr->spec) == 0) {
232 if (!pass_rule(pr, mode, uid, gid))
233 retval++;
234 else
235 rules_matched++; // Exact match found
236 }
237 pr = pr->next;
238 }
239
240 if ((retval + rules_matched) > 1)
241 printf("# WARNING # Multiple exact rules for file: %s\n", name);
242
243 // If any exact rule matched or failed, we are done with this file
244 if (retval)
245 print_new_rule(name, mode, uid, gid);
246 if (rules_matched || retval)
247 return retval;
248
249 pr = rules[WILDCARD];
250 while (pr != NULL) {
251 // Check if the spec is a prefix of the filename, and that the file
252 // is actually in the same directory as the wildcard.
253 if ((strstr(name, pr->spec) == name) &&
254 (!strchr(name + strlen(pr->spec), '/'))) {
255 if (!pass_rule(pr, mode, uid, gid))
256 retval++;
257 else
258 rules_matched++;
259 }
260 pr = pr->next;
261 }
262
263 pr = rules[RECURSIVE];
264 while (pr != NULL) {
265 if (strstr(name, pr->spec) == name) {
266 if (!pass_rule(pr, mode, uid, gid))
267 retval++;
268 else
269 rules_matched++;
270 }
271 pr = pr->next;
272 }
273
274 if (!rules_matched)
275 retval++; // In case no rules either matched or failed, be sure to fail
276
277 if (retval)
278 print_new_rule(name, mode, uid, gid);
279
280 return retval;
281 }
282
283 // Returns 0 on success
validate_link(const char * name,mode_t mode,uid_t uid,gid_t gid)284 static int validate_link(const char *name, mode_t mode, uid_t uid, gid_t gid)
285 {
286 perm_rule_t *pr;
287 int rules_matched = 0;
288 int retval = 0;
289
290 // For now, we match links against "exact" file rules only
291 pr = rules[EXACT_FILE];
292 while (pr != NULL) {
293 if (strcmp(name, pr->spec) == 0) {
294 if (!pass_rule(pr, mode, uid, gid))
295 retval++;
296 else
297 rules_matched++; // Exact match found
298 }
299 pr = pr->next;
300 }
301
302 if ((retval + rules_matched) > 1)
303 printf("# WARNING # Multiple exact rules for link: %s\n", name);
304 if (retval)
305 print_new_rule(name, mode, uid, gid);
306
307 // Note: Unlike files, if no rules matches for links, retval = 0 (success).
308 return retval;
309 }
310
311 // Returns 0 on success
validate_dir(const char * name,mode_t mode,uid_t uid,gid_t gid)312 static int validate_dir(const char *name, mode_t mode, uid_t uid, gid_t gid)
313 {
314 perm_rule_t *pr;
315 int rules_matched = 0;
316 int retval = 0;
317
318 pr = rules[EXACT_DIR];
319 while (pr != NULL) {
320 if (strcmp(name, pr->spec) == 0) {
321 if (!pass_rule(pr, mode, uid, gid))
322 retval++;
323 else
324 rules_matched++; // Exact match found
325 }
326 pr = pr->next;
327 }
328
329 if ((retval + rules_matched) > 1)
330 printf("# WARNING # Multiple exact rules for directory: %s\n", name);
331
332 // If any exact rule matched or failed, we are done with this directory
333 if (retval)
334 print_new_rule(name, mode, uid, gid);
335 if (rules_matched || retval)
336 return retval;
337
338 pr = rules[RECURSIVE];
339 while (pr != NULL) {
340 if (strstr(name, pr->spec) == name) {
341 if (!pass_rule(pr, mode, uid, gid))
342 retval++;
343 else
344 rules_matched++;
345 }
346 pr = pr->next;
347 }
348
349 if (!rules_matched)
350 retval++; // In case no rules either matched or failed, be sure to fail
351
352 if (retval)
353 print_new_rule(name, mode, uid, gid);
354
355 return retval;
356 }
357
358 // Returns 0 on success
check_path(const char * name)359 static int check_path(const char *name)
360 {
361 char namebuf[MAX_NAME_LEN + 1];
362 char tmp[MAX_NAME_LEN + 1];
363 DIR *d;
364 struct dirent *de;
365 struct stat s;
366 int err;
367 int retval = 0;
368
369 err = lstat(name, &s);
370 if (err < 0) {
371 if (errno != ENOENT)
372 {
373 perror(name);
374 return 1;
375 }
376 return 0; // File doesn't exist anymore
377 }
378
379 if (S_ISDIR(s.st_mode)) {
380 if (name[strlen(name) - 1] != '/')
381 snprintf(namebuf, sizeof(namebuf), "%s/", name);
382 else
383 snprintf(namebuf, sizeof(namebuf), "%s", name);
384
385 retval |= validate_dir(namebuf, PERMS(s.st_mode), s.st_uid, s.st_gid);
386 d = opendir(namebuf);
387 if(d == 0) {
388 printf("%s : opendir failed: %s\n", namebuf, strerror(errno));
389 return 1;
390 }
391
392 while ((de = readdir(d)) != 0) {
393 if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
394 continue;
395 snprintf(tmp, sizeof(tmp), "%s%s", namebuf, de->d_name);
396 retval |= check_path(tmp);
397 }
398 closedir(d);
399 return retval;
400 } else if (S_ISLNK(s.st_mode)) {
401 return validate_link(name, PERMS(s.st_mode), s.st_uid, s.st_gid);
402 } else {
403 return validate_file(name, PERMS(s.st_mode), s.st_uid, s.st_gid);
404 }
405 }
406
main(int argc,char ** argv)407 int main(int argc, char **argv)
408 {
409 FILE *fp;
410 int i;
411
412 if (argc > 2) {
413 printf("\nSyntax: %s [configfilename]\n", argv[0]);
414 }
415 config_file = (argc == 2) ? argv[1] : DEFAULT_CONFIG_FILE;
416 executable_file = argv[0];
417
418 // Initialize ruleset pointers
419 for (i = 0; i < NUM_PR_TYPES; i++)
420 rules[i] = NULL;
421
422 if (!(fp = fopen(config_file, "r"))) {
423 printf("Error opening %s\n", config_file);
424 exit(255);
425 }
426 read_rules(fp);
427 fclose(fp);
428
429 if (check_path("/"))
430 return 255;
431
432 printf("Passed.\n");
433 return 0;
434 }
435