1 /*
2 * The majority of this code is from Android's
3 * external/libselinux/src/android.c and upstream
4 * selinux/policycoreutils/setfiles/restore.c
5 *
6 * See selinux_restorecon(3) for details.
7 */
8
9 #include <unistd.h>
10 #include <string.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <stdbool.h>
14 #include <ctype.h>
15 #include <errno.h>
16 #include <fcntl.h>
17 #include <fts.h>
18 #include <inttypes.h>
19 #include <limits.h>
20 #include <stdint.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <sys/xattr.h>
24 #include <sys/vfs.h>
25 #include <sys/statvfs.h>
26 #include <sys/utsname.h>
27 #include <linux/magic.h>
28 #include <libgen.h>
29 #include <syslog.h>
30 #include <assert.h>
31
32 #include <selinux/selinux.h>
33 #include <selinux/context.h>
34 #include <selinux/label.h>
35 #include <selinux/restorecon.h>
36
37 #include "callbacks.h"
38 #include "selinux_internal.h"
39
40 #define RESTORECON_LAST "security.restorecon_last"
41
42 #define SYS_PATH "/sys"
43 #define SYS_PREFIX SYS_PATH "/"
44
45 #define STAR_COUNT 1024
46
47 static struct selabel_handle *fc_sehandle = NULL;
48 static unsigned char *fc_digest = NULL;
49 static size_t fc_digest_len = 0;
50 static char *rootpath = NULL;
51 static int rootpathlen;
52
53 /* Information on excluded fs and directories. */
54 struct edir {
55 char *directory;
56 size_t size;
57 /* True if excluded by selinux_restorecon_set_exclude_list(3). */
58 bool caller_excluded;
59 };
60 #define CALLER_EXCLUDED true
61 static bool ignore_mounts;
62 static int exclude_non_seclabel_mounts(void);
63 static int exclude_count = 0;
64 static struct edir *exclude_lst = NULL;
65 static uint64_t fc_count = 0; /* Number of files processed so far */
66 static uint64_t efile_count; /* Estimated total number of files */
67
68 /* Store information on directories with xattr's. */
69 struct dir_xattr *dir_xattr_list;
70 static struct dir_xattr *dir_xattr_last;
71
72 /* restorecon_flags for passing to restorecon_sb() */
73 struct rest_flags {
74 bool nochange;
75 bool verbose;
76 bool progress;
77 bool mass_relabel;
78 bool set_specctx;
79 bool add_assoc;
80 bool ignore_digest;
81 bool recurse;
82 bool userealpath;
83 bool set_xdev;
84 bool abort_on_error;
85 bool syslog_changes;
86 bool log_matches;
87 bool ignore_noent;
88 bool warnonnomatch;
89 };
90
restorecon_init(void)91 static void restorecon_init(void)
92 {
93 struct selabel_handle *sehandle = NULL;
94
95 if (!fc_sehandle) {
96 sehandle = selinux_restorecon_default_handle();
97 selinux_restorecon_set_sehandle(sehandle);
98 }
99
100 efile_count = 0;
101 if (!ignore_mounts)
102 efile_count = exclude_non_seclabel_mounts();
103 }
104
105 static pthread_once_t fc_once = PTHREAD_ONCE_INIT;
106
107 /*
108 * Manage excluded directories:
109 * remove_exclude() - This removes any conflicting entries as there could be
110 * a case where a non-seclabel fs is mounted on /foo and
111 * then a seclabel fs is mounted on top of it.
112 * However if an entry has been added via
113 * selinux_restorecon_set_exclude_list(3) do not remove.
114 *
115 * add_exclude() - Add a directory/fs to be excluded from labeling. If it
116 * has already been added, then ignore.
117 *
118 * check_excluded() - Check if directory/fs is to be excluded when relabeling.
119 *
120 * file_system_count() - Calculates the number of files to be processed.
121 * The count is only used if SELINUX_RESTORECON_PROGRESS
122 * is set and a mass relabel is requested.
123 *
124 * exclude_non_seclabel_mounts() - Reads /proc/mounts to determine what
125 * non-seclabel mounts to exclude from
126 * relabeling. restorecon_init() will not
127 * call this function if the
128 * SELINUX_RESTORECON_IGNORE_MOUNTS
129 * flag is set.
130 * Setting SELINUX_RESTORECON_IGNORE_MOUNTS
131 * is useful where there is a non-seclabel fs
132 * mounted on /foo and then a seclabel fs is
133 * mounted on a directory below this.
134 */
remove_exclude(const char * directory)135 static void remove_exclude(const char *directory)
136 {
137 int i;
138
139 for (i = 0; i < exclude_count; i++) {
140 if (strcmp(directory, exclude_lst[i].directory) == 0 &&
141 !exclude_lst[i].caller_excluded) {
142 free(exclude_lst[i].directory);
143 if (i != exclude_count - 1)
144 exclude_lst[i] = exclude_lst[exclude_count - 1];
145 exclude_count--;
146 return;
147 }
148 }
149 }
150
add_exclude(const char * directory,bool who)151 static int add_exclude(const char *directory, bool who)
152 {
153 struct edir *tmp_list, *current;
154 size_t len = 0;
155 int i;
156
157 /* Check if already present. */
158 for (i = 0; i < exclude_count; i++) {
159 if (strcmp(directory, exclude_lst[i].directory) == 0)
160 return 0;
161 }
162
163 if (directory == NULL || directory[0] != '/') {
164 selinux_log(SELINUX_ERROR,
165 "Full path required for exclude: %s.\n",
166 directory);
167 errno = EINVAL;
168 return -1;
169 }
170
171 tmp_list = realloc(exclude_lst,
172 sizeof(struct edir) * (exclude_count + 1));
173 if (!tmp_list)
174 goto oom;
175
176 exclude_lst = tmp_list;
177
178 len = strlen(directory);
179 while (len > 1 && directory[len - 1] == '/')
180 len--;
181
182 current = (exclude_lst + exclude_count);
183
184 current->directory = strndup(directory, len);
185 if (!current->directory)
186 goto oom;
187
188 current->size = len;
189 current->caller_excluded = who;
190 exclude_count++;
191 return 0;
192
193 oom:
194 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
195 return -1;
196 }
197
check_excluded(const char * file)198 static int check_excluded(const char *file)
199 {
200 int i;
201
202 for (i = 0; i < exclude_count; i++) {
203 if (strncmp(file, exclude_lst[i].directory,
204 exclude_lst[i].size) == 0) {
205 if (file[exclude_lst[i].size] == 0 ||
206 file[exclude_lst[i].size] == '/')
207 return 1;
208 }
209 }
210 return 0;
211 }
212
file_system_count(char * name)213 static int file_system_count(char *name)
214 {
215 struct statvfs statvfs_buf;
216 int nfile = 0;
217
218 memset(&statvfs_buf, 0, sizeof(statvfs_buf));
219 if (!statvfs(name, &statvfs_buf))
220 nfile = statvfs_buf.f_files - statvfs_buf.f_ffree;
221
222 return nfile;
223 }
224
225 /*
226 * This is called once when selinux_restorecon() is first called.
227 * Searches /proc/mounts for all file systems that do not support extended
228 * attributes and adds them to the exclude directory table. File systems
229 * that support security labels have the seclabel option, return
230 * approximate total file count.
231 */
exclude_non_seclabel_mounts(void)232 static int exclude_non_seclabel_mounts(void)
233 {
234 struct utsname uts;
235 FILE *fp;
236 size_t len;
237 ssize_t num;
238 int index = 0, found = 0, nfile = 0;
239 char *mount_info[4];
240 char *buf = NULL, *item;
241
242 /* Check to see if the kernel supports seclabel */
243 if (uname(&uts) == 0 && strverscmp(uts.release, "2.6.30") < 0)
244 return 0;
245 if (is_selinux_enabled() <= 0)
246 return 0;
247
248 fp = fopen("/proc/mounts", "re");
249 if (!fp)
250 return 0;
251
252 while ((num = getline(&buf, &len, fp)) != -1) {
253 found = 0;
254 index = 0;
255 item = strtok(buf, " ");
256 while (item != NULL) {
257 mount_info[index] = item;
258 index++;
259 if (index == 4)
260 break;
261 item = strtok(NULL, " ");
262 }
263 if (index < 4) {
264 selinux_log(SELINUX_ERROR,
265 "/proc/mounts record \"%s\" has incorrect format.\n",
266 buf);
267 continue;
268 }
269
270 /* Remove pre-existing entry */
271 remove_exclude(mount_info[1]);
272
273 item = strtok(mount_info[3], ",");
274 while (item != NULL) {
275 if (strcmp(item, "seclabel") == 0) {
276 found = 1;
277 nfile += file_system_count(mount_info[1]);
278 break;
279 }
280 item = strtok(NULL, ",");
281 }
282
283 /* Exclude mount points without the seclabel option */
284 if (!found) {
285 if (add_exclude(mount_info[1], !CALLER_EXCLUDED) &&
286 errno == ENOMEM)
287 assert(0);
288 }
289 }
290
291 free(buf);
292 fclose(fp);
293 /* return estimated #Files + 5% for directories and hard links */
294 return nfile * 1.05;
295 }
296
297 /* Called by selinux_restorecon_xattr(3) to build a linked list of entries. */
add_xattr_entry(const char * directory,bool delete_nonmatch,bool delete_all)298 static int add_xattr_entry(const char *directory, bool delete_nonmatch,
299 bool delete_all)
300 {
301 char *sha1_buf = NULL;
302 unsigned char *xattr_value = NULL;
303 ssize_t xattr_size;
304 size_t i;
305 int rc, digest_result;
306 struct dir_xattr *new_entry;
307
308 if (!directory) {
309 errno = EINVAL;
310 return -1;
311 }
312
313 xattr_value = malloc(fc_digest_len);
314 if (!xattr_value)
315 goto oom;
316
317 xattr_size = getxattr(directory, RESTORECON_LAST, xattr_value,
318 fc_digest_len);
319 if (xattr_size < 0) {
320 free(xattr_value);
321 return 1;
322 }
323
324 /* Convert entry to a hex encoded string. */
325 sha1_buf = malloc(xattr_size * 2 + 1);
326 if (!sha1_buf) {
327 free(xattr_value);
328 goto oom;
329 }
330
331 for (i = 0; i < (size_t)xattr_size; i++)
332 sprintf((&sha1_buf[i * 2]), "%02x", xattr_value[i]);
333
334 rc = memcmp(fc_digest, xattr_value, fc_digest_len);
335 digest_result = rc ? NOMATCH : MATCH;
336
337 if ((delete_nonmatch && rc != 0) || delete_all) {
338 digest_result = rc ? DELETED_NOMATCH : DELETED_MATCH;
339 rc = removexattr(directory, RESTORECON_LAST);
340 if (rc) {
341 selinux_log(SELINUX_ERROR,
342 "Error: %s removing xattr \"%s\" from: %s\n",
343 strerror(errno), RESTORECON_LAST, directory);
344 digest_result = ERROR;
345 }
346 }
347 free(xattr_value);
348
349 /* Now add entries to link list. */
350 new_entry = malloc(sizeof(struct dir_xattr));
351 if (!new_entry)
352 goto oom;
353 new_entry->next = NULL;
354
355 new_entry->directory = strdup(directory);
356 if (!new_entry->directory) {
357 free(new_entry);
358 free(sha1_buf);
359 goto oom;
360 }
361
362 new_entry->digest = strdup(sha1_buf);
363 if (!new_entry->digest) {
364 free(new_entry->directory);
365 free(new_entry);
366 free(sha1_buf);
367 goto oom;
368 }
369
370 new_entry->result = digest_result;
371
372 if (!dir_xattr_list) {
373 dir_xattr_list = new_entry;
374 dir_xattr_last = new_entry;
375 } else {
376 dir_xattr_last->next = new_entry;
377 dir_xattr_last = new_entry;
378 }
379
380 free(sha1_buf);
381 return 0;
382
383 oom:
384 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
385 return -1;
386 }
387
388 /*
389 * Support filespec services filespec_add(), filespec_eval() and
390 * filespec_destroy().
391 *
392 * selinux_restorecon(3) uses filespec services when the
393 * SELINUX_RESTORECON_ADD_ASSOC flag is set for adding associations between
394 * an inode and a specification.
395 */
396
397 /*
398 * The hash table of associations, hashed by inode number. Chaining is used
399 * for collisions, with elements ordered by inode number in each bucket.
400 * Each hash bucket has a dummy header.
401 */
402 #define HASH_BITS 16
403 #define HASH_BUCKETS (1 << HASH_BITS)
404 #define HASH_MASK (HASH_BUCKETS-1)
405
406 /*
407 * An association between an inode and a context.
408 */
409 typedef struct file_spec {
410 ino_t ino; /* inode number */
411 char *con; /* matched context */
412 char *file; /* full pathname */
413 struct file_spec *next; /* next association in hash bucket chain */
414 } file_spec_t;
415
416 static file_spec_t *fl_head;
417
418 /*
419 * Try to add an association between an inode and a context. If there is a
420 * different context that matched the inode, then use the first context
421 * that matched.
422 */
filespec_add(ino_t ino,const char * con,const char * file)423 static int filespec_add(ino_t ino, const char *con, const char *file)
424 {
425 file_spec_t *prevfl, *fl;
426 int h, ret;
427 struct stat64 sb;
428
429 if (!fl_head) {
430 fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS);
431 if (!fl_head)
432 goto oom;
433 memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS);
434 }
435
436 h = (ino + (ino >> HASH_BITS)) & HASH_MASK;
437 for (prevfl = &fl_head[h], fl = fl_head[h].next; fl;
438 prevfl = fl, fl = fl->next) {
439 if (ino == fl->ino) {
440 ret = lstat64(fl->file, &sb);
441 if (ret < 0 || sb.st_ino != ino) {
442 freecon(fl->con);
443 free(fl->file);
444 fl->file = strdup(file);
445 if (!fl->file)
446 goto oom;
447 fl->con = strdup(con);
448 if (!fl->con)
449 goto oom;
450 return 1;
451 }
452
453 if (strcmp(fl->con, con) == 0)
454 return 1;
455
456 selinux_log(SELINUX_ERROR,
457 "conflicting specifications for %s and %s, using %s.\n",
458 file, fl->file, fl->con);
459 free(fl->file);
460 fl->file = strdup(file);
461 if (!fl->file)
462 goto oom;
463 return 1;
464 }
465
466 if (ino > fl->ino)
467 break;
468 }
469
470 fl = malloc(sizeof(file_spec_t));
471 if (!fl)
472 goto oom;
473 fl->ino = ino;
474 fl->con = strdup(con);
475 if (!fl->con)
476 goto oom_freefl;
477 fl->file = strdup(file);
478 if (!fl->file)
479 goto oom_freefl;
480 fl->next = prevfl->next;
481 prevfl->next = fl;
482 return 0;
483
484 oom_freefl:
485 free(fl);
486 oom:
487 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
488 return -1;
489 }
490
491 /*
492 * Evaluate the association hash table distribution.
493 */
494 #ifdef DEBUG
filespec_eval(void)495 static void filespec_eval(void)
496 {
497 file_spec_t *fl;
498 int h, used, nel, len, longest;
499
500 if (!fl_head)
501 return;
502
503 used = 0;
504 longest = 0;
505 nel = 0;
506 for (h = 0; h < HASH_BUCKETS; h++) {
507 len = 0;
508 for (fl = fl_head[h].next; fl; fl = fl->next)
509 len++;
510 if (len)
511 used++;
512 if (len > longest)
513 longest = len;
514 nel += len;
515 }
516
517 selinux_log(SELINUX_INFO,
518 "filespec hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
519 nel, used, HASH_BUCKETS, longest);
520 }
521 #else
filespec_eval(void)522 static void filespec_eval(void)
523 {
524 }
525 #endif
526
527 /*
528 * Destroy the association hash table.
529 */
filespec_destroy(void)530 static void filespec_destroy(void)
531 {
532 file_spec_t *fl, *tmp;
533 int h;
534
535 if (!fl_head)
536 return;
537
538 for (h = 0; h < HASH_BUCKETS; h++) {
539 fl = fl_head[h].next;
540 while (fl) {
541 tmp = fl;
542 fl = fl->next;
543 freecon(tmp->con);
544 free(tmp->file);
545 free(tmp);
546 }
547 fl_head[h].next = NULL;
548 }
549 free(fl_head);
550 fl_head = NULL;
551 }
552
553 /*
554 * Called if SELINUX_RESTORECON_SET_SPECFILE_CTX is not set to check if
555 * the type components differ, updating newtypecon if so.
556 */
compare_types(char * curcon,char * newcon,char ** newtypecon)557 static int compare_types(char *curcon, char *newcon, char **newtypecon)
558 {
559 int types_differ = 0;
560 context_t cona;
561 context_t conb;
562 int rc = 0;
563
564 cona = context_new(curcon);
565 if (!cona) {
566 rc = -1;
567 goto out;
568 }
569 conb = context_new(newcon);
570 if (!conb) {
571 context_free(cona);
572 rc = -1;
573 goto out;
574 }
575
576 types_differ = strcmp(context_type_get(cona), context_type_get(conb));
577 if (types_differ) {
578 rc |= context_user_set(conb, context_user_get(cona));
579 rc |= context_role_set(conb, context_role_get(cona));
580 rc |= context_range_set(conb, context_range_get(cona));
581 if (!rc) {
582 *newtypecon = strdup(context_str(conb));
583 if (!*newtypecon) {
584 rc = -1;
585 goto err;
586 }
587 }
588 }
589
590 err:
591 context_free(cona);
592 context_free(conb);
593 out:
594 return rc;
595 }
596
restorecon_sb(const char * pathname,const struct stat * sb,struct rest_flags * flags)597 static int restorecon_sb(const char *pathname, const struct stat *sb,
598 struct rest_flags *flags)
599 {
600 char *newcon = NULL;
601 char *curcon = NULL;
602 char *newtypecon = NULL;
603 int rc;
604 bool updated = false;
605 const char *lookup_path = pathname;
606 float pc;
607
608 if (rootpath) {
609 if (strncmp(rootpath, lookup_path, rootpathlen) != 0) {
610 selinux_log(SELINUX_ERROR,
611 "%s is not located in alt_rootpath %s\n",
612 lookup_path, rootpath);
613 return -1;
614 }
615 lookup_path += rootpathlen;
616 }
617
618 if (rootpath != NULL && lookup_path[0] == '\0')
619 /* this is actually the root dir of the alt root. */
620 rc = selabel_lookup_raw(fc_sehandle, &newcon, "/",
621 sb->st_mode);
622 else
623 rc = selabel_lookup_raw(fc_sehandle, &newcon, lookup_path,
624 sb->st_mode);
625
626 if (rc < 0) {
627 if (errno == ENOENT && flags->warnonnomatch)
628 selinux_log(SELINUX_INFO,
629 "Warning no default label for %s\n",
630 lookup_path);
631
632 return 0; /* no match, but not an error */
633 }
634
635 if (flags->progress) {
636 fc_count++;
637 if (fc_count % STAR_COUNT == 0) {
638 if (flags->mass_relabel && efile_count > 0) {
639 pc = (fc_count < efile_count) ? (100.0 *
640 fc_count / efile_count) : 100;
641 fprintf(stdout, "\r%-.1f%%", (double)pc);
642 } else {
643 fprintf(stdout, "\r%" PRIu64 "k", fc_count / STAR_COUNT);
644 }
645 fflush(stdout);
646 }
647 }
648
649 if (flags->add_assoc) {
650 rc = filespec_add(sb->st_ino, newcon, pathname);
651
652 if (rc < 0) {
653 selinux_log(SELINUX_ERROR,
654 "filespec_add error: %s\n", pathname);
655 freecon(newcon);
656 return -1;
657 }
658
659 if (rc > 0) {
660 /* Already an association and it took precedence. */
661 freecon(newcon);
662 return 0;
663 }
664 }
665
666 if (flags->log_matches)
667 selinux_log(SELINUX_INFO, "%s matched by %s\n",
668 pathname, newcon);
669
670 if (lgetfilecon_raw(pathname, &curcon) < 0) {
671 if (errno != ENODATA)
672 goto err;
673
674 curcon = NULL;
675 }
676
677 if (curcon == NULL || strcmp(curcon, newcon) != 0) {
678 if (!flags->set_specctx && curcon &&
679 (is_context_customizable(curcon) > 0)) {
680 if (flags->verbose) {
681 selinux_log(SELINUX_INFO,
682 "%s not reset as customized by admin to %s\n",
683 pathname, curcon);
684 }
685 goto out;
686 }
687
688 if (!flags->set_specctx && curcon) {
689 /* If types different then update newcon. */
690 rc = compare_types(curcon, newcon, &newtypecon);
691 if (rc)
692 goto err;
693
694 if (newtypecon) {
695 freecon(newcon);
696 newcon = newtypecon;
697 } else {
698 goto out;
699 }
700 }
701
702 if (!flags->nochange) {
703 if (lsetfilecon(pathname, newcon) < 0)
704 goto err;
705 updated = true;
706 }
707
708 if (flags->verbose)
709 selinux_log(SELINUX_INFO,
710 "%s %s from %s to %s\n",
711 updated ? "Relabeled" : "Would relabel",
712 pathname, curcon, newcon);
713
714 if (flags->syslog_changes && !flags->nochange) {
715 if (curcon)
716 syslog(LOG_INFO,
717 "relabeling %s from %s to %s\n",
718 pathname, curcon, newcon);
719 else
720 syslog(LOG_INFO, "labeling %s to %s\n",
721 pathname, newcon);
722 }
723 }
724
725 out:
726 rc = 0;
727 out1:
728 freecon(curcon);
729 freecon(newcon);
730 return rc;
731 err:
732 selinux_log(SELINUX_ERROR,
733 "Could not set context for %s: %s\n",
734 pathname, strerror(errno));
735 rc = -1;
736 goto out1;
737 }
738
739 /*
740 * Public API
741 */
742
743 /* selinux_restorecon(3) - Main function that is responsible for labeling */
selinux_restorecon(const char * pathname_orig,unsigned int restorecon_flags)744 int selinux_restorecon(const char *pathname_orig,
745 unsigned int restorecon_flags)
746 {
747 struct rest_flags flags;
748
749 flags.ignore_digest = (restorecon_flags &
750 SELINUX_RESTORECON_IGNORE_DIGEST) ? true : false;
751 flags.nochange = (restorecon_flags &
752 SELINUX_RESTORECON_NOCHANGE) ? true : false;
753 flags.verbose = (restorecon_flags &
754 SELINUX_RESTORECON_VERBOSE) ? true : false;
755 flags.progress = (restorecon_flags &
756 SELINUX_RESTORECON_PROGRESS) ? true : false;
757 flags.mass_relabel = (restorecon_flags &
758 SELINUX_RESTORECON_MASS_RELABEL) ? true : false;
759 flags.recurse = (restorecon_flags &
760 SELINUX_RESTORECON_RECURSE) ? true : false;
761 flags.set_specctx = (restorecon_flags &
762 SELINUX_RESTORECON_SET_SPECFILE_CTX) ? true : false;
763 flags.userealpath = (restorecon_flags &
764 SELINUX_RESTORECON_REALPATH) ? true : false;
765 flags.set_xdev = (restorecon_flags &
766 SELINUX_RESTORECON_XDEV) ? true : false;
767 flags.add_assoc = (restorecon_flags &
768 SELINUX_RESTORECON_ADD_ASSOC) ? true : false;
769 flags.abort_on_error = (restorecon_flags &
770 SELINUX_RESTORECON_ABORT_ON_ERROR) ? true : false;
771 flags.syslog_changes = (restorecon_flags &
772 SELINUX_RESTORECON_SYSLOG_CHANGES) ? true : false;
773 flags.log_matches = (restorecon_flags &
774 SELINUX_RESTORECON_LOG_MATCHES) ? true : false;
775 flags.ignore_noent = (restorecon_flags &
776 SELINUX_RESTORECON_IGNORE_NOENTRY) ? true : false;
777 flags.warnonnomatch = true;
778 ignore_mounts = (restorecon_flags &
779 SELINUX_RESTORECON_IGNORE_MOUNTS) ? true : false;
780
781 bool issys;
782 bool setrestoreconlast = true; /* TRUE = set xattr RESTORECON_LAST
783 * FALSE = don't use xattr */
784 struct stat sb;
785 struct statfs sfsb;
786 FTS *fts;
787 FTSENT *ftsent;
788 char *pathname = NULL, *pathdnamer = NULL, *pathdname, *pathbname;
789 char *paths[2] = { NULL, NULL };
790 int fts_flags, error, sverrno;
791 char *xattr_value = NULL;
792 ssize_t size;
793 dev_t dev_num = 0;
794
795 if (flags.verbose && flags.progress)
796 flags.verbose = false;
797
798 __selinux_once(fc_once, restorecon_init);
799
800 if (!fc_sehandle)
801 return -1;
802
803 if (fc_digest_len) {
804 xattr_value = malloc(fc_digest_len);
805 if (!xattr_value)
806 return -1;
807 }
808
809 /*
810 * Convert passed-in pathname to canonical pathname by resolving
811 * realpath of containing dir, then appending last component name.
812 */
813 if (flags.userealpath) {
814 char *basename_cpy = strdup(pathname_orig);
815 if (!basename_cpy)
816 goto realpatherr;
817 pathbname = basename(basename_cpy);
818 if (!strcmp(pathbname, "/") || !strcmp(pathbname, ".") ||
819 !strcmp(pathbname, "..")) {
820 pathname = realpath(pathname_orig, NULL);
821 if (!pathname) {
822 free(basename_cpy);
823 goto realpatherr;
824 }
825 } else {
826 char *dirname_cpy = strdup(pathname_orig);
827 if (!dirname_cpy) {
828 free(basename_cpy);
829 goto realpatherr;
830 }
831 pathdname = dirname(dirname_cpy);
832 pathdnamer = realpath(pathdname, NULL);
833 free(dirname_cpy);
834 if (!pathdnamer) {
835 free(basename_cpy);
836 goto realpatherr;
837 }
838 if (!strcmp(pathdnamer, "/"))
839 error = asprintf(&pathname, "/%s", pathbname);
840 else
841 error = asprintf(&pathname, "%s/%s",
842 pathdnamer, pathbname);
843 if (error < 0) {
844 free(basename_cpy);
845 goto oom;
846 }
847 }
848 free(basename_cpy);
849 } else {
850 pathname = strdup(pathname_orig);
851 if (!pathname)
852 goto oom;
853 }
854
855 paths[0] = pathname;
856 issys = (!strcmp(pathname, SYS_PATH) ||
857 !strncmp(pathname, SYS_PREFIX,
858 sizeof(SYS_PREFIX) - 1)) ? true : false;
859
860 if (lstat(pathname, &sb) < 0) {
861 if (flags.ignore_noent && errno == ENOENT) {
862 free(xattr_value);
863 free(pathdnamer);
864 free(pathname);
865 return 0;
866 } else {
867 selinux_log(SELINUX_ERROR,
868 "lstat(%s) failed: %s\n",
869 pathname, strerror(errno));
870 error = -1;
871 goto cleanup;
872 }
873 }
874
875 /* Ignore restoreconlast if not a directory */
876 if ((sb.st_mode & S_IFDIR) != S_IFDIR)
877 setrestoreconlast = false;
878
879 if (!flags.recurse) {
880 if (check_excluded(pathname)) {
881 error = 0;
882 goto cleanup;
883 }
884
885 error = restorecon_sb(pathname, &sb, &flags);
886 goto cleanup;
887 }
888
889 /* Ignore restoreconlast on /sys */
890 if (issys)
891 setrestoreconlast = false;
892
893 /* Ignore restoreconlast on in-memory filesystems */
894 if (setrestoreconlast && statfs(pathname, &sfsb) == 0) {
895 if (sfsb.f_type == RAMFS_MAGIC || sfsb.f_type == TMPFS_MAGIC)
896 setrestoreconlast = false;
897 }
898
899 if (setrestoreconlast) {
900 size = getxattr(pathname, RESTORECON_LAST, xattr_value,
901 fc_digest_len);
902
903 if (!flags.ignore_digest && (size_t)size == fc_digest_len &&
904 memcmp(fc_digest, xattr_value, fc_digest_len)
905 == 0) {
906 selinux_log(SELINUX_INFO,
907 "Skipping restorecon as matching digest on: %s\n",
908 pathname);
909 error = 0;
910 goto cleanup;
911 }
912 }
913
914 if (flags.set_xdev)
915 fts_flags = FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV;
916 else
917 fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
918
919 fts = fts_open(paths, fts_flags, NULL);
920 if (!fts)
921 goto fts_err;
922
923 ftsent = fts_read(fts);
924 if (!ftsent)
925 goto fts_err;
926
927 /*
928 * Keep the inode of the first device. This is because the FTS_XDEV
929 * flag tells fts not to descend into directories with different
930 * device numbers, but fts will still give back the actual directory.
931 * By saving the device number of the directory that was passed to
932 * selinux_restorecon() and then skipping all actions on any
933 * directories with a different device number when the FTS_XDEV flag
934 * is set (from http://marc.info/?l=selinux&m=124688830500777&w=2).
935 */
936 dev_num = ftsent->fts_statp->st_dev;
937
938 error = 0;
939 do {
940 /* If the FTS_XDEV flag is set and the device is different */
941 if (flags.set_xdev && ftsent->fts_statp->st_dev != dev_num)
942 continue;
943
944 switch (ftsent->fts_info) {
945 case FTS_DC:
946 selinux_log(SELINUX_ERROR,
947 "Directory cycle on %s.\n",
948 ftsent->fts_path);
949 errno = ELOOP;
950 error = -1;
951 goto out;
952 case FTS_DP:
953 continue;
954 case FTS_DNR:
955 selinux_log(SELINUX_ERROR,
956 "Could not read %s: %s.\n",
957 ftsent->fts_path,
958 strerror(ftsent->fts_errno));
959 fts_set(fts, ftsent, FTS_SKIP);
960 continue;
961 case FTS_NS:
962 selinux_log(SELINUX_ERROR,
963 "Could not stat %s: %s.\n",
964 ftsent->fts_path,
965 strerror(ftsent->fts_errno));
966 fts_set(fts, ftsent, FTS_SKIP);
967 continue;
968 case FTS_ERR:
969 selinux_log(SELINUX_ERROR,
970 "Error on %s: %s.\n",
971 ftsent->fts_path,
972 strerror(ftsent->fts_errno));
973 fts_set(fts, ftsent, FTS_SKIP);
974 continue;
975 case FTS_D:
976 if (issys && !selabel_partial_match(fc_sehandle,
977 ftsent->fts_path)) {
978 fts_set(fts, ftsent, FTS_SKIP);
979 continue;
980 }
981
982 if (check_excluded(ftsent->fts_path)) {
983 fts_set(fts, ftsent, FTS_SKIP);
984 continue;
985 }
986 /* fall through */
987 default:
988 error |= restorecon_sb(ftsent->fts_path,
989 ftsent->fts_statp, &flags);
990 if (flags.warnonnomatch)
991 flags.warnonnomatch = false;
992 if (error && flags.abort_on_error)
993 goto out;
994 break;
995 }
996 } while ((ftsent = fts_read(fts)) != NULL);
997
998 /* Labeling successful. Mark the top level directory as completed. */
999 if (setrestoreconlast && !flags.nochange && !error && fc_digest) {
1000 error = setxattr(pathname, RESTORECON_LAST, fc_digest,
1001 fc_digest_len, 0);
1002 if (!error && flags.verbose)
1003 selinux_log(SELINUX_INFO,
1004 "Updated digest for: %s\n", pathname);
1005 }
1006
1007 out:
1008 if (flags.progress && flags.mass_relabel)
1009 fprintf(stdout, "\r%s 100.0%%\n", pathname);
1010
1011 sverrno = errno;
1012 (void) fts_close(fts);
1013 errno = sverrno;
1014 cleanup:
1015 if (flags.add_assoc) {
1016 if (flags.verbose)
1017 filespec_eval();
1018 filespec_destroy();
1019 }
1020 free(pathdnamer);
1021 free(pathname);
1022 free(xattr_value);
1023 return error;
1024
1025 oom:
1026 sverrno = errno;
1027 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
1028 errno = sverrno;
1029 error = -1;
1030 goto cleanup;
1031
1032 realpatherr:
1033 sverrno = errno;
1034 selinux_log(SELINUX_ERROR,
1035 "SELinux: Could not get canonical path for %s restorecon: %s.\n",
1036 pathname_orig, strerror(errno));
1037 errno = sverrno;
1038 error = -1;
1039 goto cleanup;
1040
1041 fts_err:
1042 selinux_log(SELINUX_ERROR,
1043 "fts error while labeling %s: %s\n",
1044 paths[0], strerror(errno));
1045 error = -1;
1046 goto cleanup;
1047 }
1048
1049 /* selinux_restorecon_set_sehandle(3) is called to set the global fc handle */
selinux_restorecon_set_sehandle(struct selabel_handle * hndl)1050 void selinux_restorecon_set_sehandle(struct selabel_handle *hndl)
1051 {
1052 char **specfiles;
1053 size_t num_specfiles;
1054
1055 fc_sehandle = (struct selabel_handle *) hndl;
1056
1057 /*
1058 * Read digest if requested in selabel_open(3) and set global params.
1059 */
1060 if (selabel_digest(fc_sehandle, &fc_digest, &fc_digest_len,
1061 &specfiles, &num_specfiles) < 0) {
1062 fc_digest = NULL;
1063 fc_digest_len = 0;
1064 }
1065 }
1066
1067 /*
1068 * selinux_restorecon_default_handle(3) is called to set the global restorecon
1069 * handle by a process if the default params are required.
1070 */
selinux_restorecon_default_handle(void)1071 struct selabel_handle *selinux_restorecon_default_handle(void)
1072 {
1073 struct selabel_handle *sehandle;
1074
1075 struct selinux_opt fc_opts[] = {
1076 { SELABEL_OPT_DIGEST, (char *)1 }
1077 };
1078
1079 sehandle = selabel_open(SELABEL_CTX_FILE, fc_opts, 1);
1080
1081 if (!sehandle) {
1082 selinux_log(SELINUX_ERROR,
1083 "Error obtaining file context handle: %s\n",
1084 strerror(errno));
1085 return NULL;
1086 }
1087
1088 return sehandle;
1089 }
1090
1091 /*
1092 * selinux_restorecon_set_exclude_list(3) is called to add additional entries
1093 * to be excluded from labeling checks.
1094 */
selinux_restorecon_set_exclude_list(const char ** exclude_list)1095 void selinux_restorecon_set_exclude_list(const char **exclude_list)
1096 {
1097 int i;
1098 struct stat sb;
1099
1100 for (i = 0; exclude_list[i]; i++) {
1101 if (lstat(exclude_list[i], &sb) < 0 && errno != EACCES) {
1102 selinux_log(SELINUX_ERROR,
1103 "lstat error on exclude path \"%s\", %s - ignoring.\n",
1104 exclude_list[i], strerror(errno));
1105 break;
1106 }
1107 if (add_exclude(exclude_list[i], CALLER_EXCLUDED) &&
1108 errno == ENOMEM)
1109 assert(0);
1110 }
1111 }
1112
1113 /* selinux_restorecon_set_alt_rootpath(3) sets an alternate rootpath. */
selinux_restorecon_set_alt_rootpath(const char * alt_rootpath)1114 int selinux_restorecon_set_alt_rootpath(const char *alt_rootpath)
1115 {
1116 int len;
1117
1118 /* This should be NULL on first use */
1119 if (rootpath)
1120 free(rootpath);
1121
1122 rootpath = strdup(alt_rootpath);
1123 if (!rootpath) {
1124 selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
1125 return -1;
1126 }
1127
1128 /* trim trailing /, if present */
1129 len = strlen(rootpath);
1130 while (len && (rootpath[len - 1] == '/'))
1131 rootpath[--len] = '\0';
1132 rootpathlen = len;
1133
1134 return 0;
1135 }
1136
1137 /* selinux_restorecon_xattr(3) - Find RESTORECON_LAST entries. */
selinux_restorecon_xattr(const char * pathname,unsigned int xattr_flags,struct dir_xattr *** xattr_list)1138 int selinux_restorecon_xattr(const char *pathname, unsigned int xattr_flags,
1139 struct dir_xattr ***xattr_list)
1140 {
1141 bool recurse = (xattr_flags &
1142 SELINUX_RESTORECON_XATTR_RECURSE) ? true : false;
1143 bool delete_nonmatch = (xattr_flags &
1144 SELINUX_RESTORECON_XATTR_DELETE_NONMATCH_DIGESTS) ? true : false;
1145 bool delete_all = (xattr_flags &
1146 SELINUX_RESTORECON_XATTR_DELETE_ALL_DIGESTS) ? true : false;
1147 ignore_mounts = (xattr_flags &
1148 SELINUX_RESTORECON_XATTR_IGNORE_MOUNTS) ? true : false;
1149
1150 int rc, fts_flags;
1151 struct stat sb;
1152 struct statfs sfsb;
1153 struct dir_xattr *current, *next;
1154 FTS *fts;
1155 FTSENT *ftsent;
1156 char *paths[2] = { NULL, NULL };
1157
1158 __selinux_once(fc_once, restorecon_init);
1159
1160 if (!fc_sehandle || !fc_digest_len)
1161 return -1;
1162
1163 if (lstat(pathname, &sb) < 0) {
1164 if (errno == ENOENT)
1165 return 0;
1166
1167 selinux_log(SELINUX_ERROR,
1168 "lstat(%s) failed: %s\n",
1169 pathname, strerror(errno));
1170 return -1;
1171 }
1172
1173 if (!recurse) {
1174 if (statfs(pathname, &sfsb) == 0) {
1175 if (sfsb.f_type == RAMFS_MAGIC ||
1176 sfsb.f_type == TMPFS_MAGIC)
1177 return 0;
1178 }
1179
1180 if (check_excluded(pathname))
1181 return 0;
1182
1183 rc = add_xattr_entry(pathname, delete_nonmatch, delete_all);
1184
1185 if (!rc && dir_xattr_list)
1186 *xattr_list = &dir_xattr_list;
1187 else if (rc == -1)
1188 return rc;
1189
1190 return 0;
1191 }
1192
1193 paths[0] = (char *)pathname;
1194 fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
1195
1196 fts = fts_open(paths, fts_flags, NULL);
1197 if (!fts) {
1198 selinux_log(SELINUX_ERROR,
1199 "fts error on %s: %s\n",
1200 paths[0], strerror(errno));
1201 return -1;
1202 }
1203
1204 while ((ftsent = fts_read(fts)) != NULL) {
1205 switch (ftsent->fts_info) {
1206 case FTS_DP:
1207 continue;
1208 case FTS_D:
1209 if (statfs(ftsent->fts_path, &sfsb) == 0) {
1210 if (sfsb.f_type == RAMFS_MAGIC ||
1211 sfsb.f_type == TMPFS_MAGIC)
1212 continue;
1213 }
1214 if (check_excluded(ftsent->fts_path)) {
1215 fts_set(fts, ftsent, FTS_SKIP);
1216 continue;
1217 }
1218
1219 rc = add_xattr_entry(ftsent->fts_path,
1220 delete_nonmatch, delete_all);
1221 if (rc == 1)
1222 continue;
1223 else if (rc == -1)
1224 goto cleanup;
1225 break;
1226 default:
1227 break;
1228 }
1229 }
1230
1231 if (dir_xattr_list)
1232 *xattr_list = &dir_xattr_list;
1233
1234 (void) fts_close(fts);
1235 return 0;
1236
1237 cleanup:
1238 rc = errno;
1239 (void) fts_close(fts);
1240 errno = rc;
1241
1242 if (dir_xattr_list) {
1243 /* Free any used memory */
1244 current = dir_xattr_list;
1245 while (current) {
1246 next = current->next;
1247 free(current->directory);
1248 free(current->digest);
1249 free(current);
1250 current = next;
1251 }
1252 }
1253 return -1;
1254 }
1255