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