#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include "selinux_internal.h"
#include <stdio.h>
#include <stdio_ext.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <regex.h>
#include <stdarg.h>
#include "policy.h"
#include "context_internal.h"
static void
#ifdef __GNUC__
__attribute__ ((format (printf, 1, 2)))
#endif
default_printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
/* If MLS is disabled, strip any MLS level field from the context.
This allows file_contexts with MLS levels to be processed on
a non-MLS system that otherwise has the same policy. */
static inline int STRIP_LEVEL(char **context, int mls_enabled)
{
char *str;
context_t con;
int rc = -1;
if (mls_enabled)
return 0;
con = context_new(*context);
if (!con)
return rc;
if (context_range_set(con,NULL))
goto out;
str = context_str(con);
if (!str)
goto out;
str = strdup(str);
if (!str)
goto out;
free(*context);
*context = str;
rc = 0;
out:
context_free(con);
return rc;
}
static void (*myprintf)(const char *fmt, ...) = &default_printf;
void set_matchpathcon_printf(void (*f)(const char *fmt, ...))
{
if (f)
myprintf = f;
else
myprintf = &default_printf;
}
static int default_invalidcon(const char *path, unsigned lineno, char *context)
{
if (security_check_context(context) < 0 && errno != ENOENT) {
myprintf("%s: line %u has invalid context %s\n", path, lineno, context);
return 1;
}
return 0;
}
static int (*myinvalidcon)(const char *p, unsigned l, char *c) = &default_invalidcon;
void set_matchpathcon_invalidcon(int (*f)(const char *p, unsigned l, char *c))
{
if (f)
myinvalidcon = f;
else
myinvalidcon = &default_invalidcon;
}
static unsigned int myflags;
void set_matchpathcon_flags(unsigned int flags)
{
myflags = flags;
}
/*
* A file security context specification.
*/
typedef struct spec {
char *regex_str; /* regular expession string for diagnostic messages */
char *type_str; /* type string for diagnostic messages */
char *context; /* context string */
regex_t regex; /* compiled regular expression */
mode_t mode; /* mode format value */
int matches; /* number of matching pathnames */
int hasMetaChars; /* indicates whether the RE has
any meta characters.
0 = no meta chars
1 = has one or more meta chars */
int stem_id; /* indicates which of the stem-compression
* items it matches */
} spec_t;
typedef struct stem {
char *buf;
int len;
} stem_t;
static stem_t *stem_arr = NULL;
static int num_stems = 0;
static int alloc_stems = 0;
static const char * const regex_chars = ".^$?*+|[({";
/* 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(regex_chars, (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). Makes buf
* point to the text AFTER the stem. */
static int find_stem_from_spec(const char **buf)
{
int i;
int stem_len = get_stem_from_spec(*buf);
if(!stem_len)
return -1;
for(i = 0; i < num_stems; i++)
{
if(stem_len == stem_arr[i].len && !strncmp(*buf, stem_arr[i].buf, stem_len))
{
*buf += stem_len;
return i;
}
}
if(num_stems == alloc_stems)
{
stem_t *tmp_arr;
alloc_stems = alloc_stems * 2 + 16;
tmp_arr = realloc(stem_arr, sizeof(stem_t) * alloc_stems);
if(!tmp_arr)
return -1;
stem_arr = tmp_arr;
}
stem_arr[num_stems].len = stem_len;
stem_arr[num_stems].buf = malloc(stem_len + 1);
if(!stem_arr[num_stems].buf)
return -1;
memcpy(stem_arr[num_stems].buf, *buf, stem_len);
stem_arr[num_stems].buf[stem_len] = '\0';
num_stems++;
*buf += stem_len;
return num_stems - 1;
}
/* 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(const char **buf)
{
int i;
int stem_len = get_stem_from_file_name(*buf);
if(!stem_len)
return -1;
for(i = 0; i < num_stems; i++)
{
if(stem_len == stem_arr[i].len && !strncmp(*buf, stem_arr[i].buf, stem_len))
{
*buf += stem_len;
return i;
}
}
return -1;
}
/*
* The array of specifications, initially in the
* same order as in the specification file.
* Sorting occurs based on hasMetaChars
*/
static spec_t *spec_arr;
static unsigned int nspec;
/*
* An association between an inode and a
* specification.
*/
typedef struct file_spec {
ino_t ino; /* inode number */
int specind; /* index of specification in spec */
char *file; /* full pathname for diagnostic messages about conflicts */
struct file_spec *next; /* next association in hash bucket chain */
} file_spec_t;
/*
* 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)
static file_spec_t *fl_head;
/*
* Try to add an association between an inode and
* a specification. If there is already an association
* for the inode and it conflicts with this specification,
* then use the specification that occurs later in the
* specification array.
*/
int matchpathcon_filespec_add(ino_t ino, int specind, const char *file)
{
file_spec_t *prevfl, *fl;
int h, no_conflict, ret;
struct stat 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 = lstat(fl->file, &sb);
if (ret < 0 || sb.st_ino != ino) {
fl->specind = specind;
free(fl->file);
fl->file = malloc(strlen(file) + 1);
if (!fl->file)
goto oom;
strcpy(fl->file, file);
return fl->specind;
}
no_conflict = (strcmp(spec_arr[fl->specind].context,spec_arr[specind].context) == 0);
if (no_conflict)
return fl->specind;
myprintf("%s: conflicting specifications for %s and %s, using %s.\n",
__FUNCTION__, file, fl->file,
((specind > fl->specind) ? spec_arr[specind].
context : spec_arr[fl->specind].context));
fl->specind =
(specind >
fl->specind) ? specind : fl->specind;
free(fl->file);
fl->file = malloc(strlen(file) + 1);
if (!fl->file)
goto oom;
strcpy(fl->file, file);
return fl->specind;
}
if (ino > fl->ino)
break;
}
fl = malloc(sizeof(file_spec_t));
if (!fl)
goto oom;
fl->ino = ino;
fl->specind = specind;
fl->file = malloc(strlen(file) + 1);
if (!fl->file)
goto oom_freefl;
strcpy(fl->file, file);
fl->next = prevfl->next;
prevfl->next = fl;
return fl->specind;
oom_freefl:
free(fl);
oom:
myprintf("%s: insufficient memory for file label entry for %s\n",
__FUNCTION__, file);
return -1;
}
/*
* Evaluate the association hash table distribution.
*/
void matchpathcon_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;
}
myprintf
("%s: hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
__FUNCTION__, nel, used, HASH_BUCKETS, longest);
}
/*
* Destroy the association hash table.
*/
void matchpathcon_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;
free(tmp->file);
free(tmp);
}
fl_head[h].next = NULL;
}
free(fl_head);
fl_head = NULL;
}
/*
* Warn about duplicate specifications.
*/
static void nodups_specs(const char *path)
{
unsigned int ii, jj;
struct spec *curr_spec;
for (ii = 0; ii < nspec; ii++) {
curr_spec = &spec_arr[ii];
for (jj = ii + 1; jj < 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)) {
if (strcmp(spec_arr[jj].context, curr_spec->context)) {
myprintf(
"%s: Multiple different specifications for %s (%s and %s).\n",
path, curr_spec->regex_str,
spec_arr[jj].context,
curr_spec->context);
}
else {
myprintf(
"%s: Multiple same specifications for %s.\n",
path, curr_spec->regex_str);
}
}
}
}
}
/* 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 process_line( const char *path, char *line_buf, int pass, unsigned lineno, int mls_enabled) {
int items, len, regerr;
char *buf_p;
char *regex, *type, *context;
char *anchored_regex;
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, "%as %as %as", ®ex, &type,
&context);
if (items < 2) {
myprintf("%s: line %d is missing fields\n, skipping", path, lineno);
return 0;
} else if (items == 2) {
/* The type field is optional. */
free(context);
context = type;
type = 0;
}
if (pass == 1) {
/* On the second pass, compile and store the specification in spec. */
const char *reg_buf = regex;
char *cp;
spec_arr[nspec].stem_id = find_stem_from_spec(®_buf);
spec_arr[nspec].regex_str = regex;
/* Anchor the regular expression. */
len = strlen(reg_buf);
cp = anchored_regex = malloc(len + 3);
if (!anchored_regex)
return -1;
/* Create ^...$ regexp. */
*cp++ = '^';
cp = mempcpy(cp, reg_buf, len);
*cp++ = '$';
*cp = '\0';
/* Compile the regular expression. */
regerr =
regcomp(&spec_arr[nspec].regex,
anchored_regex,
REG_EXTENDED | REG_NOSUB);
if (regerr != 0) {
size_t errsz = 0;
char *errbuf = NULL;
errsz = regerror(regerr, &spec_arr[nspec].regex,
errbuf, errsz);
if (errsz)
errbuf = malloc(errsz);
if (errbuf)
(void) regerror(regerr,
&spec_arr[nspec].regex,
errbuf, errsz);
myprintf("%s: line %d has invalid regex %s: %s\n", path, lineno, anchored_regex, (errbuf ? errbuf : "out of memory"));
free(anchored_regex);
return 0;
}
free(anchored_regex);
/* Convert the type string to a mode format */
spec_arr[nspec].type_str = type;
spec_arr[nspec].mode = 0;
if (!type)
goto skip_type;
len = strlen(type);
if (type[0] != '-' || len != 2) {
myprintf("%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:
myprintf("%s: line %d has invalid file type %s\n", path, lineno, type);
return 0;
}
skip_type:
if (strcmp(context, "<<none>>")) {
if (context_translations) {
if (raw_to_trans_context(context,
&spec_arr[nspec].context)) {
myprintf("%s: line %u has invalid "
"context %s\n",
path, lineno, context);
return 0;
}
free(context);
context = spec_arr[nspec].context;
} else {
if (STRIP_LEVEL(&context, mls_enabled))
return -1;
}
if (myinvalidcon(path, lineno, context))
return 0;
}
spec_arr[nspec].context = context;
/* Determine if specification has
* any meta characters in the RE */
spec_hasMetaChars(&spec_arr[nspec]);
}
nspec++;
if (pass == 0) {
free(regex);
if (type)
free(type);
free(context);
}
return 0;
}
int matchpathcon_init(const char *path)
{
FILE *fp;
FILE *localfp = NULL;
FILE *homedirfp = NULL;
char local_path[PATH_MAX + 1];
char homedir_path[PATH_MAX + 1];
char *line_buf = NULL;
size_t line_len = 0;
unsigned int lineno, pass, i, j, maxnspec;
spec_t *spec_copy=NULL;
int status=-1;
int mls_enabled=is_selinux_mls_enabled();
/* Open the specification file. */
if (!path)
path = selinux_file_context_path();
if ((fp = fopen(path, "r")) == NULL)
return -1;
__fsetlocking(fp, FSETLOCKING_BYCALLER);
if ((myflags & MATCHPATHCON_BASEONLY) == 0) {
snprintf(homedir_path, sizeof(homedir_path), "%s.homedirs", path);
homedirfp = fopen(homedir_path, "r");
if (homedirfp != NULL)
__fsetlocking(homedirfp, FSETLOCKING_BYCALLER);
snprintf(local_path, sizeof(local_path), "%s.local", path);
localfp = fopen(local_path, "r");
if (localfp != NULL)
__fsetlocking(localfp, FSETLOCKING_BYCALLER);
}
/*
* 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;
nspec = 0;
while (getline(&line_buf, &line_len, fp) > 0 && nspec < maxnspec) {
if (process_line(path, line_buf, pass, ++lineno, mls_enabled) != 0)
goto finish;
}
lineno = 0;
if (homedirfp)
while (getline(&line_buf, &line_len, homedirfp) > 0 && nspec < maxnspec) {
if (process_line(homedir_path, line_buf, pass, ++lineno, mls_enabled) != 0)
goto finish;
}
lineno = 0;
if (localfp)
while (getline(&line_buf, &line_len, localfp) > 0 && nspec < maxnspec) {
if (process_line(local_path, line_buf, pass, ++lineno, mls_enabled) != 0)
goto finish;
}
if (pass == 0) {
if (nspec == 0) {
status = 0;
goto finish;
}
if ((spec_arr = malloc(sizeof(spec_t) * nspec)) ==
NULL)
goto finish;
memset(spec_arr, '\0', sizeof(spec_t) * nspec);
maxnspec = nspec;
rewind(fp);
if (homedirfp) rewind(homedirfp);
if (localfp) rewind(localfp);
}
}
free(line_buf);
/* Move exact pathname specifications to the end. */
spec_copy = malloc(sizeof(spec_t) * nspec);
if (!spec_copy)
goto finish;
j = 0;
for (i = 0; i < nspec; i++) {
if (spec_arr[i].hasMetaChars)
memcpy(&spec_copy[j++], &spec_arr[i], sizeof(spec_t));
}
for (i = 0; i < nspec; i++) {
if (!spec_arr[i].hasMetaChars)
memcpy(&spec_copy[j++], &spec_arr[i], sizeof(spec_t));
}
free(spec_arr);
spec_arr = spec_copy;
nodups_specs(path);
status = 0;
finish:
fclose(fp);
if (spec_arr != spec_copy) free(spec_arr);
if (homedirfp) fclose(homedirfp);
if (localfp) fclose(localfp);
return status;
}
hidden_def(matchpathcon_init)
static int matchpathcon_common(const char *name,
mode_t mode)
{
int i, ret, file_stem;
const char *buf = name;
if (!nspec) {
ret = matchpathcon_init(NULL);
if (ret < 0)
return ret;
if (!nspec) {
errno = ENOENT;
return -1;
}
}
file_stem = find_stem_from_file(&buf);
mode &= S_IFMT;
/*
* Check for matching specifications in reverse order, so that
* the last matching specification is used.
*/
for (i = 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 & S_IFMT) == spec_arr[i].mode ) ) )
{
if(spec_arr[i].stem_id == -1)
ret = regexec(&spec_arr[i].regex, name, 0, NULL, 0);
else
ret = regexec(&spec_arr[i].regex, buf, 0, NULL, 0);
if (ret == 0)
break;
if (ret == REG_NOMATCH)
continue;
/* else it's an error */
return -1;
}
}
if (i < 0) {
/* No matching specification. */
errno = ENOENT;
return -1;
}
spec_arr[i].matches++;
return i;
}
int matchpathcon(const char *name,
mode_t mode,
security_context_t *con)
{
int i = matchpathcon_common(name, mode);
if (i < 0)
return -1;
if (strcmp(spec_arr[i].context, "<<none>>") == 0) {
errno = ENOENT;
return -1;
}
*con = strdup(spec_arr[i].context);
if (!(*con))
return -1;
return 0;
}
int matchpathcon_index(const char *name,
mode_t mode,
security_context_t *con)
{
int i = matchpathcon_common(name, mode);
if (i < 0)
return -1;
*con = strdup(spec_arr[i].context);
if (!(*con))
return -1;
return i;
}
void matchpathcon_checkmatches(char *str)
{
unsigned int i;
for (i = 0; i < nspec; i++) {
if (spec_arr[i].matches == 0) {
if (spec_arr[i].type_str) {
myprintf
("%s: Warning! No matches for (%s, %s, %s)\n",
str, spec_arr[i].regex_str,
spec_arr[i].type_str, spec_arr[i].context);
} else {
myprintf
("%s: Warning! No matches for (%s, %s)\n",
str, spec_arr[i].regex_str,
spec_arr[i].context);
}
}
}
}