xref: /aosp_15_r20/system/sepolicy/tools/checkfc.c (revision e4a36f4174b17bbab9dc043f4a65dc8d87377290)
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     bool non_matching_entries = false;
308     while (fgets(line, sizeof(line), test_data)) {
309         char *path;
310         char *expected_type;
311 
312         if (!strcmp(line, "\n") || line[0] == '#') {
313             continue;
314         }
315 
316         int ret = sscanf(line, "%ms %ms", &path, &expected_type);
317         if (ret != 2) {
318             fprintf(stderr, "Error: unable to parse the line %s\n", line);
319             exit(1);
320         }
321 
322         char *found_context;
323         ret = selabel_lookup(global_state.sepolicy.sehnd[0], &found_context, path, 0);
324         if (ret != 0) {
325             fprintf(stderr, "Error: unable to lookup the path for %s\n", line);
326             exit(1);
327         }
328 
329         context_t found = context_new(found_context);
330         const char *found_type = context_type_get(found);
331 
332         if (strcmp(found_type, expected_type)) {
333             fprintf(stderr, "Incorrect type for %s: resolved to %s, expected %s\n",
334                     path, found_type, expected_type);
335             non_matching_entries = true;
336         }
337 
338         free(found_context);
339         context_free(found);
340         free(path);
341         free(expected_type);
342     }
343     fclose(test_data);
344 
345     if (non_matching_entries) {
346         exit(1);
347     }
348 
349     // Prints the coverage of file_contexts on the test data. It includes
350     // warnings for rules that have not been hit by any test example.
351     union selinux_callback cb;
352     cb.func_log = log_callback;
353     selinux_set_callback(SELINUX_CB_LOG, cb);
354     selabel_stats(global_state.sepolicy.sehnd[0]);
355     if (warnings) {
356         fprintf(stderr, "No test entries were found for the contexts above. " \
357                         "You may need to update %s.\n", paths[1]);
358         exit(1);
359     }
360 }
361 
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)362 static void do_fc_check_and_die_on_error(struct selinux_opt opts[], unsigned int backend, filemode mode,
363         const char *sepolicy_file, const char *context_file, bool allow_empty)
364 {
365     struct stat sb;
366     if (stat(context_file, &sb) < 0) {
367         perror("Error: could not get stat on file contexts file");
368         exit(1);
369     }
370 
371     if (sb.st_size == 0) {
372         /* Nothing to check on empty file_contexts file if allowed*/
373         if (allow_empty) {
374             return;
375         }
376         /* else: We could throw the error here, but libselinux backend will catch it */
377     }
378 
379     global_state.sepolicy.file = fopen(sepolicy_file, "r");
380     if (!global_state.sepolicy.file) {
381       perror("Error: could not open policy file");
382       exit(1);
383     }
384 
385     global_state.sepolicy.handle = sepol_handle_create();
386     if (!global_state.sepolicy.handle) {
387         fprintf(stderr, "Error: could not create policy handle: %s\n", strerror(errno));
388         exit(1);
389     }
390 
391     if (sepol_policy_file_create(&global_state.sepolicy.pf) < 0) {
392       perror("Error: could not create policy handle");
393       exit(1);
394     }
395 
396     sepol_policy_file_set_fp(global_state.sepolicy.pf, global_state.sepolicy.file);
397     sepol_policy_file_set_handle(global_state.sepolicy.pf, global_state.sepolicy.handle);
398 
399     int rc = sepol_policydb_create(&global_state.sepolicy.sdb);
400     if (rc < 0) {
401       perror("Error: could not create policy db");
402       exit(1);
403     }
404 
405     rc = sepol_policydb_read(global_state.sepolicy.sdb, global_state.sepolicy.pf);
406     if (rc < 0) {
407       perror("Error: could not read file into policy db");
408       exit(1);
409     }
410 
411     global_state.assert.attrs = filemode_to_assert_attrs(mode);
412 
413     bool ret = ebitmap_attribute_assertion_init(&global_state.assert.set, global_state.assert.attrs);
414     if (!ret) {
415         /* error messages logged by ebitmap_attribute_assertion_init() */
416         exit(1);
417     }
418 
419     selinux_set_callback(SELINUX_CB_VALIDATE,
420                          (union selinux_callback)&validate);
421 
422     opts[1].value = context_file;
423 
424     global_state.sepolicy.sehnd[0] = selabel_open(backend, opts, 2);
425     if (!global_state.sepolicy.sehnd[0]) {
426       fprintf(stderr, "Error: could not load context file from %s\n", context_file);
427       exit(1);
428     }
429 }
430 
main(int argc,char ** argv)431 int main(int argc, char **argv)
432 {
433   struct selinux_opt opts[] = {
434     { SELABEL_OPT_VALIDATE, (void*)1 },
435     { SELABEL_OPT_PATH, NULL }
436   };
437 
438   // Default backend unless changed by input argument.
439   unsigned int backend = SELABEL_CTX_FILE;
440 
441   bool allow_empty = false;
442   bool compare = false;
443   bool test_data = false;
444   char c;
445 
446   filemode mode = filemode_file_contexts;
447 
448   while ((c = getopt(argc, argv, "clpsvet")) != -1) {
449     switch (c) {
450       case 'c':
451         compare = true;
452         break;
453       case 'e':
454         allow_empty = true;
455         break;
456       case 'p':
457         mode = filemode_property_contexts;
458         backend = SELABEL_CTX_ANDROID_PROP;
459         break;
460       case 's':
461         mode = filemode_service_contexts;
462         backend = SELABEL_CTX_ANDROID_SERVICE;
463         break;
464       case 'l':
465         mode = filemode_hw_service_contexts;
466         backend = SELABEL_CTX_ANDROID_SERVICE;
467         break;
468       case 'v':
469         mode = filemode_vendor_service_contexts;
470         backend = SELABEL_CTX_ANDROID_SERVICE;
471         break;
472       case 't':
473         test_data = true;
474         break;
475       case 'h':
476       default:
477         usage(argv[0]);
478         break;
479     }
480   }
481 
482   int index = optind;
483   if (argc - optind != 2) {
484     usage(argv[0]);
485   }
486 
487   if ((compare || test_data) && backend != SELABEL_CTX_FILE) {
488     usage(argv[0]);
489   }
490 
491   atexit(cleanup);
492 
493   if (compare) {
494       do_compare_and_die_on_error(opts, backend, &(argv[index]));
495   } else if (test_data) {
496       do_test_data_and_die_on_error(opts, backend, &(argv[index]));
497   } else {
498       /* remaining args are sepolicy file and context file  */
499       char *sepolicy_file = argv[index];
500       char *context_file = argv[index + 1];
501 
502       do_fc_check_and_die_on_error(opts, backend, mode, sepolicy_file, context_file, allow_empty);
503   }
504   exit(0);
505 }
506