/*
* 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. Return error on different specifications.
* TODO: Remove duplicate specifications. Move duplicate check to after sort
* to improve performance.
*/
static int nodups_specs(struct saved_data *data)
{
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)) {
if (strcmp(spec_arr[jj].lr.ctx_raw,
curr_spec->lr.ctx_raw)) {
rc = -1;
errno = EINVAL;
selinux_log
(SELINUX_ERROR,
"Multiple different specifications for %s (%s and %s).\n",
curr_spec->property_key,
spec_arr[jj].lr.ctx_raw,
curr_spec->lr.ctx_raw);
} else {
selinux_log
(SELINUX_WARNING,
"Multiple same specifications for %s.\n",
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 process_file(struct selabel_handle *rec, const char *path)
{
struct saved_data *data = (struct saved_data *)rec->data;
char line_buf[BUFSIZ];
unsigned int lineno, maxnspec, pass;
struct stat sb;
FILE *fp;
int status = -1;
unsigned int nspec;
spec_t *spec_arr;
/* Open the specification file. */
if ((fp = fopen(path, "re")) == NULL)
return -1;
if (fstat(fileno(fp), &sb) < 0)
goto finish;
errno = EINVAL;
if (!S_ISREG(sb.st_mode))
goto finish;
/*
* Two passes per specification file. First is to get the size.
* After the first pass, the spec array is malloced / realloced to
* the appropriate size. Second pass is to populate the spec array.
*/
maxnspec = UINT_MAX / sizeof(spec_t);
for (pass = 0; pass < 2; pass++) {
nspec = 0;
lineno = 0;
while (fgets(line_buf, sizeof(line_buf) - 1, fp) &&
nspec < maxnspec) {
if (process_line(rec, path, line_buf, pass, ++lineno))
goto finish;
nspec++;
}
if (pass == 0) {
if (nspec == 0) {
status = 0;
goto finish;
}
/* grow spec array if required */
spec_arr = realloc(data->spec_arr,
(data->nspec + nspec) * sizeof(spec_t));
if (spec_arr == NULL)
goto finish;
memset(&spec_arr[data->nspec], 0, nspec * sizeof(spec_t));
data->spec_arr = spec_arr;
maxnspec = nspec;
rewind(fp);
}
}
status = digest_add_specfile(rec->digest, fp, NULL, sb.st_size, path);
finish:
fclose(fp);
return status;
}
static void closef(struct selabel_handle *rec);
static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
unsigned n)
{
struct saved_data *data = (struct saved_data *)rec->data;
char **paths = NULL;
size_t num_paths = 0;
int status = -1;
size_t i;
/* Process arguments */
i = n;
while (i--) {
switch (opts[i].type) {
case SELABEL_OPT_PATH:
num_paths++;
break;
}
}
if (!num_paths)
return -1;
paths = calloc(num_paths, sizeof(*paths));
if (!paths)
return -1;
rec->spec_files = paths;
rec->spec_files_len = num_paths;
i = n;
while (i--) {
switch(opts[i].type) {
case SELABEL_OPT_PATH:
*paths = strdup(opts[i].value);
if (*paths == NULL)
goto finish;
paths++;
}
}
for (i = 0; i < num_paths; i++) {
status = process_file(rec, rec->spec_files[i]);
if (status)
goto finish;
}
/* warn about duplicates after all files have been processed. */
status = nodups_specs(data);
if (status)
goto finish;
qsort(data->spec_arr, data->nspec, sizeof(struct spec), cmp);
digest_gen_hash(rec->digest);
finish:
if (status)
closef(rec);
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;
if (data->spec_arr) {
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);
}
free(data->spec_arr);
}
free(data);
}
static struct selabel_lookup_rec *property_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 struct selabel_lookup_rec *service_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 (strcmp(spec_arr[i].property_key, key) == 0)
break;
if (strcmp(spec_arr[i].property_key, "*") == 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 *)calloc(1, sizeof(*data));
if (!data)
return -1;
rec->data = data;
rec->func_close = &closef;
rec->func_stats = &stats;
rec->func_lookup = &property_lookup;
return init(rec, opts, nopts);
}
int selabel_service_init(struct selabel_handle *rec,
const struct selinux_opt *opts, unsigned nopts)
{
struct saved_data *data;
data = (struct saved_data *)calloc(1, sizeof(*data));
if (!data)
return -1;
rec->data = data;
rec->func_close = &closef;
rec->func_stats = &stats;
rec->func_lookup = &service_lookup;
return init(rec, opts, nopts);
}