/* * 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); }