/*
* File contexts backend for labeling system
*
* Author : Eamon Walsh <ewalsh@tycho.nsa.gov>
*/
#include <fcntl.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <regex.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "callbacks.h"
#include "label_internal.h"
/*
* Internals, mostly moved over from matchpathcon.c
*/
/* A file security context specification. */
typedef struct spec {
struct selabel_lookup_rec lr; /* holds contexts for lookup result */
char *regex_str; /* regular expession string for diagnostics */
char *type_str; /* type string for diagnostic messages */
regex_t regex; /* compiled regular expression */
char regcomp; /* regex_str has been compiled to regex */
mode_t mode; /* mode format value */
int matches; /* number of matching pathnames */
int hasMetaChars; /* regular expression has meta-chars */
int stem_id; /* indicates which stem-compression item */
} spec_t;
/* A regular expression stem */
typedef struct stem {
char *buf;
int len;
} stem_t;
/* Our stored configuration */
struct saved_data {
/*
* The array of specifications, initially in the same order as in
* the specification file. Sorting occurs based on hasMetaChars.
*/
spec_t *spec_arr;
unsigned int nspec;
unsigned int ncomp;
/*
* The array of regular expression stems.
*/
stem_t *stem_arr;
int num_stems;
int alloc_stems;
};
/* Return the length of the text that can be considered the stem, returns 0
* if there is no identifiable stem */
static int get_stem_from_spec(const char *const buf)
{
const char *tmp = strchr(buf + 1, '/');
const char *ind;
if (!tmp)
return 0;
for (ind = buf; ind < tmp; ind++) {
if (strchr(".^$?*+|[({", (int)*ind))
return 0;
}
return tmp - buf;
}
/* return the length of the text that is the stem of a file name */
static int get_stem_from_file_name(const char *const buf)
{
const char *tmp = strchr(buf + 1, '/');
if (!tmp)
return 0;
return tmp - buf;
}
/* find the stem of a file spec, returns the index into stem_arr for a new
* or existing stem, (or -1 if there is no possible stem - IE for a file in
* the root directory or a regex that is too complex for us). */
static int find_stem_from_spec(struct saved_data *data, const char *buf)
{
int i, num = data->num_stems;
int stem_len = get_stem_from_spec(buf);
if (!stem_len)
return -1;
for (i = 0; i < num; i++) {
if (stem_len == data->stem_arr[i].len
&& !strncmp(buf, data->stem_arr[i].buf, stem_len))
return i;
}
if (data->alloc_stems == num) {
stem_t *tmp_arr;
data->alloc_stems = data->alloc_stems * 2 + 16;
tmp_arr = realloc(data->stem_arr,
sizeof(stem_t) * data->alloc_stems);
if (!tmp_arr)
return -1;
data->stem_arr = tmp_arr;
}
data->stem_arr[num].len = stem_len;
data->stem_arr[num].buf = malloc(stem_len + 1);
if (!data->stem_arr[num].buf)
return -1;
memcpy(data->stem_arr[num].buf, buf, stem_len);
data->stem_arr[num].buf[stem_len] = '\0';
data->num_stems++;
buf += stem_len;
return num;
}
/* find the stem of a file name, returns the index into stem_arr (or -1 if
* there is no match - IE for a file in the root directory or a regex that is
* too complex for us). Makes buf point to the text AFTER the stem. */
static int find_stem_from_file(struct saved_data *data, const char **buf)
{
int i;
int stem_len = get_stem_from_file_name(*buf);
if (!stem_len)
return -1;
for (i = 0; i < data->num_stems; i++) {
if (stem_len == data->stem_arr[i].len
&& !strncmp(*buf, data->stem_arr[i].buf, stem_len)) {
*buf += stem_len;
return i;
}
}
return -1;
}
/*
* Warn about duplicate specifications.
*/
static int nodups_specs(struct saved_data *data, const char *path)
{
int rc = 0;
unsigned int ii, jj;
struct spec *curr_spec, *spec_arr = data->spec_arr;
for (ii = 0; ii < data->nspec; ii++) {
curr_spec = &spec_arr[ii];
for (jj = ii + 1; jj < data->nspec; jj++) {
if ((!strcmp
(spec_arr[jj].regex_str, curr_spec->regex_str))
&& (!spec_arr[jj].mode || !curr_spec->mode
|| spec_arr[jj].mode == curr_spec->mode)) {
rc = -1;
errno = EINVAL;
if (strcmp
(spec_arr[jj].lr.ctx_raw,
curr_spec->lr.ctx_raw)) {
selinux_log
(SELINUX_ERROR,
"%s: Multiple different specifications for %s (%s and %s).\n",
path, curr_spec->regex_str,
spec_arr[jj].lr.ctx_raw,
curr_spec->lr.ctx_raw);
} else {
selinux_log
(SELINUX_ERROR,
"%s: Multiple same specifications for %s.\n",
path, curr_spec->regex_str);
}
}
}
}
return rc;
}
/* Determine if the regular expression specification has any meta characters. */
static void spec_hasMetaChars(struct spec *spec)
{
char *c;
int len;
char *end;
c = spec->regex_str;
len = strlen(spec->regex_str);
end = c + len;
spec->hasMetaChars = 0;
/* Look at each character in the RE specification string for a
* meta character. Return when any meta character reached. */
while (c != end) {
switch (*c) {
case '.':
case '^':
case '$':
case '?':
case '*':
case '+':
case '|':
case '[':
case '(':
case '{':
spec->hasMetaChars = 1;
return;
case '\\': /* skip the next character */
c++;
break;
default:
break;
}
c++;
}
return;
}
static int compile_regex(struct saved_data *data, spec_t *spec, char **errbuf)
{
char *reg_buf, *anchored_regex, *cp;
stem_t *stem_arr = data->stem_arr;
size_t len;
int regerr;
if (spec->regcomp)
return 0; /* already done */
data->ncomp++; /* how many compiled regexes required */
/* Skip the fixed stem. */
reg_buf = spec->regex_str;
if (spec->stem_id >= 0)
reg_buf += stem_arr[spec->stem_id].len;
/* Anchor the regular expression. */
len = strlen(reg_buf);
cp = anchored_regex = malloc(len + 3);
if (!anchored_regex)
return -1;
/* Create ^...$ regexp. */
*cp++ = '^';
memcpy(cp, reg_buf, len);
cp += len;
*cp++ = '$';
*cp = '\0';
/* Compile the regular expression. */
regerr = regcomp(&spec->regex, anchored_regex,
REG_EXTENDED | REG_NOSUB);
if (regerr != 0) {
size_t errsz = 0;
errsz = regerror(regerr, &spec->regex, NULL, 0);
if (errsz && errbuf)
*errbuf = malloc(errsz);
if (errbuf && *errbuf)
(void)regerror(regerr, &spec->regex,
*errbuf, errsz);
free(anchored_regex);
return -1;
}
free(anchored_regex);
/* Done. */
spec->regcomp = 1;
return 0;
}
static int process_line(struct selabel_handle *rec,
const char *path, const char *prefix,
char *line_buf, int pass, unsigned lineno)
{
int items, len;
char buf1[BUFSIZ], buf2[BUFSIZ], buf3[BUFSIZ];
char *buf_p, *regex = buf1, *type = buf2, *context = buf3;
struct saved_data *data = (struct saved_data *)rec->data;
spec_t *spec_arr = data->spec_arr;
unsigned int nspec = data->nspec;
len = strlen(line_buf);
if (line_buf[len - 1] == '\n')
line_buf[len - 1] = 0;
buf_p = line_buf;
while (isspace(*buf_p))
buf_p++;
/* Skip comment lines and empty lines. */
if (*buf_p == '#' || *buf_p == 0)
return 0;
items = sscanf(line_buf, "%255s %255s %255s", regex, type, context);
if (items < 2) {
selinux_log(SELINUX_WARNING,
"%s: line %d is missing fields, skipping\n", path,
lineno);
return 0;
} else if (items == 2) {
/* The type field is optional. */
context = type;
type = NULL;
}
len = get_stem_from_spec(regex);
if (len && prefix && strncmp(prefix, regex, len)) {
/* Stem of regex does not match requested prefix, discard. */
return 0;
}
if (pass == 1) {
/* On the second pass, process and store the specification in spec. */
char *errbuf = NULL;
spec_arr[nspec].stem_id = find_stem_from_spec(data, regex);
spec_arr[nspec].regex_str = strdup(regex);
if (!spec_arr[nspec].regex_str) {
selinux_log(SELINUX_WARNING,
"%s: out of memory at line %d on regex %s\n",
path, lineno, regex);
return -1;
}
if (rec->validating && compile_regex(data, &spec_arr[nspec], &errbuf)) {
selinux_log(SELINUX_WARNING,
"%s: line %d has invalid regex %s: %s\n",
path, lineno, regex,
(errbuf ? errbuf : "out of memory"));
}
/* Convert the type string to a mode format */
spec_arr[nspec].mode = 0;
if (!type)
goto skip_type;
spec_arr[nspec].type_str = strdup(type);
len = strlen(type);
if (type[0] != '-' || len != 2) {
selinux_log(SELINUX_WARNING,
"%s: line %d has invalid file type %s\n",
path, lineno, type);
return 0;
}
switch (type[1]) {
case 'b':
spec_arr[nspec].mode = S_IFBLK;
break;
case 'c':
spec_arr[nspec].mode = S_IFCHR;
break;
case 'd':
spec_arr[nspec].mode = S_IFDIR;
break;
case 'p':
spec_arr[nspec].mode = S_IFIFO;
break;
case 'l':
spec_arr[nspec].mode = S_IFLNK;
break;
case 's':
spec_arr[nspec].mode = S_IFSOCK;
break;
case '-':
spec_arr[nspec].mode = S_IFREG;
break;
default:
selinux_log(SELINUX_WARNING,
"%s: line %d has invalid file type %s\n",
path, lineno, type);
return 0;
}
skip_type:
spec_arr[nspec].lr.ctx_raw = strdup(context);
/* Determine if specification has
* any meta characters in the RE */
spec_hasMetaChars(&spec_arr[nspec]);
}
data->nspec = ++nspec;
return 0;
}
static int init(struct selabel_handle *rec, struct selinux_opt *opts,
unsigned n)
{
struct saved_data *data = (struct saved_data *)rec->data;
const char *path = NULL;
const char *prefix = NULL;
FILE *fp;
FILE *localfp = NULL;
FILE *homedirfp = NULL;
char local_path[PATH_MAX + 1];
char homedir_path[PATH_MAX + 1];
char line_buf[BUFSIZ];
unsigned int lineno, pass, i, j, maxnspec;
spec_t *spec_copy = NULL;
int status = -1, baseonly = 0;
struct stat sb;
/* Process arguments */
while (n--)
switch(opts[n].type) {
case SELABEL_OPT_PATH:
path = opts[n].value;
break;
case SELABEL_OPT_SUBSET:
prefix = opts[n].value;
break;
case SELABEL_OPT_BASEONLY:
baseonly = !!opts[n].value;
break;
}
/* Open the specification file. */
if ((fp = fopen(path, "r")) == NULL)
return -1;
if (fstat(fileno(fp), &sb) < 0)
return -1;
if (!S_ISREG(sb.st_mode)) {
errno = EINVAL;
return -1;
}
if (!baseonly) {
snprintf(homedir_path, sizeof(homedir_path), "%s.homedirs",
path);
homedirfp = fopen(homedir_path, "r");
snprintf(local_path, sizeof(local_path), "%s.local", path);
localfp = fopen(local_path, "r");
}
/*
* Perform two passes over the specification file.
* The first pass counts the number of specifications and
* performs simple validation of the input. At the end
* of the first pass, the spec array is allocated.
* The second pass performs detailed validation of the input
* and fills in the spec array.
*/
maxnspec = UINT_MAX / sizeof(spec_t);
for (pass = 0; pass < 2; pass++) {
lineno = 0;
data->nspec = 0;
data->ncomp = 0;
while (fgets(line_buf, sizeof line_buf - 1, fp)
&& data->nspec < maxnspec) {
if (process_line(rec, path, prefix, line_buf,
pass, ++lineno) != 0)
goto finish;
}
if (pass == 1) {
status = nodups_specs(data, path);
if (status)
goto finish;
}
lineno = 0;
if (homedirfp)
while (fgets(line_buf, sizeof line_buf - 1, homedirfp)
&& data->nspec < maxnspec) {
if (process_line
(rec, homedir_path, prefix,
line_buf, pass, ++lineno) != 0)
goto finish;
}
lineno = 0;
if (localfp)
while (fgets(line_buf, sizeof line_buf - 1, localfp)
&& data->nspec < maxnspec) {
if (process_line
(rec, local_path, prefix, line_buf,
pass, ++lineno) != 0)
goto finish;
}
if (pass == 0) {
if (data->nspec == 0) {
status = 0;
goto finish;
}
if (NULL == (data->spec_arr =
malloc(sizeof(spec_t) * data->nspec)))
goto finish;
memset(data->spec_arr, 0, sizeof(spec_t)*data->nspec);
maxnspec = data->nspec;
rewind(fp);
if (homedirfp)
rewind(homedirfp);
if (localfp)
rewind(localfp);
}
}
/* Move exact pathname specifications to the end. */
spec_copy = malloc(sizeof(spec_t) * data->nspec);
if (!spec_copy)
goto finish;
j = 0;
for (i = 0; i < data->nspec; i++)
if (data->spec_arr[i].hasMetaChars)
memcpy(&spec_copy[j++],
&data->spec_arr[i], sizeof(spec_t));
for (i = 0; i < data->nspec; i++)
if (!data->spec_arr[i].hasMetaChars)
memcpy(&spec_copy[j++],
&data->spec_arr[i], sizeof(spec_t));
free(data->spec_arr);
data->spec_arr = spec_copy;
status = 0;
finish:
fclose(fp);
if (data->spec_arr != spec_copy)
free(data->spec_arr);
if (homedirfp)
fclose(homedirfp);
if (localfp)
fclose(localfp);
return status;
}
/*
* Backend interface routines
*/
static void closef(struct selabel_handle *rec)
{
struct saved_data *data = (struct saved_data *)rec->data;
struct spec *spec;
struct stem *stem;
unsigned int i;
for (i = 0; i < data->nspec; i++) {
spec = &data->spec_arr[i];
free(spec->regex_str);
free(spec->type_str);
free(spec->lr.ctx_raw);
free(spec->lr.ctx_trans);
regfree(&spec->regex);
}
for (i = 0; i < (unsigned int)data->num_stems; i++) {
stem = &data->stem_arr[i];
free(stem->buf);
}
if (data->spec_arr)
free(data->spec_arr);
if (data->stem_arr)
free(data->stem_arr);
free(data);
}
static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
const char *key, int type)
{
struct saved_data *data = (struct saved_data *)rec->data;
spec_t *spec_arr = data->spec_arr;
int i, rc, file_stem;
mode_t mode = (mode_t)type;
const char *buf;
struct selabel_lookup_rec *ret = NULL;
char *clean_key = NULL;
const char *prev_slash, *next_slash;
unsigned int sofar = 0;
if (!data->nspec) {
errno = ENOENT;
goto finish;
}
/* Remove duplicate slashes */
if ((next_slash = strstr(key, "//"))) {
clean_key = malloc(strlen(key) + 1);
if (!clean_key)
goto finish;
prev_slash = key;
while (next_slash) {
memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash);
sofar += next_slash - prev_slash;
prev_slash = next_slash + 1;
next_slash = strstr(prev_slash, "//");
}
strcpy(clean_key + sofar, prev_slash);
key = clean_key;
}
buf = key;
file_stem = find_stem_from_file(data, &buf);
mode &= S_IFMT;
/*
* Check for matching specifications in reverse order, so that
* the last matching specification is used.
*/
for (i = data->nspec - 1; i >= 0; i--) {
/* if the spec in question matches no stem or has the same
* stem as the file AND if the spec in question has no mode
* specified or if the mode matches the file mode then we do
* a regex check */
if ((spec_arr[i].stem_id == -1
|| spec_arr[i].stem_id == file_stem)
&& (!mode || !spec_arr[i].mode
|| mode == spec_arr[i].mode)) {
if (compile_regex(data, &spec_arr[i], NULL) < 0)
goto finish;
if (spec_arr[i].stem_id == -1)
rc = regexec(&spec_arr[i].regex, key, 0, 0, 0);
else
rc = regexec(&spec_arr[i].regex, buf, 0, 0, 0);
if (rc == 0) {
spec_arr[i].matches++;
break;
}
if (rc == REG_NOMATCH)
continue;
/* else it's an error */
goto finish;
}
}
if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) {
/* No matching specification. */
errno = ENOENT;
goto finish;
}
ret = &spec_arr[i].lr;
finish:
free(clean_key);
return ret;
}
static void stats(struct selabel_handle *rec)
{
struct saved_data *data = (struct saved_data *)rec->data;
unsigned int i, nspec = data->nspec;
spec_t *spec_arr = data->spec_arr;
for (i = 0; i < nspec; i++) {
if (spec_arr[i].matches == 0) {
if (spec_arr[i].type_str) {
selinux_log(SELINUX_WARNING,
"Warning! No matches for (%s, %s, %s)\n",
spec_arr[i].regex_str,
spec_arr[i].type_str,
spec_arr[i].lr.ctx_raw);
} else {
selinux_log(SELINUX_WARNING,
"Warning! No matches for (%s, %s)\n",
spec_arr[i].regex_str,
spec_arr[i].lr.ctx_raw);
}
}
}
}
int selabel_file_init(struct selabel_handle *rec, struct selinux_opt *opts,
unsigned nopts)
{
struct saved_data *data;
data = (struct saved_data *)malloc(sizeof(*data));
if (!data)
return -1;
memset(data, 0, sizeof(*data));
rec->data = data;
rec->func_close = &closef;
rec->func_stats = &stats;
rec->func_lookup = &lookup;
return init(rec, opts, nopts);
}