1#include <getopt.h>
2#include <stdbool.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <unistd.h>
7#include <sepol/module.h>
8#include <sepol/policydb/policydb.h>
9#include <sepol/sepol.h>
10#include <selinux/selinux.h>
11#include <selinux/label.h>
12#include <sys/stat.h>
13#include <sys/types.h>
14
15static const char * const CHECK_FC_ASSERT_ATTRS[] = { "fs_type", "dev_type", "file_type", NULL };
16static const char * const CHECK_PC_ASSERT_ATTRS[] = { "property_type", NULL };
17static const char * const CHECK_SC_ASSERT_ATTRS[] = { "service_manager_type", NULL };
18static const char * const CHECK_HW_SC_ASSERT_ATTRS[] = { "hwservice_manager_type", NULL };
19static const char * const CHECK_VND_SC_ASSERT_ATTRS[] = { "vndservice_manager_type", NULL };
20
21typedef enum filemode filemode;
22enum filemode {
23    filemode_file_contexts = 0,
24    filemode_property_contexts,
25    filemode_service_contexts,
26    filemode_hw_service_contexts,
27    filemode_vendor_service_contexts
28};
29
30static struct {
31    /* policy */
32    struct {
33        union {
34            /* Union these so we don't have to cast */
35            sepol_policydb_t *sdb;
36            policydb_t *pdb;
37        };
38        sepol_policy_file_t *pf;
39        sepol_handle_t *handle;
40        FILE *file;
41#define SEHANDLE_CNT 2
42        struct selabel_handle *sehnd[SEHANDLE_CNT];
43    } sepolicy;
44
45    /* assertions */
46    struct {
47        const char * const *attrs; /* for the original set to print on error */
48        ebitmap_t set;             /* the ebitmap representation of the attrs */
49    } assert;
50
51} global_state;
52
53static const char * const *filemode_to_assert_attrs(filemode mode)
54{
55    switch (mode) {
56    case filemode_file_contexts:
57        return CHECK_FC_ASSERT_ATTRS;
58    case filemode_property_contexts:
59        return CHECK_PC_ASSERT_ATTRS;
60    case filemode_service_contexts:
61        return CHECK_SC_ASSERT_ATTRS;
62    case filemode_hw_service_contexts:
63        return CHECK_HW_SC_ASSERT_ATTRS;
64    case filemode_vendor_service_contexts:
65        return CHECK_VND_SC_ASSERT_ATTRS;
66    }
67    /* die on invalid parameters */
68    fprintf(stderr, "Error: Invalid mode of operation: %d\n", mode);
69    exit(1);
70}
71
72static int get_attr_bit(policydb_t *policydb, const char *attr_name)
73{
74    struct type_datum *attr = hashtab_search(policydb->p_types.table, (char *)attr_name);
75    if (!attr) {
76        fprintf(stderr, "Error: \"%s\" is not defined in this policy.\n", attr_name);
77        return -1;
78    }
79
80    if (attr->flavor != TYPE_ATTRIB) {
81        fprintf(stderr, "Error: \"%s\" is not an attribute in this policy.\n", attr_name);
82        return -1;
83    }
84
85    return attr->s.value - 1;
86}
87
88static bool ebitmap_attribute_assertion_init(ebitmap_t *assertions, const char * const attributes[])
89{
90
91    while (*attributes) {
92
93        int bit_pos = get_attr_bit(global_state.sepolicy.pdb, *attributes);
94        if (bit_pos < 0) {
95            /* get_attr_bit() logs error */
96            return false;
97        }
98
99        int err = ebitmap_set_bit(assertions, bit_pos, 1);
100        if (err) {
101            fprintf(stderr, "Error: setting bit on assertion ebitmap!\n");
102            return false;
103        }
104        attributes++;
105    }
106    return true;
107}
108
109static bool is_type_of_attribute_set(policydb_t *policydb, const char *type_name,
110        ebitmap_t *attr_set)
111{
112    struct type_datum *type = hashtab_search(policydb->p_types.table, (char *)type_name);
113    if (!type) {
114        fprintf(stderr, "Error: \"%s\" is not defined in this policy.\n", type_name);
115        return false;
116    }
117
118    if (type->flavor != TYPE_TYPE) {
119        fprintf(stderr, "Error: \"%s\" is not a type in this policy.\n", type_name);
120        return false;
121    }
122
123    ebitmap_t dst;
124    ebitmap_init(&dst);
125
126    /* Take the intersection, if the set is empty, then its a failure */
127    int rc = ebitmap_and(&dst, attr_set, &policydb->type_attr_map[type->s.value - 1]);
128    if (rc) {
129        fprintf(stderr, "Error: Could not perform ebitmap_and: %d\n", rc);
130        exit(1);
131    }
132
133    bool res = (bool)ebitmap_length(&dst);
134
135    ebitmap_destroy(&dst);
136    return res;
137}
138
139static void dump_char_array(FILE *stream, const char * const *strings)
140{
141
142    const char * const *p = strings;
143
144    fprintf(stream, "\"");
145
146    while (*p) {
147        const char *s = *p++;
148        const char *fmt = *p ? "%s, " : "%s\"";
149        fprintf(stream, fmt, s);
150    }
151}
152
153static int validate(char **contextp)
154{
155    bool res;
156    char *context = *contextp;
157
158    sepol_context_t *ctx;
159    int rc = sepol_context_from_string(global_state.sepolicy.handle, context,
160            &ctx);
161    if (rc < 0) {
162        fprintf(stderr, "Error: Could not allocate context from string");
163        exit(1);
164    }
165
166    rc = sepol_context_check(global_state.sepolicy.handle,
167            global_state.sepolicy.sdb, ctx);
168    if (rc < 0) {
169        goto out;
170    }
171
172    const char *type_name = sepol_context_get_type(ctx);
173
174    uint32_t len = ebitmap_length(&global_state.assert.set);
175    if (len > 0) {
176        res = !is_type_of_attribute_set(global_state.sepolicy.pdb, type_name,
177                &global_state.assert.set);
178        if (res) {
179            fprintf(stderr, "Error: type \"%s\" is not of set: ", type_name);
180            dump_char_array(stderr, global_state.assert.attrs);
181            fprintf(stderr, "\n");
182            /* The calls above did not affect rc, so set error before going to out */
183            rc = -1;
184            goto out;
185        }
186    }
187    /* Success: Although it should be 0, we explicitly set rc to 0 for clarity */
188    rc = 0;
189
190 out:
191    sepol_context_free(ctx);
192    return rc;
193}
194
195static void usage(char *name) {
196    fprintf(stderr, "usage1:  %s [-l|-p|-s|-v] [-e] sepolicy context_file\n\n"
197        "Parses a context file and checks for syntax errors.\n"
198        "If -p is specified, the property backend is used.\n"
199        "If -s is specified, the service backend is used to verify binder services.\n"
200        "If -l is specified, the service backend is used to verify hwbinder services.\n"
201        "If -v is specified, the service backend is used to verify vndbinder services.\n"
202        "Otherwise, context_file is assumed to be a file_contexts file\n"
203        "If -e is specified, then the context_file is allowed to be empty.\n\n"
204
205        "usage2:  %s -c file_contexts1 file_contexts2\n\n"
206        "Compares two file contexts files and reports one of subset, equal, superset, or incomparable.\n\n",
207        name, name);
208    exit(1);
209}
210
211static void cleanup(void) {
212
213    if (global_state.sepolicy.file) {
214        fclose(global_state.sepolicy.file);
215    }
216
217    if (global_state.sepolicy.sdb) {
218        sepol_policydb_free(global_state.sepolicy.sdb);
219    }
220
221    if (global_state.sepolicy.pf) {
222        sepol_policy_file_free(global_state.sepolicy.pf);
223    }
224
225    if (global_state.sepolicy.handle) {
226        sepol_handle_destroy(global_state.sepolicy.handle);
227    }
228
229    ebitmap_destroy(&global_state.assert.set);
230
231    int i;
232    for (i = 0; i < SEHANDLE_CNT; i++) {
233        struct selabel_handle *sehnd = global_state.sepolicy.sehnd[i];
234        if (sehnd) {
235            selabel_close(sehnd);
236        }
237    }
238}
239
240static void do_compare_and_die_on_error(struct selinux_opt opts[], unsigned int backend, char *paths[])
241{
242    enum selabel_cmp_result result;
243     char *result_str[] = { "subset", "equal", "superset", "incomparable" };
244     int i;
245
246     opts[0].value = NULL; /* not validating against a policy when comparing */
247
248     for (i = 0; i < SEHANDLE_CNT; i++) {
249         opts[1].value = paths[i];
250         global_state.sepolicy.sehnd[i] = selabel_open(backend, opts, 2);
251         if (!global_state.sepolicy.sehnd[i]) {
252             fprintf(stderr, "Error: could not load context file from %s\n", paths[i]);
253             exit(1);
254         }
255     }
256
257     result = selabel_cmp(global_state.sepolicy.sehnd[0], global_state.sepolicy.sehnd[1]);
258     printf("%s\n", result_str[result]);
259}
260
261static void do_fc_check_and_die_on_error(struct selinux_opt opts[], unsigned int backend, filemode mode,
262        const char *sepolicy_file, const char *context_file, bool allow_empty)
263{
264    struct stat sb;
265    if (stat(context_file, &sb) < 0) {
266        perror("Error: could not get stat on file contexts file");
267        exit(1);
268    }
269
270    if (sb.st_size == 0) {
271        /* Nothing to check on empty file_contexts file if allowed*/
272        if (allow_empty) {
273            return;
274        }
275        /* else: We could throw the error here, but libselinux backend will catch it */
276    }
277
278    global_state.sepolicy.file = fopen(sepolicy_file, "r");
279    if (!global_state.sepolicy.file) {
280      perror("Error: could not open policy file");
281      exit(1);
282    }
283
284    global_state.sepolicy.handle = sepol_handle_create();
285    if (!global_state.sepolicy.handle) {
286        fprintf(stderr, "Error: could not create policy handle: %s\n", strerror(errno));
287        exit(1);
288    }
289
290    if (sepol_policy_file_create(&global_state.sepolicy.pf) < 0) {
291      perror("Error: could not create policy handle");
292      exit(1);
293    }
294
295    sepol_policy_file_set_fp(global_state.sepolicy.pf, global_state.sepolicy.file);
296    sepol_policy_file_set_handle(global_state.sepolicy.pf, global_state.sepolicy.handle);
297
298    int rc = sepol_policydb_create(&global_state.sepolicy.sdb);
299    if (rc < 0) {
300      perror("Error: could not create policy db");
301      exit(1);
302    }
303
304    rc = sepol_policydb_read(global_state.sepolicy.sdb, global_state.sepolicy.pf);
305    if (rc < 0) {
306      perror("Error: could not read file into policy db");
307      exit(1);
308    }
309
310    global_state.assert.attrs = filemode_to_assert_attrs(mode);
311
312    bool ret = ebitmap_attribute_assertion_init(&global_state.assert.set, global_state.assert.attrs);
313    if (!ret) {
314        /* error messages logged by ebitmap_attribute_assertion_init() */
315        exit(1);
316    }
317
318    selinux_set_callback(SELINUX_CB_VALIDATE,
319                         (union selinux_callback)&validate);
320
321    opts[1].value = context_file;
322
323    global_state.sepolicy.sehnd[0] = selabel_open(backend, opts, 2);
324    if (!global_state.sepolicy.sehnd[0]) {
325      fprintf(stderr, "Error: could not load context file from %s\n", context_file);
326      exit(1);
327    }
328}
329
330int main(int argc, char **argv)
331{
332  struct selinux_opt opts[] = {
333    { SELABEL_OPT_VALIDATE, (void*)1 },
334    { SELABEL_OPT_PATH, NULL }
335  };
336
337  // Default backend unless changed by input argument.
338  unsigned int backend = SELABEL_CTX_FILE;
339
340  bool allow_empty = false;
341  bool compare = false;
342  char c;
343
344  filemode mode = filemode_file_contexts;
345
346  while ((c = getopt(argc, argv, "clpsve")) != -1) {
347    switch (c) {
348      case 'c':
349        compare = true;
350        break;
351      case 'e':
352        allow_empty = true;
353        break;
354      case 'p':
355        mode = filemode_property_contexts;
356        backend = SELABEL_CTX_ANDROID_PROP;
357        break;
358      case 's':
359        mode = filemode_service_contexts;
360        backend = SELABEL_CTX_ANDROID_SERVICE;
361        break;
362      case 'l':
363        mode = filemode_hw_service_contexts;
364        backend = SELABEL_CTX_ANDROID_SERVICE;
365        break;
366      case 'v':
367        mode = filemode_vendor_service_contexts;
368        backend = SELABEL_CTX_ANDROID_SERVICE;
369        break;
370      case 'h':
371      default:
372        usage(argv[0]);
373        break;
374    }
375  }
376
377  int index = optind;
378  if (argc - optind != 2) {
379    usage(argv[0]);
380  }
381
382  if (compare && backend != SELABEL_CTX_FILE) {
383    usage(argv[0]);
384  }
385
386  atexit(cleanup);
387
388  if (compare) {
389      do_compare_and_die_on_error(opts, backend, &(argv[index]));
390  } else {
391      /* remaining args are sepolicy file and context file  */
392      char *sepolicy_file = argv[index];
393      char *context_file = argv[index + 1];
394
395      do_fc_check_and_die_on_error(opts, backend, mode, sepolicy_file, context_file, allow_empty);
396  }
397  exit(0);
398}
399