selinux/policycoreutils/setfiles/setfiles.c

506 lines
11 KiB
C

#include "restore.h"
#include <unistd.h>
#include <fcntl.h>
#include <stdio_ext.h>
#include <ctype.h>
#include <regex.h>
#include <sys/vfs.h>
#include <sys/utsname.h>
#define __USE_XOPEN_EXTENDED 1 /* nftw */
#include <libgen.h>
#ifdef USE_AUDIT
#include <libaudit.h>
#ifndef AUDIT_FS_RELABEL
#define AUDIT_FS_RELABEL 2309
#endif
#endif
static int mass_relabel;
static int mass_relabel_errs;
/* cmdline opts*/
static char *policyfile = NULL;
static int warn_no_match = 0;
static int null_terminated = 0;
static int errors;
static struct restore_opts r_opts;
#define STAT_BLOCK_SIZE 1
#define SETFILES "setfiles"
#define RESTORECON "restorecon"
static int iamrestorecon;
/* Behavior flags determined based on setfiles vs. restorecon */
static int ctx_validate; /* Validate contexts */
static const char *altpath; /* Alternate path to file_contexts */
void usage(const char *const name)
{
if (iamrestorecon) {
fprintf(stderr,
"usage: %s [-iFnprRv0] [-e excludedir ] [-o filename ] [-f filename | pathname... ]\n",
name);
} else {
fprintf(stderr,
"usage: %s [-dnpqvW] [-o filename] [-r alt_root_path ] spec_file pathname...\n"
"usage: %s -c policyfile spec_file\n"
"usage: %s -s [-dnpqvW] [-o filename ] spec_file\n", name, name,
name);
}
exit(1);
}
static int nerr = 0;
void inc_err()
{
nerr++;
if (nerr > 9 && !r_opts.debug) {
fprintf(stderr, "Exiting after 10 errors.\n");
exit(1);
}
}
void set_rootpath(const char *arg)
{
int len;
r_opts.rootpath = strdup(arg);
if (NULL == r_opts.rootpath) {
fprintf(stderr, "%s: insufficient memory for r_opts.rootpath\n",
r_opts.progname);
exit(1);
}
/* trim trailing /, if present */
len = strlen(r_opts.rootpath);
while (len && ('/' == r_opts.rootpath[len - 1]))
r_opts.rootpath[--len] = 0;
r_opts.rootpathlen = len;
}
int canoncon(char **contextp)
{
char *context = *contextp, *tmpcon;
int rc = 0;
if (policyfile) {
if (sepol_check_context(context) < 0) {
fprintf(stderr, "invalid context %s\n", context);
exit(1);
}
} else if (security_canonicalize_context_raw(context, &tmpcon) == 0) {
free(context);
*contextp = tmpcon;
} else if (errno != ENOENT) {
rc = -1;
inc_err();
}
return rc;
}
#ifndef USE_AUDIT
static void maybe_audit_mass_relabel(void)
{
#else
static void maybe_audit_mass_relabel(void)
{
int audit_fd = -1;
int rc = 0;
if (!mass_relabel) /* only audit a forced full relabel */
return;
audit_fd = audit_open();
if (audit_fd < 0) {
fprintf(stderr, "Error connecting to audit system.\n");
exit(-1);
}
rc = audit_log_user_message(audit_fd, AUDIT_FS_RELABEL,
"op=mass relabel", NULL, NULL, NULL, !mass_relabel_errs);
if (rc <= 0) {
fprintf(stderr, "Error sending audit message: %s.\n",
strerror(errno));
/* exit(-1); -- don't exit atm. as fix for eff_cap isn't in most kernels */
}
audit_close(audit_fd);
#endif
}
/*
Search /proc/mounts for all file systems that do not support extended
attributes and add them to the exclude directory table. File systems
that support security labels have the seclabel option.
*/
static void exclude_non_seclabel_mounts()
{
struct utsname uts;
FILE *fp;
size_t len;
ssize_t num;
int index = 0, found = 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;
if (is_selinux_enabled() <= 0)
return;
fp = fopen("/proc/mounts", "r");
if (!fp)
return;
while ((num = getline(&buf, &len, fp)) != -1) {
found = 0;
index = 0;
item = strtok(buf, " ");
while (item != NULL) {
mount_info[index] = item;
if (index == 3)
break;
index++;
item = strtok(NULL, " ");
}
if (index < 3) {
fprintf(stderr,
"/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;
break;
}
item = strtok(NULL, ",");
}
/* exclude mount points without the seclabel option */
if (!found)
add_exclude(mount_info[1]);
}
free(buf);
}
int main(int argc, char **argv)
{
struct stat sb;
int opt, i = 0;
char *input_filename = NULL;
int use_input_file = 0;
char *buf = NULL;
size_t buf_len;
int recurse; /* Recursive descent. */
char *base;
memset(&r_opts, 0, sizeof(r_opts));
/* Initialize variables */
r_opts.progress = 0;
r_opts.count = 0;
r_opts.debug = 0;
r_opts.change = 1;
r_opts.verbose = 0;
r_opts.logging = 0;
r_opts.rootpath = NULL;
r_opts.rootpathlen = 0;
r_opts.outfile = NULL;
r_opts.force = 0;
r_opts.hard_links = 1;
altpath = NULL;
r_opts.progname = strdup(argv[0]);
if (!r_opts.progname) {
fprintf(stderr, "%s: Out of memory!\n", argv[0]);
exit(1);
}
base = basename(r_opts.progname);
if (!strcmp(base, SETFILES)) {
/*
* setfiles:
* Recursive descent,
* Does not expand paths via realpath,
* Aborts on errors during the file tree walk,
* Try to track inode associations for conflict detection,
* Does not follow mounts,
* Validates all file contexts at init time.
*/
iamrestorecon = 0;
recurse = 1;
r_opts.expand_realpath = 0;
r_opts.abort_on_error = 1;
r_opts.add_assoc = 1;
r_opts.fts_flags = FTS_PHYSICAL | FTS_XDEV;
ctx_validate = 1;
} else {
/*
* restorecon:
* No recursive descent unless -r/-R,
* Expands paths via realpath,
* Do not abort on errors during the file tree walk,
* Do not try to track inode associations for conflict detection,
* Follows mounts,
* Does lazy validation of contexts upon use.
*/
if (strcmp(base, RESTORECON) && !r_opts.quiet)
printf("Executed with an unrecognized name (%s), defaulting to %s behavior.\n", base, RESTORECON);
iamrestorecon = 1;
recurse = 0;
r_opts.expand_realpath = 1;
r_opts.abort_on_error = 0;
r_opts.add_assoc = 0;
r_opts.fts_flags = FTS_PHYSICAL;
ctx_validate = 0;
/* restorecon only: silent exit if no SELinux.
Allows unconditional execution by scripts. */
if (is_selinux_enabled() <= 0)
exit(0);
}
/* This must happen before getopt. */
exclude_non_seclabel_mounts();
/* Process any options. */
while ((opt = getopt(argc, argv, "c:de:f:ilnpqrsvo:FRW0")) > 0) {
switch (opt) {
case 'c':
{
FILE *policystream;
if (iamrestorecon)
usage(argv[0]);
policyfile = optarg;
policystream = fopen(policyfile, "r");
if (!policystream) {
fprintf(stderr,
"Error opening %s: %s\n",
policyfile, strerror(errno));
exit(1);
}
__fsetlocking(policystream,
FSETLOCKING_BYCALLER);
if (sepol_set_policydb_from_file(policystream) <
0) {
fprintf(stderr,
"Error reading policy %s: %s\n",
policyfile, strerror(errno));
exit(1);
}
fclose(policystream);
ctx_validate = 1;
break;
}
case 'e':
remove_exclude(optarg);
if (lstat(optarg, &sb) < 0 && errno != EACCES) {
fprintf(stderr, "Can't stat exclude path \"%s\", %s - ignoring.\n",
optarg, strerror(errno));
break;
}
if (add_exclude(optarg))
exit(1);
break;
case 'f':
use_input_file = 1;
input_filename = optarg;
break;
case 'd':
r_opts.debug = 1;
break;
case 'i':
r_opts.ignore_enoent = 1;
break;
case 'l':
r_opts.logging = 1;
break;
case 'F':
r_opts.force = 1;
break;
case 'n':
r_opts.change = 0;
break;
case 'o':
if (strcmp(optarg, "-") == 0) {
r_opts.outfile = stdout;
break;
}
r_opts.outfile = fopen(optarg, "w");
if (!r_opts.outfile) {
fprintf(stderr, "Error opening %s: %s\n",
optarg, strerror(errno));
usage(argv[0]);
}
__fsetlocking(r_opts.outfile, FSETLOCKING_BYCALLER);
break;
case 'q':
r_opts.quiet = 1;
break;
case 'R':
case 'r':
if (iamrestorecon) {
recurse = 1;
break;
}
if (optind + 1 >= argc) {
fprintf(stderr, "usage: %s -r rootpath\n",
argv[0]);
exit(1);
}
if (NULL != r_opts.rootpath) {
fprintf(stderr,
"%s: only one -r can be specified\n",
argv[0]);
exit(1);
}
set_rootpath(argv[optind++]);
break;
case 's':
use_input_file = 1;
input_filename = "-";
r_opts.add_assoc = 0;
break;
case 'v':
if (r_opts.progress) {
fprintf(stderr,
"Progress and Verbose mutually exclusive\n");
exit(1);
}
r_opts.verbose++;
break;
case 'p':
if (r_opts.verbose) {
fprintf(stderr,
"Progress and Verbose mutually exclusive\n");
usage(argv[0]);
}
r_opts.progress = 1;
break;
case 'W':
warn_no_match = 1;
break;
case '0':
null_terminated = 1;
break;
case '?':
usage(argv[0]);
}
}
if (!iamrestorecon) {
if (policyfile) {
if (optind != (argc - 1))
usage(argv[0]);
} else if (use_input_file) {
if (optind != (argc - 1)) {
/* Cannot mix with pathname arguments. */
usage(argv[0]);
}
} else {
if (optind > (argc - 2))
usage(argv[0]);
}
/* Use our own invalid context checking function so that
we can support either checking against the active policy or
checking against a binary policy file. */
selinux_set_callback(SELINUX_CB_VALIDATE,
(union selinux_callback)&canoncon);
if (stat(argv[optind], &sb) < 0) {
perror(argv[optind]);
exit(1);
}
if (!S_ISREG(sb.st_mode)) {
fprintf(stderr, "%s: spec file %s is not a regular file.\n",
argv[0], argv[optind]);
exit(1);
}
altpath = argv[optind];
optind++;
}
/* Load the file contexts configuration and check it. */
r_opts.selabel_opt_validate = (ctx_validate ? (char *)1 : NULL);
r_opts.selabel_opt_path = altpath;
if (nerr)
exit(1);
restore_init(&r_opts);
if (use_input_file) {
FILE *f = stdin;
ssize_t len;
int delim;
if (strcmp(input_filename, "-") != 0)
f = fopen(input_filename, "r");
if (f == NULL) {
fprintf(stderr, "Unable to open %s: %s\n", input_filename,
strerror(errno));
usage(argv[0]);
}
__fsetlocking(f, FSETLOCKING_BYCALLER);
delim = (null_terminated != 0) ? '\0' : '\n';
while ((len = getdelim(&buf, &buf_len, delim, f)) > 0) {
buf[len - 1] = 0;
if (!strcmp(buf, "/"))
mass_relabel = 1;
errors |= process_one_realpath(buf, recurse) < 0;
}
if (strcmp(input_filename, "-") != 0)
fclose(f);
} else {
for (i = optind; i < argc; i++) {
if (!strcmp(argv[i], "/"))
mass_relabel = 1;
errors |= process_one_realpath(argv[i], recurse) < 0;
}
}
if (mass_relabel)
mass_relabel_errs = errors;
maybe_audit_mass_relabel();
if (warn_no_match)
selabel_stats(r_opts.hnd);
selabel_close(r_opts.hnd);
restore_finish();
if (r_opts.outfile)
fclose(r_opts.outfile);
if (r_opts.progress && r_opts.count >= STAR_COUNT)
printf("\n");
exit(errors);
}