1 #include "restore.h" 2 #include <unistd.h> 3 #include <fcntl.h> 4 #include <stdio_ext.h> 5 #include <ctype.h> 6 #include <regex.h> 7 #include <sys/vfs.h> 8 #define __USE_XOPEN_EXTENDED 1 /* nftw */ 9 #include <libgen.h> 10 #ifdef USE_AUDIT 11 #include <libaudit.h> 12 13 #ifndef AUDIT_FS_RELABEL 14 #define AUDIT_FS_RELABEL 2309 15 #endif 16 #endif 17 18 19 /* cmdline opts*/ 20 21 static char *policyfile = NULL; 22 static int warn_no_match = 0; 23 static int null_terminated = 0; 24 static struct restore_opts r_opts; 25 26 #define STAT_BLOCK_SIZE 1 27 28 /* setfiles will abort its operation after reaching the 29 * following number of errors (e.g. invalid contexts), 30 * unless it is used in "debug" mode (-d option). 31 */ 32 #ifndef ABORT_ON_ERRORS 33 #define ABORT_ON_ERRORS 10 34 #endif 35 36 #define SETFILES "setfiles" 37 #define RESTORECON "restorecon" 38 static int iamrestorecon; 39 40 /* Behavior flags determined based on setfiles vs. restorecon */ 41 static int ctx_validate; /* Validate contexts */ 42 static const char *altpath; /* Alternate path to file_contexts */ 43 44 void usage(const char *const name) 45 { 46 if (iamrestorecon) { 47 fprintf(stderr, 48 "usage: %s [-iFnprRv0] [-e excludedir] pathname...\n" 49 "usage: %s [-iFnprRv0] [-e excludedir] -f filename\n", 50 name, name); 51 } else { 52 fprintf(stderr, 53 "usage: %s [-dilnpqvFW] [-e excludedir] [-r alt_root_path] spec_file pathname...\n" 54 "usage: %s [-dilnpqvFW] [-e excludedir] [-r alt_root_path] spec_file -f filename\n" 55 "usage: %s -s [-dilnpqvFW] spec_file\n" 56 "usage: %s -c policyfile spec_file\n", 57 name, name, name, name); 58 } 59 exit(-1); 60 } 61 62 static int nerr = 0; 63 64 void inc_err(void) 65 { 66 nerr++; 67 if (nerr > ABORT_ON_ERRORS - 1 && !r_opts.debug) { 68 fprintf(stderr, "Exiting after %d errors.\n", ABORT_ON_ERRORS); 69 exit(-1); 70 } 71 } 72 73 74 75 void set_rootpath(const char *arg) 76 { 77 int len; 78 79 r_opts.rootpath = strdup(arg); 80 if (NULL == r_opts.rootpath) { 81 fprintf(stderr, "%s: insufficient memory for r_opts.rootpath\n", 82 r_opts.progname); 83 exit(-1); 84 } 85 86 /* trim trailing /, if present */ 87 len = strlen(r_opts.rootpath); 88 while (len && ('/' == r_opts.rootpath[len - 1])) 89 r_opts.rootpath[--len] = 0; 90 r_opts.rootpathlen = len; 91 } 92 93 int canoncon(char **contextp) 94 { 95 char *context = *contextp, *tmpcon; 96 int rc = 0; 97 98 if (policyfile) { 99 if (sepol_check_context(context) < 0) { 100 fprintf(stderr, "invalid context %s\n", context); 101 exit(-1); 102 } 103 } else if (security_canonicalize_context_raw(context, &tmpcon) == 0) { 104 free(context); 105 *contextp = tmpcon; 106 } else if (errno != ENOENT) { 107 rc = -1; 108 inc_err(); 109 } 110 111 return rc; 112 } 113 114 #ifndef USE_AUDIT 115 static void maybe_audit_mass_relabel(int mass_relabel __attribute__((unused)), 116 int mass_relabel_errs __attribute__((unused))) 117 { 118 #else 119 static void maybe_audit_mass_relabel(int mass_relabel, int mass_relabel_errs) 120 { 121 int audit_fd = -1; 122 int rc = 0; 123 124 if (!mass_relabel) /* only audit a forced full relabel */ 125 return; 126 127 audit_fd = audit_open(); 128 129 if (audit_fd < 0) { 130 fprintf(stderr, "Error connecting to audit system.\n"); 131 exit(-1); 132 } 133 134 rc = audit_log_user_message(audit_fd, AUDIT_FS_RELABEL, 135 "op=mass relabel", NULL, NULL, NULL, !mass_relabel_errs); 136 if (rc <= 0) { 137 fprintf(stderr, "Error sending audit message: %s.\n", 138 strerror(errno)); 139 /* exit(-1); -- don't exit atm. as fix for eff_cap isn't in most kernels */ 140 } 141 audit_close(audit_fd); 142 #endif 143 } 144 145 int main(int argc, char **argv) 146 { 147 struct stat sb; 148 int opt, i = 0; 149 const char *input_filename = NULL; 150 int use_input_file = 0; 151 char *buf = NULL; 152 size_t buf_len; 153 int recurse; /* Recursive descent. */ 154 const char *base; 155 int mass_relabel = 0, errors = 0; 156 const char *ropts = "e:f:hilno:pqrsvFRW0"; 157 const char *sopts = "c:de:f:hilno:pqr:svFR:W0"; 158 const char *opts; 159 160 memset(&r_opts, 0, sizeof(r_opts)); 161 162 /* Initialize variables */ 163 r_opts.progress = 0; 164 r_opts.count = 0; 165 r_opts.nfile = 0; 166 r_opts.debug = 0; 167 r_opts.change = 1; 168 r_opts.verbose = 0; 169 r_opts.logging = 0; 170 r_opts.rootpath = NULL; 171 r_opts.rootpathlen = 0; 172 r_opts.outfile = NULL; 173 r_opts.force = 0; 174 r_opts.hard_links = 1; 175 176 altpath = NULL; 177 178 r_opts.progname = strdup(argv[0]); 179 if (!r_opts.progname) { 180 fprintf(stderr, "%s: Out of memory!\n", argv[0]); 181 exit(-1); 182 } 183 base = basename(r_opts.progname); 184 185 if (!strcmp(base, SETFILES)) { 186 /* 187 * setfiles: 188 * Recursive descent, 189 * Does not expand paths via realpath, 190 * Aborts on errors during the file tree walk, 191 * Try to track inode associations for conflict detection, 192 * Does not follow mounts, 193 * Validates all file contexts at init time. 194 */ 195 iamrestorecon = 0; 196 recurse = 1; 197 r_opts.expand_realpath = 0; 198 r_opts.abort_on_error = 1; 199 r_opts.add_assoc = 1; 200 r_opts.fts_flags = FTS_PHYSICAL | FTS_XDEV; 201 ctx_validate = 1; 202 opts = sopts; 203 } else { 204 /* 205 * restorecon: 206 * No recursive descent unless -r/-R, 207 * Expands paths via realpath, 208 * Do not abort on errors during the file tree walk, 209 * Do not try to track inode associations for conflict detection, 210 * Follows mounts, 211 * Does lazy validation of contexts upon use. 212 */ 213 if (strcmp(base, RESTORECON) && !r_opts.quiet) 214 printf("Executed with an unrecognized name (%s), defaulting to %s behavior.\n", base, RESTORECON); 215 iamrestorecon = 1; 216 recurse = 0; 217 r_opts.expand_realpath = 1; 218 r_opts.abort_on_error = 0; 219 r_opts.add_assoc = 0; 220 r_opts.fts_flags = FTS_PHYSICAL; 221 ctx_validate = 0; 222 opts = ropts; 223 224 /* restorecon only: silent exit if no SELinux. 225 Allows unconditional execution by scripts. */ 226 if (is_selinux_enabled() <= 0) 227 exit(0); 228 } 229 230 /* This must happen before getopt. */ 231 r_opts.nfile = exclude_non_seclabel_mounts(); 232 233 /* Process any options. */ 234 while ((opt = getopt(argc, argv, opts)) > 0) { 235 switch (opt) { 236 case 'c': 237 { 238 FILE *policystream; 239 240 if (iamrestorecon) 241 usage(argv[0]); 242 243 policyfile = optarg; 244 245 policystream = fopen(policyfile, "r"); 246 if (!policystream) { 247 fprintf(stderr, 248 "Error opening %s: %s\n", 249 policyfile, strerror(errno)); 250 exit(-1); 251 } 252 __fsetlocking(policystream, 253 FSETLOCKING_BYCALLER); 254 255 if (sepol_set_policydb_from_file(policystream) < 256 0) { 257 fprintf(stderr, 258 "Error reading policy %s: %s\n", 259 policyfile, strerror(errno)); 260 exit(-1); 261 } 262 fclose(policystream); 263 264 ctx_validate = 1; 265 266 break; 267 } 268 case 'e': 269 remove_exclude(optarg); 270 if (lstat(optarg, &sb) < 0 && errno != EACCES) { 271 fprintf(stderr, "Can't stat exclude path \"%s\", %s - ignoring.\n", 272 optarg, strerror(errno)); 273 break; 274 } 275 if (add_exclude(optarg)) 276 exit(-1); 277 break; 278 case 'f': 279 use_input_file = 1; 280 input_filename = optarg; 281 break; 282 case 'd': 283 if (iamrestorecon) 284 usage(argv[0]); 285 r_opts.debug = 1; 286 break; 287 case 'i': 288 r_opts.ignore_enoent = 1; 289 break; 290 case 'l': 291 r_opts.logging = 1; 292 break; 293 case 'F': 294 r_opts.force = 1; 295 break; 296 case 'n': 297 r_opts.change = 0; 298 break; 299 case 'o': 300 if (strcmp(optarg, "-") == 0) { 301 r_opts.outfile = stdout; 302 break; 303 } 304 305 r_opts.outfile = fopen(optarg, "w"); 306 if (!r_opts.outfile) { 307 fprintf(stderr, "Error opening %s: %s\n", 308 optarg, strerror(errno)); 309 310 usage(argv[0]); 311 } 312 __fsetlocking(r_opts.outfile, FSETLOCKING_BYCALLER); 313 break; 314 case 'q': 315 r_opts.quiet = 1; 316 break; 317 case 'R': 318 case 'r': 319 if (iamrestorecon) { 320 recurse = 1; 321 break; 322 } 323 if (NULL != r_opts.rootpath) { 324 fprintf(stderr, 325 "%s: only one -r can be specified\n", 326 argv[0]); 327 exit(-1); 328 } 329 set_rootpath(optarg); 330 break; 331 case 's': 332 use_input_file = 1; 333 input_filename = "-"; 334 r_opts.add_assoc = 0; 335 break; 336 case 'v': 337 if (r_opts.progress) { 338 fprintf(stderr, 339 "Progress and Verbose mutually exclusive\n"); 340 exit(-1); 341 } 342 r_opts.verbose++; 343 break; 344 case 'p': 345 if (r_opts.verbose) { 346 fprintf(stderr, 347 "Progress and Verbose mutually exclusive\n"); 348 usage(argv[0]); 349 } 350 r_opts.progress++; 351 break; 352 case 'W': 353 warn_no_match = 1; 354 break; 355 case '0': 356 null_terminated = 1; 357 break; 358 case 'h': 359 case '?': 360 usage(argv[0]); 361 } 362 } 363 364 for (i = optind; i < argc; i++) { 365 if (!strcmp(argv[i], "/")) { 366 mass_relabel = 1; 367 if (r_opts.progress) 368 r_opts.progress++; 369 } 370 } 371 372 if (!iamrestorecon) { 373 if (policyfile) { 374 if (optind != (argc - 1)) 375 usage(argv[0]); 376 } else if (use_input_file) { 377 if (optind != (argc - 1)) { 378 /* Cannot mix with pathname arguments. */ 379 usage(argv[0]); 380 } 381 } else { 382 if (optind > (argc - 2)) 383 usage(argv[0]); 384 } 385 386 /* Use our own invalid context checking function so that 387 we can support either checking against the active policy or 388 checking against a binary policy file. */ 389 selinux_set_callback(SELINUX_CB_VALIDATE, 390 (union selinux_callback)&canoncon); 391 392 if (stat(argv[optind], &sb) < 0) { 393 perror(argv[optind]); 394 exit(-1); 395 } 396 if (!S_ISREG(sb.st_mode)) { 397 fprintf(stderr, "%s: spec file %s is not a regular file.\n", 398 argv[0], argv[optind]); 399 exit(-1); 400 } 401 402 altpath = argv[optind]; 403 optind++; 404 } else if (argc == 1) 405 usage(argv[0]); 406 407 /* Load the file contexts configuration and check it. */ 408 r_opts.selabel_opt_validate = (ctx_validate ? (char *)1 : NULL); 409 r_opts.selabel_opt_path = altpath; 410 411 if (nerr) 412 exit(-1); 413 414 restore_init(&r_opts); 415 if (use_input_file) { 416 FILE *f = stdin; 417 ssize_t len; 418 int delim; 419 if (strcmp(input_filename, "-") != 0) 420 f = fopen(input_filename, "r"); 421 if (f == NULL) { 422 fprintf(stderr, "Unable to open %s: %s\n", input_filename, 423 strerror(errno)); 424 usage(argv[0]); 425 } 426 __fsetlocking(f, FSETLOCKING_BYCALLER); 427 428 delim = (null_terminated != 0) ? '\0' : '\n'; 429 while ((len = getdelim(&buf, &buf_len, delim, f)) > 0) { 430 buf[len - 1] = 0; 431 if (!strcmp(buf, "/")) 432 mass_relabel = 1; 433 errors |= process_glob(buf, recurse) < 0; 434 } 435 if (strcmp(input_filename, "-") != 0) 436 fclose(f); 437 } else { 438 for (i = optind; i < argc; i++) 439 errors |= process_glob(argv[i], recurse) < 0; 440 } 441 442 maybe_audit_mass_relabel(mass_relabel, errors); 443 444 if (warn_no_match) 445 selabel_stats(r_opts.hnd); 446 447 selabel_close(r_opts.hnd); 448 restore_finish(); 449 450 if (r_opts.outfile) 451 fclose(r_opts.outfile); 452 453 if (r_opts.progress && r_opts.count >= STAR_COUNT) 454 printf("\n"); 455 exit(errors ? -1: 0); 456 } 457