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
46static char *config_file;
47static char *executable_file;
48
49enum perm_rule_type {EXACT_FILE = 0, EXACT_DIR, WILDCARD, RECURSIVE,
50    NUM_PR_TYPES};
51
52struct 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
66typedef struct perm_rule perm_rule_t;
67
68static perm_rule_t *rules[NUM_PR_TYPES];
69
70static 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
84static 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
98static 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
155static 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
195static 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
200static 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
211static 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
223static 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
284static 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
312static 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
359static 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
407int 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