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