platform_external_selinux/policycoreutils/setfiles/setfiles.c
Laszlo Ersek 2b6f7e969c setfiles: introduce the -C option for distinguishing file tree walk errors
setfiles(8) exits with status 255 if it encounters any error. Introduce
the "-C" option: if the only errors that setfiles(8) encounters are
labeling errors seen during the file tree walk(s), then let setfiles(8)
exit with status 1.

Cc: "Richard W.M. Jones" <rjones@redhat.com>
Cc: Petr Lautrbach <plautrba@redhat.com>
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1794518
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
2022-05-04 10:31:43 +02:00

478 lines
11 KiB
C

#include "restore.h"
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio_ext.h>
#include <ctype.h>
#include <regex.h>
#include <sys/vfs.h>
#include <libgen.h>
#ifdef USE_AUDIT
#include <libaudit.h>
#ifndef AUDIT_FS_RELABEL
#define AUDIT_FS_RELABEL 2309
#endif
#endif
static char *policyfile;
static int warn_no_match;
static int null_terminated;
static int request_digest;
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 */
static __attribute__((__noreturn__)) void usage(const char *const name)
{
if (iamrestorecon) {
fprintf(stderr,
"usage: %s [-iIDFmnprRv0xT] [-e excludedir] pathname...\n"
"usage: %s [-iIDFmnprRv0xT] [-e excludedir] -f filename\n",
name, name);
} else {
fprintf(stderr,
"usage: %s [-diIDlmnpqvCEFWT] [-e excludedir] [-r alt_root_path] [-c policyfile] spec_file pathname...\n"
"usage: %s [-diIDlmnpqvCEFWT] [-e excludedir] [-r alt_root_path] [-c policyfile] spec_file -f filename\n"
"usage: %s -s [-diIDlmnpqvFWT] spec_file\n",
name, name, name);
}
exit(-1);
}
static void set_rootpath(const char *arg)
{
if (strlen(arg) == 1 && strncmp(arg, "/", 1) == 0) {
fprintf(stderr, "%s: invalid alt_rootpath: %s\n",
r_opts.progname, arg);
exit(-1);
}
r_opts.rootpath = strdup(arg);
if (!r_opts.rootpath) {
fprintf(stderr,
"%s: insufficient memory for r_opts.rootpath\n",
r_opts.progname);
exit(-1);
}
}
static 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;
}
return rc;
}
#ifndef USE_AUDIT
static void maybe_audit_mass_relabel(int mass_relabel __attribute__((unused)),
int mass_relabel_errs __attribute__((unused)))
{
#else
static void maybe_audit_mass_relabel(int mass_relabel, int mass_relabel_errs)
{
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
}
static int __attribute__ ((format(printf, 2, 3)))
log_callback(int type, const char *fmt, ...)
{
int rc;
FILE *out;
va_list ap;
if (type == SELINUX_INFO) {
out = stdout;
} else {
out = stderr;
fflush(stdout);
fprintf(out, "%s: ", r_opts.progname);
}
va_start(ap, fmt);
rc = vfprintf(out, fmt, ap);
va_end(ap);
return rc;
}
int main(int argc, char **argv)
{
struct stat sb;
int opt, i = 0;
const char *input_filename = NULL;
int use_input_file = 0;
char *buf = NULL, *endptr;
size_t buf_len, nthreads = 1;
const char *base;
int errors = 0;
const char *ropts = "e:f:hiIDlmno:pqrsvFRW0xT:";
const char *sopts = "c:de:f:hiIDlmno:pqr:svCEFR:W0T:";
const char *opts;
union selinux_callback cb;
long unsigned skipped_errors;
/* Initialize variables */
memset(&r_opts, 0, sizeof(r_opts));
altpath = NULL;
null_terminated = 0;
warn_no_match = 0;
request_digest = 0;
policyfile = NULL;
skipped_errors = 0;
if (!argv[0]) {
fprintf(stderr, "Called without required program name!\n");
exit(-1);
}
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,
* Try to track inode associations for conflict detection,
* Does not follow mounts (sets SELINUX_RESTORECON_XDEV),
* Validates all file contexts at init time.
*/
iamrestorecon = 0;
r_opts.recurse = SELINUX_RESTORECON_RECURSE;
r_opts.userealpath = 0; /* SELINUX_RESTORECON_REALPATH */
r_opts.add_assoc = SELINUX_RESTORECON_ADD_ASSOC;
/* FTS_PHYSICAL and FTS_NOCHDIR are always set by selinux_restorecon(3) */
r_opts.xdev = SELINUX_RESTORECON_XDEV;
r_opts.ignore_mounts = 0; /* SELINUX_RESTORECON_IGNORE_MOUNTS */
ctx_validate = 1;
opts = sopts;
} else {
/*
* restorecon:
* No recursive descent unless -r/-R,
* Expands paths via realpath,
* Do not try to track inode associations for conflict detection,
* Follows mounts,
* Does lazy validation of contexts upon use.
*/
if (strcmp(base, RESTORECON))
fprintf(stderr, "Executed with unrecognized name (%s), defaulting to %s behavior.\n",
base, RESTORECON);
iamrestorecon = 1;
r_opts.recurse = 0;
r_opts.userealpath = SELINUX_RESTORECON_REALPATH;
r_opts.add_assoc = 0;
r_opts.xdev = 0;
r_opts.ignore_mounts = 0;
ctx_validate = 0;
opts = ropts;
/* restorecon only: silent exit if no SELinux.
* Allows unconditional execution by scripts.
*/
if (is_selinux_enabled() <= 0)
exit(0);
}
/* Process any options. */
while ((opt = getopt(argc, argv, opts)) > 0) {
switch (opt) {
case 'c':
{
FILE *policystream;
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':
if (lstat(optarg, &sb) < 0 && errno != EACCES) {
fprintf(stderr, "Can't stat exclude path \"%s\", %s - ignoring.\n",
optarg, strerror(errno));
break;
}
add_exclude(optarg);
break;
case 'f':
use_input_file = 1;
input_filename = optarg;
break;
case 'd':
r_opts.debug = 1;
r_opts.log_matches =
SELINUX_RESTORECON_LOG_MATCHES;
break;
case 'i':
r_opts.ignore_noent =
SELINUX_RESTORECON_IGNORE_NOENTRY;
break;
case 'I': /* Force label check by ignoring directory digest. */
r_opts.ignore_digest =
SELINUX_RESTORECON_IGNORE_DIGEST;
request_digest = 1;
break;
case 'D': /*
* Request file_contexts digest in selabel_open
* This will effectively enable usage of the
* security.restorecon_last extended attribute.
*/
request_digest = 1;
break;
case 'l':
r_opts.syslog_changes =
SELINUX_RESTORECON_SYSLOG_CHANGES;
break;
case 'C':
r_opts.count_errors = SELINUX_RESTORECON_COUNT_ERRORS;
break;
case 'E':
r_opts.conflict_error =
SELINUX_RESTORECON_CONFLICT_ERROR;
break;
case 'F':
r_opts.set_specctx =
SELINUX_RESTORECON_SET_SPECFILE_CTX;
break;
case 'm':
r_opts.ignore_mounts =
SELINUX_RESTORECON_IGNORE_MOUNTS;
break;
case 'n':
r_opts.nochange = SELINUX_RESTORECON_NOCHANGE;
break;
case 'o': /* Deprecated */
fprintf(stderr, "%s: -o option no longer supported\n",
r_opts.progname);
break;
case 'q':
/* Deprecated - Was only used to say whether print
* filespec_eval() params. Now uses verbose flag.
*/
break;
case 'R':
case 'r':
if (iamrestorecon) {
r_opts.recurse = SELINUX_RESTORECON_RECURSE;
break;
}
if (lstat(optarg, &sb) < 0 && errno != EACCES) {
fprintf(stderr,
"Can't stat alt_root_path \"%s\", %s\n",
optarg, strerror(errno));
exit(-1);
}
if (r_opts.rootpath) {
fprintf(stderr,
"%s: only one -r can be specified\n",
argv[0]);
exit(-1);
}
set_rootpath(optarg);
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");
usage(argv[0]);
}
r_opts.verbose = SELINUX_RESTORECON_VERBOSE;
break;
case 'p':
if (r_opts.verbose) {
fprintf(stderr,
"Progress and Verbose mutually exclusive\n");
usage(argv[0]);
}
r_opts.progress = SELINUX_RESTORECON_PROGRESS;
break;
case 'W':
warn_no_match = 1; /* Print selabel_stats() */
break;
case '0':
null_terminated = 1;
break;
case 'x':
r_opts.xdev = SELINUX_RESTORECON_XDEV;
break;
case 'T':
nthreads = strtoull(optarg, &endptr, 10);
if (*optarg == '\0' || *endptr != '\0')
usage(argv[0]);
break;
case 'h':
case '?':
usage(argv[0]);
}
}
for (i = optind; i < argc; i++) {
if (!strcmp(argv[i], "/"))
r_opts.mass_relabel = SELINUX_RESTORECON_MASS_RELABEL;
}
cb.func_log = log_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
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.
*/
cb.func_validate = canoncon;
selinux_set_callback(SELINUX_CB_VALIDATE, cb);
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++;
} else if (argc < 2)
usage(argv[0]);
/* Set selabel_open options. */
r_opts.selabel_opt_validate = (ctx_validate ? (char *)1 : NULL);
r_opts.selabel_opt_digest = (request_digest ? (char *)1 : NULL);
r_opts.selabel_opt_path = altpath;
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, "/"))
r_opts.mass_relabel = SELINUX_RESTORECON_MASS_RELABEL;
errors |= process_glob(buf, &r_opts, nthreads,
&skipped_errors) < 0;
}
if (strcmp(input_filename, "-") != 0)
fclose(f);
} else {
for (i = optind; i < argc; i++)
errors |= process_glob(argv[i], &r_opts, nthreads,
&skipped_errors) < 0;
}
maybe_audit_mass_relabel(r_opts.mass_relabel, errors);
if (warn_no_match)
selabel_stats(r_opts.hnd);
selabel_close(r_opts.hnd);
restore_finish();
if (r_opts.progress)
fprintf(stdout, "\n");
exit(errors ? -1 : skipped_errors ? 1 : 0);
}