blob: 05826f9f922068edcbad2f3b7ee26bffe2acb107 [file] [log] [blame]
Robert Craigd98d26e2013-01-23 14:04:50 -05001#include <getopt.h>
William Robertsad3cb392015-09-24 18:10:54 -07002#include <stdbool.h>
Stephen Smalley01a58af2012-10-02 12:46:37 -04003#include <stdio.h>
4#include <stdlib.h>
William Robertsad3cb392015-09-24 18:10:54 -07005#include <string.h>
William Roberts922b4e92016-01-14 17:06:37 -08006#include <unistd.h>
William Robertsad3cb392015-09-24 18:10:54 -07007#include <sepol/module.h>
8#include <sepol/policydb/policydb.h>
Stephen Smalley01a58af2012-10-02 12:46:37 -04009#include <sepol/sepol.h>
Thiébaud Weksteen9a924ba2023-10-05 09:44:24 +110010#include <selinux/context.h>
Stephen Smalley01a58af2012-10-02 12:46:37 -040011#include <selinux/selinux.h>
12#include <selinux/label.h>
William Roberts922b4e92016-01-14 17:06:37 -080013#include <sys/stat.h>
14#include <sys/types.h>
Stephen Smalley01a58af2012-10-02 12:46:37 -040015
William Robertsad3cb392015-09-24 18:10:54 -070016static const char * const CHECK_FC_ASSERT_ATTRS[] = { "fs_type", "dev_type", "file_type", NULL };
17static const char * const CHECK_PC_ASSERT_ATTRS[] = { "property_type", NULL };
18static const char * const CHECK_SC_ASSERT_ATTRS[] = { "service_manager_type", NULL };
Martijn Coenend48d54a2017-04-06 13:22:44 -070019static const char * const CHECK_HW_SC_ASSERT_ATTRS[] = { "hwservice_manager_type", NULL };
20static const char * const CHECK_VND_SC_ASSERT_ATTRS[] = { "vndservice_manager_type", NULL };
William Robertsad3cb392015-09-24 18:10:54 -070021
22typedef enum filemode filemode;
23enum filemode {
24 filemode_file_contexts = 0,
25 filemode_property_contexts,
Martijn Coenend48d54a2017-04-06 13:22:44 -070026 filemode_service_contexts,
27 filemode_hw_service_contexts,
28 filemode_vendor_service_contexts
William Robertsad3cb392015-09-24 18:10:54 -070029};
30
31static 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
54static 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;
Martijn Coenend48d54a2017-04-06 13:22:44 -070063 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;
William Robertsad3cb392015-09-24 18:10:54 -070067 }
68 /* die on invalid parameters */
69 fprintf(stderr, "Error: Invalid mode of operation: %d\n", mode);
70 exit(1);
71}
72
73static 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
89static 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
110static 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
140static 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}
Stephen Smalley01a58af2012-10-02 12:46:37 -0400153
154static int validate(char **contextp)
155{
William Robertsad3cb392015-09-24 18:10:54 -0700156 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
Inseob Kimb5e23532022-02-16 02:26:11 +0000175 // 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
William Robertsad3cb392015-09-24 18:10:54 -0700181 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;
Stephen Smalley01a58af2012-10-02 12:46:37 -0400200}
201
Robert Craigd98d26e2013-01-23 14:04:50 -0500202static void usage(char *name) {
Martijn Coenend48d54a2017-04-06 13:22:44 -0700203 fprintf(stderr, "usage1: %s [-l|-p|-s|-v] [-e] sepolicy context_file\n\n"
William Robertsad3cb392015-09-24 18:10:54 -0700204 "Parses a context file and checks for syntax errors.\n"
Martijn Coenend48d54a2017-04-06 13:22:44 -0700205 "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"
William Roberts922b4e92016-01-14 17:06:37 -0800210 "If -e is specified, then the context_file is allowed to be empty.\n\n"
Stephen Smalley13b6b7e2015-08-05 12:43:15 -0400211
William Robertsad3cb392015-09-24 18:10:54 -0700212 "usage2: %s -c file_contexts1 file_contexts2\n\n"
Thiébaud Weksteen9a924ba2023-10-05 09:44:24 +1100213 "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);
Robert Craigd98d26e2013-01-23 14:04:50 -0500221 exit(1);
222}
223
William Robertsad3cb392015-09-24 18:10:54 -0700224static 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
253static 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
Thiébaud Weksteen9a924ba2023-10-05 09:44:24 +1100274static void do_test_data_and_die_on_error(struct selinux_opt opts[], unsigned int backend,
275 char *paths[])
276{
277 opts[0].value = NULL; /* not validating against a policy */
278 opts[1].value = paths[0];
279 global_state.sepolicy.sehnd[0] = selabel_open(backend, opts, 2);
280 if (!global_state.sepolicy.sehnd[0]) {
281 fprintf(stderr, "Error: could not load context file from %s: %s\n",
282 paths[0], strerror(errno));
283 exit(1);
284 }
285
286 FILE* test_data = fopen(paths[1], "r");
287 if (test_data == NULL) {
288 fprintf(stderr, "Error: could not load test file from %s : %s\n",
289 paths[1], strerror(errno));
290 exit(1);
291 }
292
293 char line[1024];
294 while (fgets(line, sizeof(line), test_data)) {
295 char *path;
296 char *expected_type;
297
298 if (!strcmp(line, "\n") || line[0] == '#') {
299 continue;
300 }
301
302 int ret = sscanf(line, "%ms %ms", &path, &expected_type);
303 if (ret != 2) {
304 fprintf(stderr, "Error: unable to parse the line %s\n", line);
305 exit(1);
306 }
307
308 char *found_context;
309 ret = selabel_lookup(global_state.sepolicy.sehnd[0], &found_context, path, 0);
310 if (ret != 0) {
311 fprintf(stderr, "Error: unable to lookup the path for %s\n", line);
312 exit(1);
313 }
314
315 context_t found = context_new(found_context);
316 const char *found_type = context_type_get(found);
317
318 if (strcmp(found_type, expected_type)) {
319 fprintf(stderr, "Incorrect type for %s: resolved to %s, expected %s\n",
320 path, found_type, expected_type);
321 }
322
323 free(found_context);
324 context_free(found);
325 free(path);
326 free(expected_type);
327 }
328 fclose(test_data);
329
330 // Prints the coverage of file_contexts on the test data. It includes
331 // warnings for rules that have not been hit by any test example.
332 selabel_stats(global_state.sepolicy.sehnd[0]);
333}
334
William Robertsad3cb392015-09-24 18:10:54 -0700335static void do_fc_check_and_die_on_error(struct selinux_opt opts[], unsigned int backend, filemode mode,
William Roberts922b4e92016-01-14 17:06:37 -0800336 const char *sepolicy_file, const char *context_file, bool allow_empty)
William Robertsad3cb392015-09-24 18:10:54 -0700337{
William Roberts922b4e92016-01-14 17:06:37 -0800338 struct stat sb;
339 if (stat(context_file, &sb) < 0) {
340 perror("Error: could not get stat on file contexts file");
341 exit(1);
342 }
343
344 if (sb.st_size == 0) {
345 /* Nothing to check on empty file_contexts file if allowed*/
346 if (allow_empty) {
347 return;
348 }
349 /* else: We could throw the error here, but libselinux backend will catch it */
350 }
351
William Robertsad3cb392015-09-24 18:10:54 -0700352 global_state.sepolicy.file = fopen(sepolicy_file, "r");
353 if (!global_state.sepolicy.file) {
354 perror("Error: could not open policy file");
355 exit(1);
356 }
357
358 global_state.sepolicy.handle = sepol_handle_create();
359 if (!global_state.sepolicy.handle) {
360 fprintf(stderr, "Error: could not create policy handle: %s\n", strerror(errno));
361 exit(1);
362 }
363
364 if (sepol_policy_file_create(&global_state.sepolicy.pf) < 0) {
365 perror("Error: could not create policy handle");
366 exit(1);
367 }
368
369 sepol_policy_file_set_fp(global_state.sepolicy.pf, global_state.sepolicy.file);
370 sepol_policy_file_set_handle(global_state.sepolicy.pf, global_state.sepolicy.handle);
371
372 int rc = sepol_policydb_create(&global_state.sepolicy.sdb);
373 if (rc < 0) {
374 perror("Error: could not create policy db");
375 exit(1);
376 }
377
378 rc = sepol_policydb_read(global_state.sepolicy.sdb, global_state.sepolicy.pf);
379 if (rc < 0) {
380 perror("Error: could not read file into policy db");
381 exit(1);
382 }
383
384 global_state.assert.attrs = filemode_to_assert_attrs(mode);
385
386 bool ret = ebitmap_attribute_assertion_init(&global_state.assert.set, global_state.assert.attrs);
387 if (!ret) {
388 /* error messages logged by ebitmap_attribute_assertion_init() */
389 exit(1);
390 }
391
392 selinux_set_callback(SELINUX_CB_VALIDATE,
393 (union selinux_callback)&validate);
394
395 opts[1].value = context_file;
396
397 global_state.sepolicy.sehnd[0] = selabel_open(backend, opts, 2);
398 if (!global_state.sepolicy.sehnd[0]) {
399 fprintf(stderr, "Error: could not load context file from %s\n", context_file);
400 exit(1);
401 }
402}
403
Stephen Smalley01a58af2012-10-02 12:46:37 -0400404int main(int argc, char **argv)
405{
406 struct selinux_opt opts[] = {
407 { SELABEL_OPT_VALIDATE, (void*)1 },
408 { SELABEL_OPT_PATH, NULL }
409 };
Robert Craigd98d26e2013-01-23 14:04:50 -0500410
411 // Default backend unless changed by input argument.
412 unsigned int backend = SELABEL_CTX_FILE;
413
William Roberts922b4e92016-01-14 17:06:37 -0800414 bool allow_empty = false;
Stephen Smalley13b6b7e2015-08-05 12:43:15 -0400415 bool compare = false;
Thiébaud Weksteen9a924ba2023-10-05 09:44:24 +1100416 bool test_data = false;
Robert Craigd98d26e2013-01-23 14:04:50 -0500417 char c;
Stephen Smalley01a58af2012-10-02 12:46:37 -0400418
William Robertsad3cb392015-09-24 18:10:54 -0700419 filemode mode = filemode_file_contexts;
420
Thiébaud Weksteen9a924ba2023-10-05 09:44:24 +1100421 while ((c = getopt(argc, argv, "clpsvet")) != -1) {
Robert Craigd98d26e2013-01-23 14:04:50 -0500422 switch (c) {
Stephen Smalley13b6b7e2015-08-05 12:43:15 -0400423 case 'c':
424 compare = true;
425 break;
William Roberts922b4e92016-01-14 17:06:37 -0800426 case 'e':
427 allow_empty = true;
428 break;
Robert Craigd98d26e2013-01-23 14:04:50 -0500429 case 'p':
William Robertsad3cb392015-09-24 18:10:54 -0700430 mode = filemode_property_contexts;
431 backend = SELABEL_CTX_ANDROID_PROP;
432 break;
433 case 's':
434 mode = filemode_service_contexts;
Janis Danisevskis3e463292016-09-08 13:15:19 +0100435 backend = SELABEL_CTX_ANDROID_SERVICE;
Robert Craigd98d26e2013-01-23 14:04:50 -0500436 break;
Martijn Coenend48d54a2017-04-06 13:22:44 -0700437 case 'l':
438 mode = filemode_hw_service_contexts;
439 backend = SELABEL_CTX_ANDROID_SERVICE;
440 break;
441 case 'v':
442 mode = filemode_vendor_service_contexts;
443 backend = SELABEL_CTX_ANDROID_SERVICE;
444 break;
Thiébaud Weksteen9a924ba2023-10-05 09:44:24 +1100445 case 't':
446 test_data = true;
447 break;
Robert Craigd98d26e2013-01-23 14:04:50 -0500448 case 'h':
449 default:
450 usage(argv[0]);
451 break;
452 }
Stephen Smalley01a58af2012-10-02 12:46:37 -0400453 }
454
Robert Craigd98d26e2013-01-23 14:04:50 -0500455 int index = optind;
456 if (argc - optind != 2) {
Robert Craigd98d26e2013-01-23 14:04:50 -0500457 usage(argv[0]);
458 }
459
Thiébaud Weksteen9a924ba2023-10-05 09:44:24 +1100460 if ((compare || test_data) && backend != SELABEL_CTX_FILE) {
Stephen Smalley13b6b7e2015-08-05 12:43:15 -0400461 usage(argv[0]);
462 }
463
William Robertsad3cb392015-09-24 18:10:54 -0700464 atexit(cleanup);
465
Stephen Smalley13b6b7e2015-08-05 12:43:15 -0400466 if (compare) {
William Robertsad3cb392015-09-24 18:10:54 -0700467 do_compare_and_die_on_error(opts, backend, &(argv[index]));
Thiébaud Weksteen9a924ba2023-10-05 09:44:24 +1100468 } else if (test_data) {
469 do_test_data_and_die_on_error(opts, backend, &(argv[index]));
William Robertsad3cb392015-09-24 18:10:54 -0700470 } else {
471 /* remaining args are sepolicy file and context file */
472 char *sepolicy_file = argv[index];
473 char *context_file = argv[index + 1];
Stephen Smalley13b6b7e2015-08-05 12:43:15 -0400474
William Roberts922b4e92016-01-14 17:06:37 -0800475 do_fc_check_and_die_on_error(opts, backend, mode, sepolicy_file, context_file, allow_empty);
Stephen Smalley13b6b7e2015-08-05 12:43:15 -0400476 }
Stephen Smalley01a58af2012-10-02 12:46:37 -0400477 exit(0);
478}