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