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