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