perm_checker.c revision 7341494707810f709855ea85ce03a8ec3ac8dbaf
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 PERMS(M) (M & ~S_IFMT)
40#define MAX_NAME_LEN 4096
41#define MAX_UID_LEN 256
42#define MAX_GID_LEN MAX_UID_LEN
43
44enum perm_rule_type {EXACT_FILE = 0, EXACT_DIR, WILDCARD, RECURSIVE,
45    NUM_PR_TYPES};
46
47struct perm_rule {
48    char *rule_text;
49    int rule_line;
50    char *spec;
51    mode_t min_mode;
52    mode_t max_mode;
53    uid_t min_uid;
54    uid_t max_uid;
55    gid_t min_gid;
56    gid_t max_gid;
57    enum perm_rule_type type;
58    struct perm_rule *next;
59};
60
61typedef struct perm_rule perm_rule_t;
62
63static perm_rule_t *rules[NUM_PR_TYPES];
64
65static uid_t str2uid(char *str, int line_num)
66{
67    struct passwd *pw;
68
69    if (isdigit(str[0]))
70        return (uid_t) atol(str);
71
72    if (!(pw = getpwnam(str))) {
73        printf("# ERROR # Invalid uid '%s' reading line %d\n", str, line_num);
74        exit(255);
75    }
76    return pw->pw_uid;
77}
78
79static gid_t str2gid(char *str, int line_num)
80{
81    struct group *gr;
82
83    if (isdigit(str[0]))
84        return (uid_t) atol(str);
85
86    if (!(gr = getgrnam(str))) {
87        printf("# ERROR # Invalid gid '%s' reading line %d\n", str, line_num);
88        exit(255);
89    }
90    return gr->gr_gid;
91}
92
93static int read_rules(FILE *fp)
94{
95    char spec[MAX_NAME_LEN + 5];  // Allows for "/..." suffix + terminator
96    char min_uid_buf[MAX_UID_LEN + 1], max_uid_buf[MAX_UID_LEN + 1];
97    char min_gid_buf[MAX_GID_LEN + 1], max_gid_buf[MAX_GID_LEN + 1];
98    char rule_text_buf[MAX_NAME_LEN + 2*MAX_UID_LEN + 2*MAX_GID_LEN + 9];
99    unsigned long min_mode, max_mode;
100    perm_rule_t *pr;
101    int res;
102    int num_rules = 0, num_lines = 0;
103
104    // Note: Use of an unsafe C function here is OK, since this is a test
105    while ((res = fscanf(fp, "%s %lo %lo %s %s %s %s\n", spec,
106                         &min_mode, &max_mode, min_uid_buf, max_uid_buf,
107                         min_gid_buf, max_gid_buf)) != EOF) {
108        num_lines++;
109        if (res < 7) {
110            printf("# WARNING # Invalid rule on line number %d\n", num_lines);
111            continue;
112        }
113        if (!(pr = malloc(sizeof(perm_rule_t)))) {
114            printf("Out of memory.\n");
115            exit(255);
116        }
117        if (snprintf(rule_text_buf, sizeof(rule_text_buf),
118                     "%s %lo %lo %s %s %s %s", spec, min_mode, max_mode,
119                     min_uid_buf, max_uid_buf, min_gid_buf, max_gid_buf)
120                     >= (long int) sizeof(rule_text_buf)) {
121            // This should never happen, but just in case...
122            printf("# ERROR # Maximum length limits exceeded on line %d\n",
123                   num_lines);
124            exit(255);
125        }
126        pr->rule_text = strndup(rule_text_buf, sizeof(rule_text_buf));
127        pr->rule_line = num_lines;
128        if (strstr(spec, "/...")) {
129            pr->spec = strndup(spec, strlen(spec) - 3);
130            pr->type = RECURSIVE;
131        } else if (spec[strlen(spec) - 1] == '*') {
132            pr->spec = strndup(spec, strlen(spec) - 1);
133            pr->type = WILDCARD;
134        } else if (spec[strlen(spec) - 1] == '/') {
135            pr->spec = strdup(spec);
136            pr->type = EXACT_DIR;
137        } else {
138            pr->spec = strdup(spec);
139            pr->type = EXACT_FILE;
140        }
141        if ((pr->spec == NULL) || (pr->rule_text == NULL)) {
142            printf("Out of memory.\n");
143            exit(255);
144        }
145        pr->min_mode = min_mode;
146        pr->max_mode = max_mode;
147        pr->min_uid = str2uid(min_uid_buf, num_lines);
148        pr->max_uid = str2uid(max_uid_buf, num_lines);
149        pr->min_gid = str2gid(min_gid_buf, num_lines);
150        pr->max_gid = str2gid(max_gid_buf, num_lines);
151
152        // Add the rule to the appropriate set
153        pr->next = rules[pr->type];
154        rules[pr->type] = pr;
155        num_rules++;
156#if 0  // Useful for debugging
157        printf("rule #%d: type = %d spec = %s min_mode = %o max_mode = %o "
158               "min_uid = %d max_uid = %d min_gid = %d max_gid = %d\n",
159               num_rules, pr->type, pr->spec, pr->min_mode, pr->max_mode,
160               pr->min_uid, pr->max_uid, pr->min_gid, pr->max_gid);
161#endif
162    }
163    return num_lines - num_rules;
164}
165
166static void print_failed_rule(const perm_rule_t *pr)
167{
168    printf("# INFO # Failed rule #%d: %s\n", pr->rule_line, pr->rule_text);
169}
170
171static void print_new_rule(const char *name, mode_t mode, uid_t uid, gid_t gid)
172{
173    struct passwd *pw;
174    struct group *gr;
175    gr = getgrgid(gid);
176    pw = getpwuid(uid);
177    printf("%s %4o %4o %s %d %s %d\n", name, mode, mode, pw->pw_name, uid,
178           gr->gr_name, gid);
179}
180
181// Returns 1 if the rule passes, prints the failure and returns 0 if not
182static int pass_rule(const perm_rule_t *pr, mode_t mode, uid_t uid, gid_t gid)
183{
184    if (((pr->min_mode & mode) == pr->min_mode) &&
185            ((pr->max_mode | mode) == pr->max_mode) &&
186            (pr->min_gid <= gid) && (pr->max_gid >= gid) &&
187            (pr->min_uid <= uid) && (pr->max_uid >= uid))
188        return 1;
189    print_failed_rule(pr);
190    return 0;
191}
192
193// Returns 0 on success
194static int validate_file(const char *name, mode_t mode, uid_t uid, gid_t gid)
195{
196    perm_rule_t *pr;
197    int rules_matched = 0;
198    int retval = 0;
199
200    pr = rules[EXACT_FILE];
201    while (pr != NULL) {
202        if (strcmp(name, pr->spec) == 0) {
203            if (!pass_rule(pr, mode, uid, gid))
204                retval++;
205            else
206                rules_matched++;  // Exact match found
207        }
208        pr = pr->next;
209    }
210
211    if ((retval + rules_matched) > 1)
212        printf("# WARNING # Multiple exact rules for file: %s\n", name);
213
214    // If any exact rule matched or failed, we are done with this file
215    if (retval)
216        print_new_rule(name, mode, uid, gid);
217    if (rules_matched || retval)
218        return retval;
219
220    pr = rules[WILDCARD];
221    while (pr != NULL) {
222        // Check if the spec is a prefix of the filename, and that the file
223        // is actually in the same directory as the wildcard.
224        if ((strstr(name, pr->spec) == name) &&
225                (!strchr(name + strlen(pr->spec), '/'))) {
226            if (!pass_rule(pr, mode, uid, gid))
227                retval++;
228            else
229                rules_matched++;
230        }
231        pr = pr->next;
232    }
233
234    pr = rules[RECURSIVE];
235    while (pr != NULL) {
236        if (strstr(name, pr->spec) == name) {
237            if (!pass_rule(pr, mode, uid, gid))
238                retval++;
239            else
240                rules_matched++;
241        }
242        pr = pr->next;
243    }
244
245    if (!rules_matched)
246        retval++;  // In case no rules either matched or failed, be sure to fail
247
248    if (retval)
249        print_new_rule(name, mode, uid, gid);
250
251    return retval;
252}
253
254// Returns 0 on success
255static int validate_link(const char *name, mode_t mode, uid_t uid, gid_t gid)
256{
257    perm_rule_t *pr;
258    int rules_matched = 0;
259    int retval = 0;
260
261    // For now, we match links against "exact" file rules only
262    pr = rules[EXACT_FILE];
263    while (pr != NULL) {
264        if (strcmp(name, pr->spec) == 0) {
265            if (!pass_rule(pr, mode, uid, gid))
266                retval++;
267            else
268                rules_matched++;  // Exact match found
269        }
270        pr = pr->next;
271    }
272
273    if ((retval + rules_matched) > 1)
274        printf("# WARNING # Multiple exact rules for link: %s\n", name);
275    if (retval)
276        print_new_rule(name, mode, uid, gid);
277
278    // Note: Unlike files, if no rules matches for links, retval = 0 (success).
279    return retval;
280}
281
282// Returns 0 on success
283static int validate_dir(const char *name, mode_t mode, uid_t uid, gid_t gid)
284{
285    perm_rule_t *pr;
286    int rules_matched = 0;
287    int retval = 0;
288
289    pr = rules[EXACT_DIR];
290    while (pr != NULL) {
291        if (strcmp(name, pr->spec) == 0) {
292            if (!pass_rule(pr, mode, uid, gid))
293                retval++;
294            else
295                rules_matched++;  // Exact match found
296        }
297        pr = pr->next;
298    }
299
300    if ((retval + rules_matched) > 1)
301        printf("# WARNING # Multiple exact rules for directory: %s\n", name);
302
303    // If any exact rule matched or failed, we are done with this directory
304    if (retval)
305        print_new_rule(name, mode, uid, gid);
306    if (rules_matched || retval)
307        return retval;
308
309    pr = rules[RECURSIVE];
310    while (pr != NULL) {
311        if (strstr(name, pr->spec) == name) {
312            if (!pass_rule(pr, mode, uid, gid))
313                retval++;
314            else
315                rules_matched++;
316        }
317        pr = pr->next;
318    }
319
320    if (!rules_matched)
321        retval++;  // In case no rules either matched or failed, be sure to fail
322
323    if (retval)
324        print_new_rule(name, mode, uid, gid);
325
326    return retval;
327}
328
329// Returns 0 on success
330static int check_path(const char *name)
331{
332    char namebuf[MAX_NAME_LEN + 1];
333    char tmp[MAX_NAME_LEN + 1];
334    DIR *d;
335    struct dirent *de;
336    struct stat s;
337    int err;
338    int retval = 0;
339
340    err = lstat(name, &s);
341    if (err < 0) {
342        if (errno != ENOENT)
343        {
344            perror(name);
345            return 1;
346        }
347        return 0;  // File doesn't exist anymore
348    }
349
350    if (S_ISDIR(s.st_mode)) {
351        if (name[strlen(name) - 1] != '/')
352            snprintf(namebuf, sizeof(namebuf), "%s/", name);
353        else
354            snprintf(namebuf, sizeof(namebuf), "%s", name);
355
356        retval |= validate_dir(namebuf, PERMS(s.st_mode), s.st_uid, s.st_gid);
357        d = opendir(namebuf);
358        if(d == 0) {
359            printf("%s : opendir failed: %s\n", namebuf, strerror(errno));
360            return 1;
361        }
362
363        while ((de = readdir(d)) != 0) {
364            if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
365                continue;
366            snprintf(tmp, sizeof(tmp), "%s%s", namebuf, de->d_name);
367            retval |= check_path(tmp);
368        }
369        closedir(d);
370        return retval;
371    } else if (S_ISLNK(s.st_mode)) {
372        validate_link(name, PERMS(s.st_mode), s.st_uid, s.st_gid);
373    } else {
374        return validate_file(name, PERMS(s.st_mode), s.st_uid, s.st_gid);
375    }
376}
377
378int main(int argc, char **argv)
379{
380    FILE *fp;
381    int i;
382
383    // Initialize ruleset pointers
384    for (i = 0; i < NUM_PR_TYPES; i++)
385        rules[i] = NULL;
386
387    if (!(fp = fopen("/etc/perm_checker.conf", "r"))) {
388        printf("Error opening /etc/perm_checker.conf\n");
389        exit(255);
390    }
391    read_rules(fp);
392    fclose(fp);
393
394    if (check_path("/"))
395        return 255;
396
397    printf("Passed.\n");
398    return 0;
399}
400