Home | History | Annotate | Download | only in fstest
      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 
     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 
     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 
     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 
    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 
    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 
    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
    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
    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
    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
    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
    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 
    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