/* * The majority of this code is from Android's * external/libselinux/src/android.c and upstream * selinux/policycoreutils/setfiles/restore.c * * See selinux_restorecon(3) for details. */ #include <unistd.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <ctype.h> #include <errno.h> #include <fcntl.h> #include <fts.h> #include <inttypes.h> #include <limits.h> #include <stdint.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/xattr.h> #include <sys/vfs.h> #include <sys/statvfs.h> #include <sys/utsname.h> #include <linux/magic.h> #include <libgen.h> #include <syslog.h> #include <assert.h> #include <selinux/selinux.h> #include <selinux/context.h> #include <selinux/label.h> #include <selinux/restorecon.h> #include "callbacks.h" #include "selinux_internal.h" #define RESTORECON_LAST "security.restorecon_last" #define SYS_PATH "/sys" #define SYS_PREFIX SYS_PATH "/" #define STAR_COUNT 1024 static struct selabel_handle *fc_sehandle = NULL; static unsigned char *fc_digest = NULL; static size_t fc_digest_len = 0; static char *rootpath = NULL; static int rootpathlen; /* Information on excluded fs and directories. */ struct edir { char *directory; size_t size; /* True if excluded by selinux_restorecon_set_exclude_list(3). */ bool caller_excluded; }; #define CALLER_EXCLUDED true static bool ignore_mounts; static int exclude_non_seclabel_mounts(void); static int exclude_count = 0; static struct edir *exclude_lst = NULL; static uint64_t fc_count = 0; /* Number of files processed so far */ static uint64_t efile_count; /* Estimated total number of files */ /* Store information on directories with xattr's. */ struct dir_xattr *dir_xattr_list; static struct dir_xattr *dir_xattr_last; /* restorecon_flags for passing to restorecon_sb() */ struct rest_flags { bool nochange; bool verbose; bool progress; bool mass_relabel; bool set_specctx; bool add_assoc; bool ignore_digest; bool recurse; bool userealpath; bool set_xdev; bool abort_on_error; bool syslog_changes; bool log_matches; bool ignore_noent; bool warnonnomatch; }; static void restorecon_init(void) { struct selabel_handle *sehandle = NULL; if (!fc_sehandle) { sehandle = selinux_restorecon_default_handle(); selinux_restorecon_set_sehandle(sehandle); } efile_count = 0; if (!ignore_mounts) efile_count = exclude_non_seclabel_mounts(); } static pthread_once_t fc_once = PTHREAD_ONCE_INIT; /* * Manage excluded directories: * remove_exclude() - This removes any conflicting entries as there could be * a case where a non-seclabel fs is mounted on /foo and * then a seclabel fs is mounted on top of it. * However if an entry has been added via * selinux_restorecon_set_exclude_list(3) do not remove. * * add_exclude() - Add a directory/fs to be excluded from labeling. If it * has already been added, then ignore. * * check_excluded() - Check if directory/fs is to be excluded when relabeling. * * file_system_count() - Calculates the number of files to be processed. * The count is only used if SELINUX_RESTORECON_PROGRESS * is set and a mass relabel is requested. * * exclude_non_seclabel_mounts() - Reads /proc/mounts to determine what * non-seclabel mounts to exclude from * relabeling. restorecon_init() will not * call this function if the * SELINUX_RESTORECON_IGNORE_MOUNTS * flag is set. * Setting SELINUX_RESTORECON_IGNORE_MOUNTS * is useful where there is a non-seclabel fs * mounted on /foo and then a seclabel fs is * mounted on a directory below this. */ static void remove_exclude(const char *directory) { int i; for (i = 0; i < exclude_count; i++) { if (strcmp(directory, exclude_lst[i].directory) == 0 && !exclude_lst[i].caller_excluded) { free(exclude_lst[i].directory); if (i != exclude_count - 1) exclude_lst[i] = exclude_lst[exclude_count - 1]; exclude_count--; return; } } } static int add_exclude(const char *directory, bool who) { struct edir *tmp_list, *current; size_t len = 0; int i; /* Check if already present. */ for (i = 0; i < exclude_count; i++) { if (strcmp(directory, exclude_lst[i].directory) == 0) return 0; } if (directory == NULL || directory[0] != '/') { selinux_log(SELINUX_ERROR, "Full path required for exclude: %s.\n", directory); errno = EINVAL; return -1; } tmp_list = realloc(exclude_lst, sizeof(struct edir) * (exclude_count + 1)); if (!tmp_list) goto oom; exclude_lst = tmp_list; len = strlen(directory); while (len > 1 && directory[len - 1] == '/') len--; current = (exclude_lst + exclude_count); current->directory = strndup(directory, len); if (!current->directory) goto oom; current->size = len; current->caller_excluded = who; exclude_count++; return 0; oom: selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__); return -1; } static int check_excluded(const char *file) { int i; for (i = 0; i < exclude_count; i++) { if (strncmp(file, exclude_lst[i].directory, exclude_lst[i].size) == 0) { if (file[exclude_lst[i].size] == 0 || file[exclude_lst[i].size] == '/') return 1; } } return 0; } static int file_system_count(char *name) { struct statvfs statvfs_buf; int nfile = 0; memset(&statvfs_buf, 0, sizeof(statvfs_buf)); if (!statvfs(name, &statvfs_buf)) nfile = statvfs_buf.f_files - statvfs_buf.f_ffree; return nfile; } /* * This is called once when selinux_restorecon() is first called. * Searches /proc/mounts for all file systems that do not support extended * attributes and adds them to the exclude directory table. File systems * that support security labels have the seclabel option, return * approximate total file count. */ static int exclude_non_seclabel_mounts(void) { struct utsname uts; FILE *fp; size_t len; ssize_t num; int index = 0, found = 0, nfile = 0; char *mount_info[4]; char *buf = NULL, *item; /* Check to see if the kernel supports seclabel */ if (uname(&uts) == 0 && strverscmp(uts.release, "2.6.30") < 0) return 0; if (is_selinux_enabled() <= 0) return 0; fp = fopen("/proc/mounts", "re"); if (!fp) return 0; while ((num = getline(&buf, &len, fp)) != -1) { found = 0; index = 0; item = strtok(buf, " "); while (item != NULL) { mount_info[index] = item; index++; if (index == 4) break; item = strtok(NULL, " "); } if (index < 4) { selinux_log(SELINUX_ERROR, "/proc/mounts record \"%s\" has incorrect format.\n", buf); continue; } /* Remove pre-existing entry */ remove_exclude(mount_info[1]); item = strtok(mount_info[3], ","); while (item != NULL) { if (strcmp(item, "seclabel") == 0) { found = 1; nfile += file_system_count(mount_info[1]); break; } item = strtok(NULL, ","); } /* Exclude mount points without the seclabel option */ if (!found) { if (add_exclude(mount_info[1], !CALLER_EXCLUDED) && errno == ENOMEM) assert(0); } } free(buf); fclose(fp); /* return estimated #Files + 5% for directories and hard links */ return nfile * 1.05; } /* Called by selinux_restorecon_xattr(3) to build a linked list of entries. */ static int add_xattr_entry(const char *directory, bool delete_nonmatch, bool delete_all) { char *sha1_buf = NULL; unsigned char *xattr_value = NULL; ssize_t xattr_size; size_t i; int rc, digest_result; struct dir_xattr *new_entry; if (!directory) { errno = EINVAL; return -1; } xattr_value = malloc(fc_digest_len); if (!xattr_value) goto oom; xattr_size = getxattr(directory, RESTORECON_LAST, xattr_value, fc_digest_len); if (xattr_size < 0) { free(xattr_value); return 1; } /* Convert entry to a hex encoded string. */ sha1_buf = malloc(xattr_size * 2 + 1); if (!sha1_buf) { free(xattr_value); goto oom; } for (i = 0; i < (size_t)xattr_size; i++) sprintf((&sha1_buf[i * 2]), "%02x", xattr_value[i]); rc = memcmp(fc_digest, xattr_value, fc_digest_len); digest_result = rc ? NOMATCH : MATCH; if ((delete_nonmatch && rc != 0) || delete_all) { digest_result = rc ? DELETED_NOMATCH : DELETED_MATCH; rc = removexattr(directory, RESTORECON_LAST); if (rc) { selinux_log(SELINUX_ERROR, "Error: %s removing xattr \"%s\" from: %s\n", strerror(errno), RESTORECON_LAST, directory); digest_result = ERROR; } } free(xattr_value); /* Now add entries to link list. */ new_entry = malloc(sizeof(struct dir_xattr)); if (!new_entry) goto oom; new_entry->next = NULL; new_entry->directory = strdup(directory); if (!new_entry->directory) { free(new_entry); free(sha1_buf); goto oom; } new_entry->digest = strdup(sha1_buf); if (!new_entry->digest) { free(new_entry->directory); free(new_entry); free(sha1_buf); goto oom; } new_entry->result = digest_result; if (!dir_xattr_list) { dir_xattr_list = new_entry; dir_xattr_last = new_entry; } else { dir_xattr_last->next = new_entry; dir_xattr_last = new_entry; } free(sha1_buf); return 0; oom: selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__); return -1; } /* * Support filespec services filespec_add(), filespec_eval() and * filespec_destroy(). * * selinux_restorecon(3) uses filespec services when the * SELINUX_RESTORECON_ADD_ASSOC flag is set for adding associations between * an inode and a specification. */ /* * The hash table of associations, hashed by inode number. Chaining is used * for collisions, with elements ordered by inode number in each bucket. * Each hash bucket has a dummy header. */ #define HASH_BITS 16 #define HASH_BUCKETS (1 << HASH_BITS) #define HASH_MASK (HASH_BUCKETS-1) /* * An association between an inode and a context. */ typedef struct file_spec { ino_t ino; /* inode number */ char *con; /* matched context */ char *file; /* full pathname */ struct file_spec *next; /* next association in hash bucket chain */ } file_spec_t; static file_spec_t *fl_head; /* * Try to add an association between an inode and a context. If there is a * different context that matched the inode, then use the first context * that matched. */ static int filespec_add(ino_t ino, const char *con, const char *file) { file_spec_t *prevfl, *fl; int h, ret; struct stat64 sb; if (!fl_head) { fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS); if (!fl_head) goto oom; memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS); } h = (ino + (ino >> HASH_BITS)) & HASH_MASK; for (prevfl = &fl_head[h], fl = fl_head[h].next; fl; prevfl = fl, fl = fl->next) { if (ino == fl->ino) { ret = lstat64(fl->file, &sb); if (ret < 0 || sb.st_ino != ino) { freecon(fl->con); free(fl->file); fl->file = strdup(file); if (!fl->file) goto oom; fl->con = strdup(con); if (!fl->con) goto oom; return 1; } if (strcmp(fl->con, con) == 0) return 1; selinux_log(SELINUX_ERROR, "conflicting specifications for %s and %s, using %s.\n", file, fl->file, fl->con); free(fl->file); fl->file = strdup(file); if (!fl->file) goto oom; return 1; } if (ino > fl->ino) break; } fl = malloc(sizeof(file_spec_t)); if (!fl) goto oom; fl->ino = ino; fl->con = strdup(con); if (!fl->con) goto oom_freefl; fl->file = strdup(file); if (!fl->file) goto oom_freefl; fl->next = prevfl->next; prevfl->next = fl; return 0; oom_freefl: free(fl); oom: selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__); return -1; } /* * Evaluate the association hash table distribution. */ #ifdef DEBUG static void filespec_eval(void) { file_spec_t *fl; int h, used, nel, len, longest; if (!fl_head) return; used = 0; longest = 0; nel = 0; for (h = 0; h < HASH_BUCKETS; h++) { len = 0; for (fl = fl_head[h].next; fl; fl = fl->next) len++; if (len) used++; if (len > longest) longest = len; nel += len; } selinux_log(SELINUX_INFO, "filespec hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n", nel, used, HASH_BUCKETS, longest); } #else static void filespec_eval(void) { } #endif /* * Destroy the association hash table. */ static void filespec_destroy(void) { file_spec_t *fl, *tmp; int h; if (!fl_head) return; for (h = 0; h < HASH_BUCKETS; h++) { fl = fl_head[h].next; while (fl) { tmp = fl; fl = fl->next; freecon(tmp->con); free(tmp->file); free(tmp); } fl_head[h].next = NULL; } free(fl_head); fl_head = NULL; } /* * Called if SELINUX_RESTORECON_SET_SPECFILE_CTX is not set to check if * the type components differ, updating newtypecon if so. */ static int compare_types(char *curcon, char *newcon, char **newtypecon) { int types_differ = 0; context_t cona; context_t conb; int rc = 0; cona = context_new(curcon); if (!cona) { rc = -1; goto out; } conb = context_new(newcon); if (!conb) { context_free(cona); rc = -1; goto out; } types_differ = strcmp(context_type_get(cona), context_type_get(conb)); if (types_differ) { rc |= context_user_set(conb, context_user_get(cona)); rc |= context_role_set(conb, context_role_get(cona)); rc |= context_range_set(conb, context_range_get(cona)); if (!rc) { *newtypecon = strdup(context_str(conb)); if (!*newtypecon) { rc = -1; goto err; } } } err: context_free(cona); context_free(conb); out: return rc; } static int restorecon_sb(const char *pathname, const struct stat *sb, struct rest_flags *flags) { char *newcon = NULL; char *curcon = NULL; char *newtypecon = NULL; int rc; bool updated = false; const char *lookup_path = pathname; float pc; if (rootpath) { if (strncmp(rootpath, lookup_path, rootpathlen) != 0) { selinux_log(SELINUX_ERROR, "%s is not located in alt_rootpath %s\n", lookup_path, rootpath); return -1; } lookup_path += rootpathlen; } if (rootpath != NULL && lookup_path[0] == '\0') /* this is actually the root dir of the alt root. */ rc = selabel_lookup_raw(fc_sehandle, &newcon, "/", sb->st_mode); else rc = selabel_lookup_raw(fc_sehandle, &newcon, lookup_path, sb->st_mode); if (rc < 0) { if (errno == ENOENT && flags->warnonnomatch) selinux_log(SELINUX_INFO, "Warning no default label for %s\n", lookup_path); return 0; /* no match, but not an error */ } if (flags->progress) { fc_count++; if (fc_count % STAR_COUNT == 0) { if (flags->mass_relabel && efile_count > 0) { pc = (fc_count < efile_count) ? (100.0 * fc_count / efile_count) : 100; fprintf(stdout, "\r%-.1f%%", (double)pc); } else { fprintf(stdout, "\r%" PRIu64 "k", fc_count / STAR_COUNT); } fflush(stdout); } } if (flags->add_assoc) { rc = filespec_add(sb->st_ino, newcon, pathname); if (rc < 0) { selinux_log(SELINUX_ERROR, "filespec_add error: %s\n", pathname); freecon(newcon); return -1; } if (rc > 0) { /* Already an association and it took precedence. */ freecon(newcon); return 0; } } if (flags->log_matches) selinux_log(SELINUX_INFO, "%s matched by %s\n", pathname, newcon); if (lgetfilecon_raw(pathname, &curcon) < 0) { if (errno != ENODATA) goto err; curcon = NULL; } if (curcon == NULL || strcmp(curcon, newcon) != 0) { if (!flags->set_specctx && curcon && (is_context_customizable(curcon) > 0)) { if (flags->verbose) { selinux_log(SELINUX_INFO, "%s not reset as customized by admin to %s\n", pathname, curcon); } goto out; } if (!flags->set_specctx && curcon) { /* If types different then update newcon. */ rc = compare_types(curcon, newcon, &newtypecon); if (rc) goto err; if (newtypecon) { freecon(newcon); newcon = newtypecon; } else { goto out; } } if (!flags->nochange) { if (lsetfilecon(pathname, newcon) < 0) goto err; updated = true; } if (flags->verbose) selinux_log(SELINUX_INFO, "%s %s from %s to %s\n", updated ? "Relabeled" : "Would relabel", pathname, curcon, newcon); if (flags->syslog_changes && !flags->nochange) { if (curcon) syslog(LOG_INFO, "relabeling %s from %s to %s\n", pathname, curcon, newcon); else syslog(LOG_INFO, "labeling %s to %s\n", pathname, newcon); } } out: rc = 0; out1: freecon(curcon); freecon(newcon); return rc; err: selinux_log(SELINUX_ERROR, "Could not set context for %s: %s\n", pathname, strerror(errno)); rc = -1; goto out1; } /* * Public API */ /* selinux_restorecon(3) - Main function that is responsible for labeling */ int selinux_restorecon(const char *pathname_orig, unsigned int restorecon_flags) { struct rest_flags flags; flags.ignore_digest = (restorecon_flags & SELINUX_RESTORECON_IGNORE_DIGEST) ? true : false; flags.nochange = (restorecon_flags & SELINUX_RESTORECON_NOCHANGE) ? true : false; flags.verbose = (restorecon_flags & SELINUX_RESTORECON_VERBOSE) ? true : false; flags.progress = (restorecon_flags & SELINUX_RESTORECON_PROGRESS) ? true : false; flags.mass_relabel = (restorecon_flags & SELINUX_RESTORECON_MASS_RELABEL) ? true : false; flags.recurse = (restorecon_flags & SELINUX_RESTORECON_RECURSE) ? true : false; flags.set_specctx = (restorecon_flags & SELINUX_RESTORECON_SET_SPECFILE_CTX) ? true : false; flags.userealpath = (restorecon_flags & SELINUX_RESTORECON_REALPATH) ? true : false; flags.set_xdev = (restorecon_flags & SELINUX_RESTORECON_XDEV) ? true : false; flags.add_assoc = (restorecon_flags & SELINUX_RESTORECON_ADD_ASSOC) ? true : false; flags.abort_on_error = (restorecon_flags & SELINUX_RESTORECON_ABORT_ON_ERROR) ? true : false; flags.syslog_changes = (restorecon_flags & SELINUX_RESTORECON_SYSLOG_CHANGES) ? true : false; flags.log_matches = (restorecon_flags & SELINUX_RESTORECON_LOG_MATCHES) ? true : false; flags.ignore_noent = (restorecon_flags & SELINUX_RESTORECON_IGNORE_NOENTRY) ? true : false; flags.warnonnomatch = true; ignore_mounts = (restorecon_flags & SELINUX_RESTORECON_IGNORE_MOUNTS) ? true : false; bool issys; bool setrestoreconlast = true; /* TRUE = set xattr RESTORECON_LAST * FALSE = don't use xattr */ struct stat sb; struct statfs sfsb; FTS *fts; FTSENT *ftsent; char *pathname = NULL, *pathdnamer = NULL, *pathdname, *pathbname; char *paths[2] = { NULL, NULL }; int fts_flags, error, sverrno; char *xattr_value = NULL; ssize_t size; dev_t dev_num = 0; if (flags.verbose && flags.progress) flags.verbose = false; __selinux_once(fc_once, restorecon_init); if (!fc_sehandle) return -1; if (fc_digest_len) { xattr_value = malloc(fc_digest_len); if (!xattr_value) return -1; } /* * Convert passed-in pathname to canonical pathname by resolving * realpath of containing dir, then appending last component name. */ if (flags.userealpath) { char *basename_cpy = strdup(pathname_orig); if (!basename_cpy) goto realpatherr; pathbname = basename(basename_cpy); if (!strcmp(pathbname, "/") || !strcmp(pathbname, ".") || !strcmp(pathbname, "..")) { pathname = realpath(pathname_orig, NULL); if (!pathname) { free(basename_cpy); goto realpatherr; } } else { char *dirname_cpy = strdup(pathname_orig); if (!dirname_cpy) { free(basename_cpy); goto realpatherr; } pathdname = dirname(dirname_cpy); pathdnamer = realpath(pathdname, NULL); free(dirname_cpy); if (!pathdnamer) { free(basename_cpy); goto realpatherr; } if (!strcmp(pathdnamer, "/")) error = asprintf(&pathname, "/%s", pathbname); else error = asprintf(&pathname, "%s/%s", pathdnamer, pathbname); if (error < 0) { free(basename_cpy); goto oom; } } free(basename_cpy); } else { pathname = strdup(pathname_orig); if (!pathname) goto oom; } paths[0] = pathname; issys = (!strcmp(pathname, SYS_PATH) || !strncmp(pathname, SYS_PREFIX, sizeof(SYS_PREFIX) - 1)) ? true : false; if (lstat(pathname, &sb) < 0) { if (flags.ignore_noent && errno == ENOENT) { free(xattr_value); free(pathdnamer); free(pathname); return 0; } else { selinux_log(SELINUX_ERROR, "lstat(%s) failed: %s\n", pathname, strerror(errno)); error = -1; goto cleanup; } } /* Ignore restoreconlast if not a directory */ if ((sb.st_mode & S_IFDIR) != S_IFDIR) setrestoreconlast = false; if (!flags.recurse) { if (check_excluded(pathname)) { error = 0; goto cleanup; } error = restorecon_sb(pathname, &sb, &flags); goto cleanup; } /* Ignore restoreconlast on /sys */ if (issys) setrestoreconlast = false; /* Ignore restoreconlast on in-memory filesystems */ if (setrestoreconlast && statfs(pathname, &sfsb) == 0) { if (sfsb.f_type == RAMFS_MAGIC || sfsb.f_type == TMPFS_MAGIC) setrestoreconlast = false; } if (setrestoreconlast) { size = getxattr(pathname, RESTORECON_LAST, xattr_value, fc_digest_len); if (!flags.ignore_digest && (size_t)size == fc_digest_len && memcmp(fc_digest, xattr_value, fc_digest_len) == 0) { selinux_log(SELINUX_INFO, "Skipping restorecon as matching digest on: %s\n", pathname); error = 0; goto cleanup; } } if (flags.set_xdev) fts_flags = FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV; else fts_flags = FTS_PHYSICAL | FTS_NOCHDIR; fts = fts_open(paths, fts_flags, NULL); if (!fts) goto fts_err; ftsent = fts_read(fts); if (!ftsent) goto fts_err; /* * Keep the inode of the first device. This is because the FTS_XDEV * flag tells fts not to descend into directories with different * device numbers, but fts will still give back the actual directory. * By saving the device number of the directory that was passed to * selinux_restorecon() and then skipping all actions on any * directories with a different device number when the FTS_XDEV flag * is set (from http://marc.info/?l=selinux&m=124688830500777&w=2). */ dev_num = ftsent->fts_statp->st_dev; error = 0; do { /* If the FTS_XDEV flag is set and the device is different */ if (flags.set_xdev && ftsent->fts_statp->st_dev != dev_num) continue; switch (ftsent->fts_info) { case FTS_DC: selinux_log(SELINUX_ERROR, "Directory cycle on %s.\n", ftsent->fts_path); errno = ELOOP; error = -1; goto out; case FTS_DP: continue; case FTS_DNR: selinux_log(SELINUX_ERROR, "Could not read %s: %s.\n", ftsent->fts_path, strerror(ftsent->fts_errno)); fts_set(fts, ftsent, FTS_SKIP); continue; case FTS_NS: selinux_log(SELINUX_ERROR, "Could not stat %s: %s.\n", ftsent->fts_path, strerror(ftsent->fts_errno)); fts_set(fts, ftsent, FTS_SKIP); continue; case FTS_ERR: selinux_log(SELINUX_ERROR, "Error on %s: %s.\n", ftsent->fts_path, strerror(ftsent->fts_errno)); fts_set(fts, ftsent, FTS_SKIP); continue; case FTS_D: if (issys && !selabel_partial_match(fc_sehandle, ftsent->fts_path)) { fts_set(fts, ftsent, FTS_SKIP); continue; } if (check_excluded(ftsent->fts_path)) { fts_set(fts, ftsent, FTS_SKIP); continue; } /* fall through */ default: error |= restorecon_sb(ftsent->fts_path, ftsent->fts_statp, &flags); if (flags.warnonnomatch) flags.warnonnomatch = false; if (error && flags.abort_on_error) goto out; break; } } while ((ftsent = fts_read(fts)) != NULL); /* Labeling successful. Mark the top level directory as completed. */ if (setrestoreconlast && !flags.nochange && !error && fc_digest) { error = setxattr(pathname, RESTORECON_LAST, fc_digest, fc_digest_len, 0); if (!error && flags.verbose) selinux_log(SELINUX_INFO, "Updated digest for: %s\n", pathname); } out: if (flags.progress && flags.mass_relabel) fprintf(stdout, "\r%s 100.0%%\n", pathname); sverrno = errno; (void) fts_close(fts); errno = sverrno; cleanup: if (flags.add_assoc) { if (flags.verbose) filespec_eval(); filespec_destroy(); } free(pathdnamer); free(pathname); free(xattr_value); return error; oom: sverrno = errno; selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__); errno = sverrno; error = -1; goto cleanup; realpatherr: sverrno = errno; selinux_log(SELINUX_ERROR, "SELinux: Could not get canonical path for %s restorecon: %s.\n", pathname_orig, strerror(errno)); errno = sverrno; error = -1; goto cleanup; fts_err: selinux_log(SELINUX_ERROR, "fts error while labeling %s: %s\n", paths[0], strerror(errno)); error = -1; goto cleanup; } /* selinux_restorecon_set_sehandle(3) is called to set the global fc handle */ void selinux_restorecon_set_sehandle(struct selabel_handle *hndl) { char **specfiles; size_t num_specfiles; fc_sehandle = (struct selabel_handle *) hndl; /* * Read digest if requested in selabel_open(3) and set global params. */ if (selabel_digest(fc_sehandle, &fc_digest, &fc_digest_len, &specfiles, &num_specfiles) < 0) { fc_digest = NULL; fc_digest_len = 0; } } /* * selinux_restorecon_default_handle(3) is called to set the global restorecon * handle by a process if the default params are required. */ struct selabel_handle *selinux_restorecon_default_handle(void) { struct selabel_handle *sehandle; struct selinux_opt fc_opts[] = { { SELABEL_OPT_DIGEST, (char *)1 } }; sehandle = selabel_open(SELABEL_CTX_FILE, fc_opts, 1); if (!sehandle) { selinux_log(SELINUX_ERROR, "Error obtaining file context handle: %s\n", strerror(errno)); return NULL; } return sehandle; } /* * selinux_restorecon_set_exclude_list(3) is called to add additional entries * to be excluded from labeling checks. */ void selinux_restorecon_set_exclude_list(const char **exclude_list) { int i; struct stat sb; for (i = 0; exclude_list[i]; i++) { if (lstat(exclude_list[i], &sb) < 0 && errno != EACCES) { selinux_log(SELINUX_ERROR, "lstat error on exclude path \"%s\", %s - ignoring.\n", exclude_list[i], strerror(errno)); break; } if (add_exclude(exclude_list[i], CALLER_EXCLUDED) && errno == ENOMEM) assert(0); } } /* selinux_restorecon_set_alt_rootpath(3) sets an alternate rootpath. */ int selinux_restorecon_set_alt_rootpath(const char *alt_rootpath) { int len; /* This should be NULL on first use */ if (rootpath) free(rootpath); rootpath = strdup(alt_rootpath); if (!rootpath) { selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__); return -1; } /* trim trailing /, if present */ len = strlen(rootpath); while (len && (rootpath[len - 1] == '/')) rootpath[--len] = '\0'; rootpathlen = len; return 0; } /* selinux_restorecon_xattr(3) - Find RESTORECON_LAST entries. */ int selinux_restorecon_xattr(const char *pathname, unsigned int xattr_flags, struct dir_xattr ***xattr_list) { bool recurse = (xattr_flags & SELINUX_RESTORECON_XATTR_RECURSE) ? true : false; bool delete_nonmatch = (xattr_flags & SELINUX_RESTORECON_XATTR_DELETE_NONMATCH_DIGESTS) ? true : false; bool delete_all = (xattr_flags & SELINUX_RESTORECON_XATTR_DELETE_ALL_DIGESTS) ? true : false; ignore_mounts = (xattr_flags & SELINUX_RESTORECON_XATTR_IGNORE_MOUNTS) ? true : false; int rc, fts_flags; struct stat sb; struct statfs sfsb; struct dir_xattr *current, *next; FTS *fts; FTSENT *ftsent; char *paths[2] = { NULL, NULL }; __selinux_once(fc_once, restorecon_init); if (!fc_sehandle || !fc_digest_len) return -1; if (lstat(pathname, &sb) < 0) { if (errno == ENOENT) return 0; selinux_log(SELINUX_ERROR, "lstat(%s) failed: %s\n", pathname, strerror(errno)); return -1; } if (!recurse) { if (statfs(pathname, &sfsb) == 0) { if (sfsb.f_type == RAMFS_MAGIC || sfsb.f_type == TMPFS_MAGIC) return 0; } if (check_excluded(pathname)) return 0; rc = add_xattr_entry(pathname, delete_nonmatch, delete_all); if (!rc && dir_xattr_list) *xattr_list = &dir_xattr_list; else if (rc == -1) return rc; return 0; } paths[0] = (char *)pathname; fts_flags = FTS_PHYSICAL | FTS_NOCHDIR; fts = fts_open(paths, fts_flags, NULL); if (!fts) { selinux_log(SELINUX_ERROR, "fts error on %s: %s\n", paths[0], strerror(errno)); return -1; } while ((ftsent = fts_read(fts)) != NULL) { switch (ftsent->fts_info) { case FTS_DP: continue; case FTS_D: if (statfs(ftsent->fts_path, &sfsb) == 0) { if (sfsb.f_type == RAMFS_MAGIC || sfsb.f_type == TMPFS_MAGIC) continue; } if (check_excluded(ftsent->fts_path)) { fts_set(fts, ftsent, FTS_SKIP); continue; } rc = add_xattr_entry(ftsent->fts_path, delete_nonmatch, delete_all); if (rc == 1) continue; else if (rc == -1) goto cleanup; break; default: break; } } if (dir_xattr_list) *xattr_list = &dir_xattr_list; (void) fts_close(fts); return 0; cleanup: rc = errno; (void) fts_close(fts); errno = rc; if (dir_xattr_list) { /* Free any used memory */ current = dir_xattr_list; while (current) { next = current->next; free(current->directory); free(current->digest); free(current); current = next; } } return -1; }