blob: 887e32cd180cc543621bb3abaab6a3fcab27af3b [file] [log] [blame]
/*
* Property Service contexts backend for labeling Android
* property keys
*/
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "callbacks.h"
#include "label_internal.h"
/* A property security context specification. */
typedef struct spec {
struct selabel_lookup_rec lr; /* holds contexts for lookup result */
char *property_key; /* property key string */
} spec_t;
/* Our stored configuration */
struct saved_data {
/*
* The array of specifications is sorted for longest
* prefix match
*/
spec_t *spec_arr;
unsigned int nspec; /* total number of specifications */
};
static int cmp(const void *A, const void *B)
{
const struct spec *sp1 = A, *sp2 = B;
if (strncmp(sp1->property_key, "*", 1) == 0)
return 1;
if (strncmp(sp2->property_key, "*", 1) == 0)
return -1;
size_t L1 = strlen(sp1->property_key);
size_t L2 = strlen(sp2->property_key);
return (L1 < L2) - (L1 > L2);
}
/*
* 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].property_key,
curr_spec->property_key)) {
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->property_key,
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->property_key);
}
}
}
}
return rc;
}
static int process_line(struct selabel_handle *rec,
const char *path, char *line_buf,
int pass, unsigned lineno)
{
int items;
char *prop = NULL, *context = NULL;
struct saved_data *data = (struct saved_data *)rec->data;
spec_t *spec_arr = data->spec_arr;
unsigned int nspec = data->nspec;
const char *errbuf = NULL;
items = read_spec_entries(line_buf, &errbuf, 2, &prop, &context);
if (items < 0) {
items = errno;
selinux_log(SELINUX_ERROR,
"%s: line %u error due to: %s\n", path,
lineno, errbuf ?: strerror(errno));
errno = items;
return -1;
}
if (items == 0)
return items;
if (items != 2) {
selinux_log(SELINUX_ERROR,
"%s: line %u is missing fields\n", path,
lineno);
free(prop);
errno = EINVAL;
return -1;
}
if (pass == 0) {
free(prop);
free(context);
} else if (pass == 1) {
/* On the second pass, process and store the specification in spec. */
spec_arr[nspec].property_key = prop;
spec_arr[nspec].lr.ctx_raw = context;
if (rec->validating) {
if (selabel_validate(rec, &spec_arr[nspec].lr) < 0) {
selinux_log(SELINUX_ERROR,
"%s: line %u has invalid context %s\n",
path, lineno, spec_arr[nspec].lr.ctx_raw);
errno = EINVAL;
return -1;
}
}
}
data->nspec = ++nspec;
return 0;
}
static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
unsigned n)
{
struct saved_data *data = (struct saved_data *)rec->data;
const char *path = NULL;
FILE *fp;
char line_buf[BUFSIZ];
unsigned int lineno, maxnspec, pass;
int status = -1;
struct stat sb;
/* Process arguments */
while (n--)
switch (opts[n].type) {
case SELABEL_OPT_PATH:
path = opts[n].value;
break;
}
if (!path)
return -1;
/* Open the specification file. */
if ((fp = fopen(path, "r")) == NULL)
return -1;
if (fstat(fileno(fp), &sb) < 0)
goto finish;
errno = EINVAL;
if (!S_ISREG(sb.st_mode))
goto finish;
/*
* Two passes of the specification file. First is to get the size.
* After the first pass, the spec array is malloced to the appropriate
* size. Second pass is to populate the spec array and check for
* dups.
*/
maxnspec = UINT_MAX / sizeof(spec_t);
for (pass = 0; pass < 2; pass++) {
data->nspec = 0;
lineno = 0;
while (fgets(line_buf, sizeof(line_buf) - 1, fp)
&& data->nspec < maxnspec) {
if (process_line(rec, path, line_buf, pass, ++lineno)
!= 0)
goto finish;
}
if (pass == 1) {
status = nodups_specs(data, path);
if (status)
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);
}
}
qsort(data->spec_arr, data->nspec, sizeof(struct spec), cmp);
status = 0;
finish:
fclose(fp);
return status;
}
/*
* Backend interface routines
*/
static void closef(struct selabel_handle *rec)
{
struct saved_data *data = (struct saved_data *)rec->data;
struct spec *spec;
unsigned int i;
for (i = 0; i < data->nspec; i++) {
spec = &data->spec_arr[i];
free(spec->property_key);
free(spec->lr.ctx_raw);
free(spec->lr.ctx_trans);
}
if (data->spec_arr)
free(data->spec_arr);
free(data);
}
static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
const char *key,
int __attribute__((unused)) type)
{
struct saved_data *data = (struct saved_data *)rec->data;
spec_t *spec_arr = data->spec_arr;
unsigned int i;
struct selabel_lookup_rec *ret = NULL;
if (!data->nspec) {
errno = ENOENT;
goto finish;
}
for (i = 0; i < data->nspec; i++) {
if (strncmp(spec_arr[i].property_key, key,
strlen(spec_arr[i].property_key)) == 0) {
break;
}
if (strncmp(spec_arr[i].property_key, "*", 1) == 0)
break;
}
if (i >= data->nspec) {
/* No matching specification. */
errno = ENOENT;
goto finish;
}
ret = &spec_arr[i].lr;
finish:
return ret;
}
static void stats(struct selabel_handle __attribute__((unused)) *rec)
{
selinux_log(SELINUX_WARNING, "'stats' functionality not implemented.\n");
}
int selabel_property_init(struct selabel_handle *rec,
const 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);
}