1 #include "restore.h"
2 #include <glob.h>
3 #include <selinux/context.h>
4
5 #define SKIP -2
6 #define ERR -1
7 #define MAX_EXCLUDES 1000
8
9 /*
10 * The hash table of associations, hashed by inode number.
11 * Chaining is used for collisions, with elements ordered
12 * by inode number in each bucket. Each hash bucket has a dummy
13 * header.
14 */
15 #define HASH_BITS 16
16 #define HASH_BUCKETS (1 << HASH_BITS)
17 #define HASH_MASK (HASH_BUCKETS-1)
18
19 /*
20 * An association between an inode and a context.
21 */
22 typedef struct file_spec {
23 ino_t ino; /* inode number */
24 char *con; /* matched context */
25 char *file; /* full pathname */
26 struct file_spec *next; /* next association in hash bucket chain */
27 } file_spec_t;
28
29 struct edir {
30 char *directory;
31 size_t size;
32 };
33
34
35 static file_spec_t *fl_head;
36 static int filespec_add(ino_t ino, const security_context_t con, const char *file);
37 struct restore_opts *r_opts = NULL;
38 static void filespec_destroy(void);
39 static void filespec_eval(void);
40 static int excludeCtr = 0;
41 static struct edir excludeArray[MAX_EXCLUDES];
42
remove_exclude(const char * directory)43 void remove_exclude(const char *directory)
44 {
45 int i = 0;
46 for (i = 0; i < excludeCtr; i++) {
47 if (strcmp(directory, excludeArray[i].directory) == 0) {
48 free(excludeArray[i].directory);
49 if (i != excludeCtr-1)
50 excludeArray[i] = excludeArray[excludeCtr-1];
51 excludeCtr--;
52 return;
53 }
54 }
55 return;
56 }
57
restore_init(struct restore_opts * opts)58 void restore_init(struct restore_opts *opts)
59 {
60 r_opts = opts;
61 struct selinux_opt selinux_opts[] = {
62 { SELABEL_OPT_VALIDATE, r_opts->selabel_opt_validate },
63 { SELABEL_OPT_PATH, r_opts->selabel_opt_path }
64 };
65 r_opts->hnd = selabel_open(SELABEL_CTX_FILE, selinux_opts, 2);
66 if (!r_opts->hnd) {
67 perror(r_opts->selabel_opt_path);
68 exit(1);
69 }
70 }
71
restore_finish()72 void restore_finish()
73 {
74 int i;
75 for (i = 0; i < excludeCtr; i++) {
76 free(excludeArray[i].directory);
77 }
78 }
79
match(const char * name,struct stat * sb,char ** con)80 static int match(const char *name, struct stat *sb, char **con)
81 {
82 if (!(r_opts->hard_links) && !S_ISDIR(sb->st_mode) && (sb->st_nlink > 1)) {
83 fprintf(stderr, "Warning! %s refers to a file with more than one hard link, not fixing hard links.\n",
84 name);
85 return -1;
86 }
87
88 if (NULL != r_opts->rootpath) {
89 if (0 != strncmp(r_opts->rootpath, name, r_opts->rootpathlen)) {
90 fprintf(stderr, "%s: %s is not located in %s\n",
91 r_opts->progname, name, r_opts->rootpath);
92 return -1;
93 }
94 name += r_opts->rootpathlen;
95 }
96
97 if (r_opts->rootpath != NULL && name[0] == '\0')
98 /* this is actually the root dir of the alt root */
99 return selabel_lookup_raw(r_opts->hnd, con, "/", sb->st_mode);
100 else
101 return selabel_lookup_raw(r_opts->hnd, con, name, sb->st_mode);
102 }
restore(FTSENT * ftsent,int recurse)103 static int restore(FTSENT *ftsent, int recurse)
104 {
105 char *my_file = strdupa(ftsent->fts_path);
106 int ret = -1;
107 security_context_t curcon = NULL, newcon = NULL;
108 float progress;
109 if (match(my_file, ftsent->fts_statp, &newcon) < 0) {
110 if ((errno == ENOENT) && ((!recurse) || (r_opts->verbose)))
111 fprintf(stderr, "%s: Warning no default label for %s\n", r_opts->progname, my_file);
112
113 /* Check for no matching specification. */
114 return (errno == ENOENT) ? 0 : -1;
115 }
116
117 if (r_opts->progress) {
118 r_opts->count++;
119 if (r_opts->count % STAR_COUNT == 0) {
120 if (r_opts->progress == 1) {
121 fprintf(stdout, "\r%luk", (size_t) r_opts->count / STAR_COUNT );
122 } else {
123 if (r_opts->nfile > 0) {
124 progress = (r_opts->count < r_opts->nfile) ? (100.0 * r_opts->count / r_opts->nfile) : 100;
125 fprintf(stdout, "\r%-.1f%%", progress);
126 }
127 }
128 fflush(stdout);
129 }
130 }
131
132 /*
133 * Try to add an association between this inode and
134 * this specification. If there is already an association
135 * for this inode and it conflicts with this specification,
136 * then use the last matching specification.
137 */
138 if (r_opts->add_assoc) {
139 ret = filespec_add(ftsent->fts_statp->st_ino, newcon, my_file);
140 if (ret < 0)
141 goto err;
142
143 if (ret > 0)
144 /* There was already an association and it took precedence. */
145 goto out;
146 }
147
148 if (r_opts->debug) {
149 printf("%s: %s matched by %s\n", r_opts->progname, my_file, newcon);
150 }
151
152 /*
153 * Do not relabel if their is no default specification for this file
154 */
155
156 if (strcmp(newcon, "<<none>>") == 0) {
157 goto out;
158 }
159
160 /* Get the current context of the file. */
161 ret = lgetfilecon_raw(ftsent->fts_accpath, &curcon);
162 if (ret < 0) {
163 if (errno == ENODATA) {
164 curcon = NULL;
165 } else {
166 fprintf(stderr, "%s get context on %s failed: '%s'\n",
167 r_opts->progname, my_file, strerror(errno));
168 goto err;
169 }
170 }
171
172 /* lgetfilecon returns number of characters and ret needs to be reset
173 * to 0.
174 */
175 ret = 0;
176
177 /*
178 * Do not relabel the file if the file is already labeled according to
179 * the specification.
180 */
181 if (curcon && (strcmp(curcon, newcon) == 0)) {
182 goto out;
183 }
184
185 if (!r_opts->force && curcon && (is_context_customizable(curcon) > 0)) {
186 if (r_opts->verbose > 1) {
187 fprintf(stderr,
188 "%s: %s not reset customized by admin to %s\n",
189 r_opts->progname, my_file, curcon);
190 }
191 goto out;
192 }
193
194 /*
195 * Do not change label unless this is a force or the type is different
196 */
197 if (!r_opts->force && curcon) {
198 int types_differ = 0;
199 context_t cona;
200 context_t conb;
201 int err = 0;
202 cona = context_new(curcon);
203 if (! cona) {
204 goto out;
205 }
206 conb = context_new(newcon);
207 if (! conb) {
208 context_free(cona);
209 goto out;
210 }
211
212 types_differ = strcmp(context_type_get(cona), context_type_get(conb));
213 if (types_differ) {
214 err |= context_user_set(conb, context_user_get(cona));
215 err |= context_role_set(conb, context_role_get(cona));
216 err |= context_range_set(conb, context_range_get(cona));
217 if (!err) {
218 freecon(newcon);
219 newcon = strdup(context_str(conb));
220 }
221 }
222 context_free(cona);
223 context_free(conb);
224
225 if (!types_differ || err) {
226 goto out;
227 }
228 }
229
230 if (r_opts->verbose) {
231 printf("%s reset %s context %s->%s\n",
232 r_opts->progname, my_file, curcon ?: "", newcon);
233 }
234
235 if (r_opts->logging && r_opts->change) {
236 if (curcon)
237 syslog(LOG_INFO, "relabeling %s from %s to %s\n",
238 my_file, curcon, newcon);
239 else
240 syslog(LOG_INFO, "labeling %s to %s\n",
241 my_file, newcon);
242 }
243
244 if (r_opts->outfile)
245 fprintf(r_opts->outfile, "%s\n", my_file);
246
247 /*
248 * Do not relabel the file if -n was used.
249 */
250 if (!r_opts->change)
251 goto out;
252
253 /*
254 * Relabel the file to the specified context.
255 */
256 ret = lsetfilecon(ftsent->fts_accpath, newcon);
257 if (ret) {
258 fprintf(stderr, "%s set context %s->%s failed:'%s'\n",
259 r_opts->progname, my_file, newcon, strerror(errno));
260 goto skip;
261 }
262 ret = 0;
263 out:
264 freecon(curcon);
265 freecon(newcon);
266 return ret;
267 skip:
268 freecon(curcon);
269 freecon(newcon);
270 return SKIP;
271 err:
272 freecon(curcon);
273 freecon(newcon);
274 return ERR;
275 }
276 /*
277 * Apply the last matching specification to a file.
278 * This function is called by fts on each file during
279 * the directory traversal.
280 */
apply_spec(FTSENT * ftsent,int recurse)281 static int apply_spec(FTSENT *ftsent, int recurse)
282 {
283 if (ftsent->fts_info == FTS_DNR) {
284 fprintf(stderr, "%s: unable to read directory %s\n",
285 r_opts->progname, ftsent->fts_path);
286 return SKIP;
287 }
288
289 int rc = restore(ftsent, recurse);
290 if (rc == ERR) {
291 if (!r_opts->abort_on_error)
292 return SKIP;
293 }
294 return rc;
295 }
296
297 #include <sys/statvfs.h>
298
process_one(char * name,int recurse_this_path)299 static int process_one(char *name, int recurse_this_path)
300 {
301 int rc = 0;
302 const char *namelist[2] = {name, NULL};
303 dev_t dev_num = 0;
304 FTS *fts_handle = NULL;
305 FTSENT *ftsent = NULL;
306
307 if (r_opts == NULL){
308 fprintf(stderr,
309 "Must call initialize first!");
310 goto err;
311 }
312
313 fts_handle = fts_open((char **)namelist, r_opts->fts_flags, NULL);
314 if (fts_handle == NULL) {
315 fprintf(stderr,
316 "%s: error while labeling %s: %s\n",
317 r_opts->progname, namelist[0], strerror(errno));
318 goto err;
319 }
320
321
322 ftsent = fts_read(fts_handle);
323 if (ftsent == NULL) {
324 fprintf(stderr,
325 "%s: error while labeling %s: %s\n",
326 r_opts->progname, namelist[0], strerror(errno));
327 goto err;
328 }
329
330 /* Keep the inode of the first one. */
331 dev_num = ftsent->fts_statp->st_dev;
332
333 do {
334 rc = 0;
335 /* Skip the post order nodes. */
336 if (ftsent->fts_info == FTS_DP)
337 continue;
338 /* If the XDEV flag is set and the device is different */
339 if (ftsent->fts_statp->st_dev != dev_num &&
340 FTS_XDEV == (r_opts->fts_flags & FTS_XDEV))
341 continue;
342 if (excludeCtr > 0) {
343 if (exclude(ftsent->fts_path)) {
344 fts_set(fts_handle, ftsent, FTS_SKIP);
345 continue;
346 }
347 }
348
349 rc = apply_spec(ftsent, recurse_this_path);
350 if (rc == SKIP)
351 fts_set(fts_handle, ftsent, FTS_SKIP);
352 if (rc == ERR)
353 goto err;
354 if (!recurse_this_path)
355 break;
356 } while ((ftsent = fts_read(fts_handle)) != NULL);
357
358 out:
359 if (r_opts->add_assoc) {
360 if (!r_opts->quiet)
361 filespec_eval();
362 filespec_destroy();
363 }
364 if (fts_handle)
365 fts_close(fts_handle);
366 return rc;
367
368 err:
369 rc = -1;
370 goto out;
371 }
372
process_glob(char * name,int recurse)373 int process_glob(char *name, int recurse) {
374 glob_t globbuf;
375 size_t i = 0;
376 int errors;
377 memset(&globbuf, 0, sizeof(globbuf));
378 errors = glob(name, GLOB_TILDE | GLOB_PERIOD | GLOB_NOCHECK | GLOB_BRACE, NULL, &globbuf);
379 if (errors)
380 return errors;
381
382 for (i = 0; i < globbuf.gl_pathc; i++) {
383 int len = strlen(globbuf.gl_pathv[i]) -2;
384 if (len > 0 && strcmp(&globbuf.gl_pathv[i][len--], "/.") == 0)
385 continue;
386 if (len > 0 && strcmp(&globbuf.gl_pathv[i][len], "/..") == 0)
387 continue;
388 int rc = process_one_realpath(globbuf.gl_pathv[i], recurse);
389 if (rc < 0)
390 errors = rc;
391 }
392 globfree(&globbuf);
393 return errors;
394 }
395
process_one_realpath(char * name,int recurse)396 int process_one_realpath(char *name, int recurse)
397 {
398 int rc = 0;
399 char *p;
400 struct stat64 sb;
401
402 if (r_opts == NULL){
403 fprintf(stderr,
404 "Must call initialize first!");
405 return -1;
406 }
407
408 if (!r_opts->expand_realpath) {
409 return process_one(name, recurse);
410 } else {
411 rc = lstat64(name, &sb);
412 if (rc < 0) {
413 if (r_opts->ignore_enoent && errno == ENOENT)
414 return 0;
415 fprintf(stderr, "%s: lstat(%s) failed: %s\n",
416 r_opts->progname, name, strerror(errno));
417 return -1;
418 }
419
420 if (S_ISLNK(sb.st_mode)) {
421 char path[PATH_MAX + 1];
422
423 rc = realpath_not_final(name, path);
424 if (rc < 0)
425 return rc;
426 rc = process_one(path, 0);
427 if (rc < 0)
428 return rc;
429
430 p = realpath(name, NULL);
431 if (p) {
432 rc = process_one(p, recurse);
433 free(p);
434 }
435 return rc;
436 } else {
437 p = realpath(name, NULL);
438 if (!p) {
439 fprintf(stderr, "realpath(%s) failed %s\n", name,
440 strerror(errno));
441 return -1;
442 }
443 rc = process_one(p, recurse);
444 free(p);
445 return rc;
446 }
447 }
448 }
449
exclude(const char * file)450 int exclude(const char *file)
451 {
452 int i = 0;
453 for (i = 0; i < excludeCtr; i++) {
454 if (strncmp
455 (file, excludeArray[i].directory,
456 excludeArray[i].size) == 0) {
457 if (file[excludeArray[i].size] == 0
458 || file[excludeArray[i].size] == '/') {
459 return 1;
460 }
461 }
462 }
463 return 0;
464 }
465
add_exclude(const char * directory)466 int add_exclude(const char *directory)
467 {
468 size_t len = 0;
469
470 if (directory == NULL || directory[0] != '/') {
471 fprintf(stderr, "Full path required for exclude: %s.\n",
472 directory);
473 return 1;
474 }
475 if (excludeCtr == MAX_EXCLUDES) {
476 fprintf(stderr, "Maximum excludes %d exceeded.\n",
477 MAX_EXCLUDES);
478 return 1;
479 }
480
481 len = strlen(directory);
482 while (len > 1 && directory[len - 1] == '/') {
483 len--;
484 }
485 excludeArray[excludeCtr].directory = strndup(directory, len);
486
487 if (excludeArray[excludeCtr].directory == NULL) {
488 fprintf(stderr, "Out of memory.\n");
489 return 1;
490 }
491 excludeArray[excludeCtr++].size = len;
492
493 return 0;
494 }
495
496 /*
497 * Evaluate the association hash table distribution.
498 */
filespec_eval(void)499 static void filespec_eval(void)
500 {
501 file_spec_t *fl;
502 int h, used, nel, len, longest;
503
504 if (!fl_head)
505 return;
506
507 used = 0;
508 longest = 0;
509 nel = 0;
510 for (h = 0; h < HASH_BUCKETS; h++) {
511 len = 0;
512 for (fl = fl_head[h].next; fl; fl = fl->next) {
513 len++;
514 }
515 if (len)
516 used++;
517 if (len > longest)
518 longest = len;
519 nel += len;
520 }
521
522 if (r_opts->verbose > 1)
523 printf
524 ("%s: hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
525 __FUNCTION__, nel, used, HASH_BUCKETS, longest);
526 }
527
528 /*
529 * Destroy the association hash table.
530 */
filespec_destroy(void)531 static void filespec_destroy(void)
532 {
533 file_spec_t *fl, *tmp;
534 int h;
535
536 if (!fl_head)
537 return;
538
539 for (h = 0; h < HASH_BUCKETS; h++) {
540 fl = fl_head[h].next;
541 while (fl) {
542 tmp = fl;
543 fl = fl->next;
544 freecon(tmp->con);
545 free(tmp->file);
546 free(tmp);
547 }
548 fl_head[h].next = NULL;
549 }
550 free(fl_head);
551 fl_head = NULL;
552 }
553 /*
554 * Try to add an association between an inode and a context.
555 * If there is a different context that matched the inode,
556 * then use the first context that matched.
557 */
filespec_add(ino_t ino,const security_context_t con,const char * file)558 static int filespec_add(ino_t ino, const security_context_t con, const char *file)
559 {
560 file_spec_t *prevfl, *fl;
561 int h, ret;
562 struct stat64 sb;
563
564 if (!fl_head) {
565 fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS);
566 if (!fl_head)
567 goto oom;
568 memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS);
569 }
570
571 h = (ino + (ino >> HASH_BITS)) & HASH_MASK;
572 for (prevfl = &fl_head[h], fl = fl_head[h].next; fl;
573 prevfl = fl, fl = fl->next) {
574 if (ino == fl->ino) {
575 ret = lstat64(fl->file, &sb);
576 if (ret < 0 || sb.st_ino != ino) {
577 freecon(fl->con);
578 free(fl->file);
579 fl->file = strdup(file);
580 if (!fl->file)
581 goto oom;
582 fl->con = strdup(con);
583 if (!fl->con)
584 goto oom;
585 return 1;
586 }
587
588 if (strcmp(fl->con, con) == 0)
589 return 1;
590
591 fprintf(stderr,
592 "%s: conflicting specifications for %s and %s, using %s.\n",
593 __FUNCTION__, file, fl->file, fl->con);
594 free(fl->file);
595 fl->file = strdup(file);
596 if (!fl->file)
597 goto oom;
598 return 1;
599 }
600
601 if (ino > fl->ino)
602 break;
603 }
604
605 fl = malloc(sizeof(file_spec_t));
606 if (!fl)
607 goto oom;
608 fl->ino = ino;
609 fl->con = strdup(con);
610 if (!fl->con)
611 goto oom_freefl;
612 fl->file = strdup(file);
613 if (!fl->file)
614 goto oom_freefl;
615 fl->next = prevfl->next;
616 prevfl->next = fl;
617 return 0;
618 oom_freefl:
619 free(fl);
620 oom:
621 fprintf(stderr,
622 "%s: insufficient memory for file label entry for %s\n",
623 __FUNCTION__, file);
624 return -1;
625 }
626
627 #include <sys/utsname.h>
file_system_count(char * name)628 int file_system_count(char *name) {
629 struct statvfs statvfs_buf;
630 int nfile = 0;
631 memset(&statvfs_buf, 0, sizeof(statvfs_buf));
632 if (!statvfs(name, &statvfs_buf)) {
633 nfile = statvfs_buf.f_files - statvfs_buf.f_ffree;
634 }
635 return nfile;
636 }
637
638 /*
639 Search /proc/mounts for all file systems that do not support extended
640 attributes and add them to the exclude directory table. File systems
641 that support security labels have the seclabel option, return total file count
642 */
exclude_non_seclabel_mounts()643 int exclude_non_seclabel_mounts()
644 {
645 struct utsname uts;
646 FILE *fp;
647 size_t len;
648 ssize_t num;
649 int index = 0, found = 0;
650 char *mount_info[4];
651 char *buf = NULL, *item;
652 int nfile = 0;
653 /* Check to see if the kernel supports seclabel */
654 if (uname(&uts) == 0 && strverscmp(uts.release, "2.6.30") < 0)
655 return 0;
656 if (is_selinux_enabled() <= 0)
657 return 0;
658
659 fp = fopen("/proc/mounts", "r");
660 if (!fp)
661 return 0;
662
663 while ((num = getline(&buf, &len, fp)) != -1) {
664 found = 0;
665 index = 0;
666 item = strtok(buf, " ");
667 while (item != NULL) {
668 mount_info[index] = item;
669 if (index == 3)
670 break;
671 index++;
672 item = strtok(NULL, " ");
673 }
674 if (index < 3) {
675 fprintf(stderr,
676 "/proc/mounts record \"%s\" has incorrect format.\n",
677 buf);
678 continue;
679 }
680
681 /* remove pre-existing entry */
682 remove_exclude(mount_info[1]);
683
684 item = strtok(mount_info[3], ",");
685 while (item != NULL) {
686 if (strcmp(item, "seclabel") == 0) {
687 found = 1;
688 nfile += file_system_count(mount_info[1]);
689 break;
690 }
691 item = strtok(NULL, ",");
692 }
693
694 /* exclude mount points without the seclabel option */
695 if (!found)
696 add_exclude(mount_info[1]);
697 }
698
699 free(buf);
700 fclose(fp);
701 /* return estimated #Files + 5% for directories and hard links */
702 return nfile * 1.05;
703 }
704
705