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/context.h>
11 #include <selinux/selinux.h>
12 #include <selinux/label.h>
13 #include <sys/stat.h>
14 #include <sys/types.h>
15 
16 static const char * const CHECK_FC_ASSERT_ATTRS[] = { "fs_type", "dev_type", "file_type", NULL };
17 static const char * const CHECK_PC_ASSERT_ATTRS[] = { "property_type", NULL };
18 static const char * const CHECK_SC_ASSERT_ATTRS[] = { "service_manager_type", NULL };
19 static const char * const CHECK_HW_SC_ASSERT_ATTRS[] = { "hwservice_manager_type", NULL };
20 static const char * const CHECK_VND_SC_ASSERT_ATTRS[] = { "vndservice_manager_type", NULL };
21 
22 typedef enum filemode filemode;
23 enum filemode {
24     filemode_file_contexts = 0,
25     filemode_property_contexts,
26     filemode_service_contexts,
27     filemode_hw_service_contexts,
28     filemode_vendor_service_contexts
29 };
30 
31 static struct {
32     /* policy */
33     struct {
34         union {
35             /* Union these so we don't have to cast */
36             sepol_policydb_t *sdb;
37             policydb_t *pdb;
38         };
39         sepol_policy_file_t *pf;
40         sepol_handle_t *handle;
41         FILE *file;
42 #define SEHANDLE_CNT 2
43         struct selabel_handle *sehnd[SEHANDLE_CNT];
44     } sepolicy;
45 
46     /* assertions */
47     struct {
48         const char * const *attrs; /* for the original set to print on error */
49         ebitmap_t set;             /* the ebitmap representation of the attrs */
50     } assert;
51 
52 } global_state;
53 
filemode_to_assert_attrs(filemode mode)54 static const char * const *filemode_to_assert_attrs(filemode mode)
55 {
56     switch (mode) {
57     case filemode_file_contexts:
58         return CHECK_FC_ASSERT_ATTRS;
59     case filemode_property_contexts:
60         return CHECK_PC_ASSERT_ATTRS;
61     case filemode_service_contexts:
62         return CHECK_SC_ASSERT_ATTRS;
63     case filemode_hw_service_contexts:
64         return CHECK_HW_SC_ASSERT_ATTRS;
65     case filemode_vendor_service_contexts:
66         return CHECK_VND_SC_ASSERT_ATTRS;
67     }
68     /* die on invalid parameters */
69     fprintf(stderr, "Error: Invalid mode of operation: %d\n", mode);
70     exit(1);
71 }
72 
get_attr_bit(policydb_t * policydb,const char * attr_name)73 static int get_attr_bit(policydb_t *policydb, const char *attr_name)
74 {
75     struct type_datum *attr = hashtab_search(policydb->p_types.table, (char *)attr_name);
76     if (!attr) {
77         fprintf(stderr, "Error: \"%s\" is not defined in this policy.\n", attr_name);
78         return -1;
79     }
80 
81     if (attr->flavor != TYPE_ATTRIB) {
82         fprintf(stderr, "Error: \"%s\" is not an attribute in this policy.\n", attr_name);
83         return -1;
84     }
85 
86     return attr->s.value - 1;
87 }
88 
ebitmap_attribute_assertion_init(ebitmap_t * assertions,const char * const attributes[])89 static bool ebitmap_attribute_assertion_init(ebitmap_t *assertions, const char * const attributes[])
90 {
91 
92     while (*attributes) {
93 
94         int bit_pos = get_attr_bit(global_state.sepolicy.pdb, *attributes);
95         if (bit_pos < 0) {
96             /* get_attr_bit() logs error */
97             return false;
98         }
99 
100         int err = ebitmap_set_bit(assertions, bit_pos, 1);
101         if (err) {
102             fprintf(stderr, "Error: setting bit on assertion ebitmap!\n");
103             return false;
104         }
105         attributes++;
106     }
107     return true;
108 }
109 
is_type_of_attribute_set(policydb_t * policydb,const char * type_name,ebitmap_t * attr_set)110 static bool is_type_of_attribute_set(policydb_t *policydb, const char *type_name,
111         ebitmap_t *attr_set)
112 {
113     struct type_datum *type = hashtab_search(policydb->p_types.table, (char *)type_name);
114     if (!type) {
115         fprintf(stderr, "Error: \"%s\" is not defined in this policy.\n", type_name);
116         return false;
117     }
118 
119     if (type->flavor != TYPE_TYPE) {
120         fprintf(stderr, "Error: \"%s\" is not a type in this policy.\n", type_name);
121         return false;
122     }
123 
124     ebitmap_t dst;
125     ebitmap_init(&dst);
126 
127     /* Take the intersection, if the set is empty, then its a failure */
128     int rc = ebitmap_and(&dst, attr_set, &policydb->type_attr_map[type->s.value - 1]);
129     if (rc) {
130         fprintf(stderr, "Error: Could not perform ebitmap_and: %d\n", rc);
131         exit(1);
132     }
133 
134     bool res = (bool)ebitmap_length(&dst);
135 
136     ebitmap_destroy(&dst);
137     return res;
138 }
139 
dump_char_array(FILE * stream,const char * const * strings)140 static void dump_char_array(FILE *stream, const char * const *strings)
141 {
142 
143     const char * const *p = strings;
144 
145     fprintf(stream, "\"");
146 
147     while (*p) {
148         const char *s = *p++;
149         const char *fmt = *p ? "%s, " : "%s\"";
150         fprintf(stream, fmt, s);
151     }
152 }
153 
validate(char ** contextp)154 static int validate(char **contextp)
155 {
156     bool res;
157     char *context = *contextp;
158 
159     sepol_context_t *ctx;
160     int rc = sepol_context_from_string(global_state.sepolicy.handle, context,
161             &ctx);
162     if (rc < 0) {
163         fprintf(stderr, "Error: Could not allocate context from string");
164         exit(1);
165     }
166 
167     rc = sepol_context_check(global_state.sepolicy.handle,
168             global_state.sepolicy.sdb, ctx);
169     if (rc < 0) {
170         goto out;
171     }
172 
173     const char *type_name = sepol_context_get_type(ctx);
174 
175     // Temporarily exempt hal_power_stats_vendor_service from the check.
176     // TODO(b/211953546): remove this
177     if (strcmp(type_name, "hal_power_stats_vendor_service") == 0) {
178         goto out;
179     }
180 
181     uint32_t len = ebitmap_length(&global_state.assert.set);
182     if (len > 0) {
183         res = !is_type_of_attribute_set(global_state.sepolicy.pdb, type_name,
184                 &global_state.assert.set);
185         if (res) {
186             fprintf(stderr, "Error: type \"%s\" is not of set: ", type_name);
187             dump_char_array(stderr, global_state.assert.attrs);
188             fprintf(stderr, "\n");
189             /* The calls above did not affect rc, so set error before going to out */
190             rc = -1;
191             goto out;
192         }
193     }
194     /* Success: Although it should be 0, we explicitly set rc to 0 for clarity */
195     rc = 0;
196 
197  out:
198     sepol_context_free(ctx);
199     return rc;
200 }
201 
usage(char * name)202 static void usage(char *name) {
203     fprintf(stderr, "usage1:  %s [-l|-p|-s|-v] [-e] sepolicy context_file\n\n"
204         "Parses a context file and checks for syntax errors.\n"
205         "If -p is specified, the property backend is used.\n"
206         "If -s is specified, the service backend is used to verify binder services.\n"
207         "If -l is specified, the service backend is used to verify hwbinder services.\n"
208         "If -v is specified, the service backend is used to verify vndbinder services.\n"
209         "Otherwise, context_file is assumed to be a file_contexts file\n"
210         "If -e is specified, then the context_file is allowed to be empty.\n\n"
211 
212         "usage2:  %s -c file_contexts1 file_contexts2\n\n"
213         "Compares two file contexts files and reports one of \n"
214         "subset, equal, superset, or incomparable.\n\n"
215 
216         "usage3:  %s -t file_contexts test_data\n\n"
217         "Validates a file contexts file against test_data.\n"
218         "test_data is a text file where each line has the format:\n"
219         "  path expected_type\n\n\n",
220         name, name, name);
221     exit(1);
222 }
223 
cleanup(void)224 static void cleanup(void) {
225 
226     if (global_state.sepolicy.file) {
227         fclose(global_state.sepolicy.file);
228     }
229 
230     if (global_state.sepolicy.sdb) {
231         sepol_policydb_free(global_state.sepolicy.sdb);
232     }
233 
234     if (global_state.sepolicy.pf) {
235         sepol_policy_file_free(global_state.sepolicy.pf);
236     }
237 
238     if (global_state.sepolicy.handle) {
239         sepol_handle_destroy(global_state.sepolicy.handle);
240     }
241 
242     ebitmap_destroy(&global_state.assert.set);
243 
244     int i;
245     for (i = 0; i < SEHANDLE_CNT; i++) {
246         struct selabel_handle *sehnd = global_state.sepolicy.sehnd[i];
247         if (sehnd) {
248             selabel_close(sehnd);
249         }
250     }
251 }
252 
do_compare_and_die_on_error(struct selinux_opt opts[],unsigned int backend,char * paths[])253 static void do_compare_and_die_on_error(struct selinux_opt opts[], unsigned int backend, char *paths[])
254 {
255     enum selabel_cmp_result result;
256      char *result_str[] = { "subset", "equal", "superset", "incomparable" };
257      int i;
258 
259      opts[0].value = NULL; /* not validating against a policy when comparing */
260 
261      for (i = 0; i < SEHANDLE_CNT; i++) {
262          opts[1].value = paths[i];
263          global_state.sepolicy.sehnd[i] = selabel_open(backend, opts, 2);
264          if (!global_state.sepolicy.sehnd[i]) {
265              fprintf(stderr, "Error: could not load context file from %s\n", paths[i]);
266              exit(1);
267          }
268      }
269 
270      result = selabel_cmp(global_state.sepolicy.sehnd[0], global_state.sepolicy.sehnd[1]);
271      printf("%s\n", result_str[result]);
272 }
273 
274 static int warnings = 0;
log_callback(int type,const char * fmt,...)275 static int log_callback(int type, const char *fmt, ...) {
276     va_list ap;
277 
278     if (type == SELINUX_WARNING) {
279         warnings += 1;
280     }
281     va_start(ap, fmt);
282     vfprintf(stderr, fmt, ap);
283     va_end(ap);
284     return 0;
285 }
286 
do_test_data_and_die_on_error(struct selinux_opt opts[],unsigned int backend,char * paths[])287 static void do_test_data_and_die_on_error(struct selinux_opt opts[], unsigned int backend,
288         char *paths[])
289 {
290     opts[0].value = NULL; /* not validating against a policy */
291     opts[1].value = paths[0];
292     global_state.sepolicy.sehnd[0] = selabel_open(backend, opts, 2);
293     if (!global_state.sepolicy.sehnd[0]) {
294         fprintf(stderr, "Error: could not load context file from %s: %s\n",
295                 paths[0], strerror(errno));
296         exit(1);
297     }
298 
299     FILE* test_data = fopen(paths[1], "r");
300     if (test_data == NULL) {
301         fprintf(stderr, "Error: could not load test file from %s : %s\n",
302                 paths[1], strerror(errno));
303         exit(1);
304     }
305 
306     char line[1024];
307     while (fgets(line, sizeof(line), test_data)) {
308         char *path;
309         char *expected_type;
310 
311         if (!strcmp(line, "\n") || line[0] == '#') {
312             continue;
313         }
314 
315         int ret = sscanf(line, "%ms %ms", &path, &expected_type);
316         if (ret != 2) {
317             fprintf(stderr, "Error: unable to parse the line %s\n", line);
318             exit(1);
319         }
320 
321         char *found_context;
322         ret = selabel_lookup(global_state.sepolicy.sehnd[0], &found_context, path, 0);
323         if (ret != 0) {
324             fprintf(stderr, "Error: unable to lookup the path for %s\n", line);
325             exit(1);
326         }
327 
328         context_t found = context_new(found_context);
329         const char *found_type = context_type_get(found);
330 
331         if (strcmp(found_type, expected_type)) {
332             fprintf(stderr, "Incorrect type for %s: resolved to %s, expected %s\n",
333                     path, found_type, expected_type);
334         }
335 
336         free(found_context);
337         context_free(found);
338         free(path);
339         free(expected_type);
340     }
341     fclose(test_data);
342 
343     // Prints the coverage of file_contexts on the test data. It includes
344     // warnings for rules that have not been hit by any test example.
345     union selinux_callback cb;
346     cb.func_log = log_callback;
347     selinux_set_callback(SELINUX_CB_LOG, cb);
348     selabel_stats(global_state.sepolicy.sehnd[0]);
349     if (warnings) {
350         fprintf(stderr, "No test entries were found for the contexts above. " \
351                         "You may need to update %s.\n", paths[1]);
352         exit(1);
353     }
354 }
355 
do_fc_check_and_die_on_error(struct selinux_opt opts[],unsigned int backend,filemode mode,const char * sepolicy_file,const char * context_file,bool allow_empty)356 static void do_fc_check_and_die_on_error(struct selinux_opt opts[], unsigned int backend, filemode mode,
357         const char *sepolicy_file, const char *context_file, bool allow_empty)
358 {
359     struct stat sb;
360     if (stat(context_file, &sb) < 0) {
361         perror("Error: could not get stat on file contexts file");
362         exit(1);
363     }
364 
365     if (sb.st_size == 0) {
366         /* Nothing to check on empty file_contexts file if allowed*/
367         if (allow_empty) {
368             return;
369         }
370         /* else: We could throw the error here, but libselinux backend will catch it */
371     }
372 
373     global_state.sepolicy.file = fopen(sepolicy_file, "r");
374     if (!global_state.sepolicy.file) {
375       perror("Error: could not open policy file");
376       exit(1);
377     }
378 
379     global_state.sepolicy.handle = sepol_handle_create();
380     if (!global_state.sepolicy.handle) {
381         fprintf(stderr, "Error: could not create policy handle: %s\n", strerror(errno));
382         exit(1);
383     }
384 
385     if (sepol_policy_file_create(&global_state.sepolicy.pf) < 0) {
386       perror("Error: could not create policy handle");
387       exit(1);
388     }
389 
390     sepol_policy_file_set_fp(global_state.sepolicy.pf, global_state.sepolicy.file);
391     sepol_policy_file_set_handle(global_state.sepolicy.pf, global_state.sepolicy.handle);
392 
393     int rc = sepol_policydb_create(&global_state.sepolicy.sdb);
394     if (rc < 0) {
395       perror("Error: could not create policy db");
396       exit(1);
397     }
398 
399     rc = sepol_policydb_read(global_state.sepolicy.sdb, global_state.sepolicy.pf);
400     if (rc < 0) {
401       perror("Error: could not read file into policy db");
402       exit(1);
403     }
404 
405     global_state.assert.attrs = filemode_to_assert_attrs(mode);
406 
407     bool ret = ebitmap_attribute_assertion_init(&global_state.assert.set, global_state.assert.attrs);
408     if (!ret) {
409         /* error messages logged by ebitmap_attribute_assertion_init() */
410         exit(1);
411     }
412 
413     selinux_set_callback(SELINUX_CB_VALIDATE,
414                          (union selinux_callback)&validate);
415 
416     opts[1].value = context_file;
417 
418     global_state.sepolicy.sehnd[0] = selabel_open(backend, opts, 2);
419     if (!global_state.sepolicy.sehnd[0]) {
420       fprintf(stderr, "Error: could not load context file from %s\n", context_file);
421       exit(1);
422     }
423 }
424 
main(int argc,char ** argv)425 int main(int argc, char **argv)
426 {
427   struct selinux_opt opts[] = {
428     { SELABEL_OPT_VALIDATE, (void*)1 },
429     { SELABEL_OPT_PATH, NULL }
430   };
431 
432   // Default backend unless changed by input argument.
433   unsigned int backend = SELABEL_CTX_FILE;
434 
435   bool allow_empty = false;
436   bool compare = false;
437   bool test_data = false;
438   char c;
439 
440   filemode mode = filemode_file_contexts;
441 
442   while ((c = getopt(argc, argv, "clpsvet")) != -1) {
443     switch (c) {
444       case 'c':
445         compare = true;
446         break;
447       case 'e':
448         allow_empty = true;
449         break;
450       case 'p':
451         mode = filemode_property_contexts;
452         backend = SELABEL_CTX_ANDROID_PROP;
453         break;
454       case 's':
455         mode = filemode_service_contexts;
456         backend = SELABEL_CTX_ANDROID_SERVICE;
457         break;
458       case 'l':
459         mode = filemode_hw_service_contexts;
460         backend = SELABEL_CTX_ANDROID_SERVICE;
461         break;
462       case 'v':
463         mode = filemode_vendor_service_contexts;
464         backend = SELABEL_CTX_ANDROID_SERVICE;
465         break;
466       case 't':
467         test_data = true;
468         break;
469       case 'h':
470       default:
471         usage(argv[0]);
472         break;
473     }
474   }
475 
476   int index = optind;
477   if (argc - optind != 2) {
478     usage(argv[0]);
479   }
480 
481   if ((compare || test_data) && backend != SELABEL_CTX_FILE) {
482     usage(argv[0]);
483   }
484 
485   atexit(cleanup);
486 
487   if (compare) {
488       do_compare_and_die_on_error(opts, backend, &(argv[index]));
489   } else if (test_data) {
490       do_test_data_and_die_on_error(opts, backend, &(argv[index]));
491   } else {
492       /* remaining args are sepolicy file and context file  */
493       char *sepolicy_file = argv[index];
494       char *context_file = argv[index + 1];
495 
496       do_fc_check_and_die_on_error(opts, backend, mode, sepolicy_file, context_file, allow_empty);
497   }
498   exit(0);
499 }
500