/* * 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, len; char buf1[BUFSIZ], buf2[BUFSIZ]; char *buf_p, *prop = buf1, *context = buf2; 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", prop, context); if (items != 2) { selinux_log(SELINUX_WARNING, "%s: line %u is missing fields, skipping\n", path, lineno); return 0; } if (pass == 1) { /* On the second pass, process and store the specification in spec. */ spec_arr[nspec].property_key = strdup(prop); if (!spec_arr[nspec].property_key) { selinux_log(SELINUX_WARNING, "%s: out of memory at line %u on prop %s\n", path, lineno, prop); return -1; } spec_arr[nspec].lr.ctx_raw = strdup(context); if (!spec_arr[nspec].lr.ctx_raw) { selinux_log(SELINUX_WARNING, "%s: out of memory at line %u on context %s\n", path, lineno, context); return -1; } if (rec->validating) { if (selabel_validate(rec, &spec_arr[nspec].lr) < 0) { selinux_log(SELINUX_WARNING, "%s: line %u has invalid context %s\n", path, lineno, spec_arr[nspec].lr.ctx_raw); } } } 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; FILE *fp; char line_buf[BUFSIZ]; unsigned int lineno = 0, 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; 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, 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); }