1#include <ctype.h>
2#include <errno.h>
3#include <stdint.h>
4#include <stdio.h>
5#include <string.h>
6#include <unistd.h>
7#include <sys/types.h>
8#include <sys/stat.h>
9#include <getopt.h>
10#include <limits.h>
11#include <selinux/selinux.h>
12#include <sepol/sepol.h>
13
14#include "../src/label_file.h"
15#include "../src/regex.h"
16
17static const char *policy_file;
18static int ctx_err;
19
20static int validate_context(char **ctxp)
21{
22	char *ctx = *ctxp;
23
24	if (policy_file && sepol_check_context(ctx) < 0) {
25		ctx_err = -1;
26		return ctx_err;
27	}
28
29	return 0;
30}
31
32static int process_file(struct selabel_handle *rec, const char *filename)
33{
34	unsigned int line_num;
35	int rc;
36	char *line_buf = NULL;
37	size_t line_len = 0;
38	FILE *context_file;
39	const char *prefix = NULL;
40
41	context_file = fopen(filename, "r");
42	if (!context_file) {
43		fprintf(stderr, "Error opening %s: %s\n",
44			    filename, strerror(errno));
45		return -1;
46	}
47
48	line_num = 0;
49	rc = 0;
50	while (getline(&line_buf, &line_len, context_file) > 0) {
51		rc = process_line(rec, filename, prefix, line_buf, ++line_num);
52		if (rc || ctx_err) {
53			/* With -p option need to check and fail if ctx err as
54			 * process_line() context validation on Linux does not
55			 * return an error, but does print the error line to
56			 * stderr. Android will set both to error and print
57			 * the error line. */
58			rc = -1;
59			goto out;
60		}
61	}
62out:
63	free(line_buf);
64	fclose(context_file);
65	return rc;
66}
67
68/*
69 * File Format
70 *
71 * u32 - magic number
72 * u32 - version
73 * u32 - length of pcre version EXCLUDING nul
74 * char - pcre version string EXCLUDING nul
75 * u32 - number of stems
76 * ** Stems
77 *	u32  - length of stem EXCLUDING nul
78 *	char - stem char array INCLUDING nul
79 * u32 - number of regexs
80 * ** Regexes
81 *	u32  - length of upcoming context INCLUDING nul
82 *	char - char array of the raw context
83 *	u32  - length of the upcoming regex_str
84 *	char - char array of the original regex string including the stem.
85 *	u32  - mode bits for >= SELINUX_COMPILED_FCONTEXT_MODE
86 *	       mode_t for <= SELINUX_COMPILED_FCONTEXT_PCRE_VERS
87 *	s32  - stemid associated with the regex
88 *	u32  - spec has meta characters
89 *	u32  - The specs prefix_len if >= SELINUX_COMPILED_FCONTEXT_PREFIX_LEN
90 *	u32  - data length of the pcre regex
91 *	char - a buffer holding the raw pcre regex info
92 *	u32  - data length of the pcre regex study daya
93 *	char - a buffer holding the raw pcre regex study data
94 */
95static int write_binary_file(struct saved_data *data, int fd,
96			     int do_write_precompregex)
97{
98	struct spec *specs = data->spec_arr;
99	FILE *bin_file;
100	size_t len;
101	uint32_t magic = SELINUX_MAGIC_COMPILED_FCONTEXT;
102	uint32_t section_len;
103	uint32_t i;
104	int rc;
105	const char *reg_version;
106	const char *reg_arch;
107
108	bin_file = fdopen(fd, "w");
109	if (!bin_file) {
110		perror("fopen output_file");
111		exit(EXIT_FAILURE);
112	}
113
114	/* write some magic number */
115	len = fwrite(&magic, sizeof(uint32_t), 1, bin_file);
116	if (len != 1)
117		goto err;
118
119	/* write the version */
120	section_len = SELINUX_COMPILED_FCONTEXT_MAX_VERS;
121	len = fwrite(&section_len, sizeof(uint32_t), 1, bin_file);
122	if (len != 1)
123		goto err;
124
125	/* write version of the regex back-end */
126	reg_version = regex_version();
127	if (!reg_version)
128		goto err;
129	section_len = strlen(reg_version);
130	len = fwrite(&section_len, sizeof(uint32_t), 1, bin_file);
131	if (len != 1)
132		goto err;
133	len = fwrite(reg_version, sizeof(char), section_len, bin_file);
134	if (len != section_len)
135		goto err;
136
137	/* write regex arch string */
138	reg_arch = regex_arch_string();
139	if (!reg_arch)
140		goto err;
141	section_len = strlen(reg_arch);
142	len = fwrite(&section_len, sizeof(uint32_t), 1, bin_file);
143	if (len != 1)
144		goto err;
145	len = fwrite(reg_arch, sizeof(char), section_len, bin_file);
146	if (len != section_len)
147		goto err;
148
149	/* write the number of stems coming */
150	section_len = data->num_stems;
151	len = fwrite(&section_len, sizeof(uint32_t), 1, bin_file);
152	if (len != 1)
153		goto err;
154
155	for (i = 0; i < section_len; i++) {
156		char *stem = data->stem_arr[i].buf;
157		uint32_t stem_len = data->stem_arr[i].len;
158
159		/* write the strlen (aka no nul) */
160		len = fwrite(&stem_len, sizeof(uint32_t), 1, bin_file);
161		if (len != 1)
162			goto err;
163
164		/* include the nul in the file */
165		stem_len += 1;
166		len = fwrite(stem, sizeof(char), stem_len, bin_file);
167		if (len != stem_len)
168			goto err;
169	}
170
171	/* write the number of regexes coming */
172	section_len = data->nspec;
173	len = fwrite(&section_len, sizeof(uint32_t), 1, bin_file);
174	if (len != 1)
175		goto err;
176
177	for (i = 0; i < section_len; i++) {
178		char *context = specs[i].lr.ctx_raw;
179		char *regex_str = specs[i].regex_str;
180		mode_t mode = specs[i].mode;
181		size_t prefix_len = specs[i].prefix_len;
182		int32_t stem_id = specs[i].stem_id;
183		struct regex_data *re = specs[i].regex;
184		uint32_t to_write;
185
186		/* length of the context string (including nul) */
187		to_write = strlen(context) + 1;
188		len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file);
189		if (len != 1)
190			goto err;
191
192		/* original context strin (including nul) */
193		len = fwrite(context, sizeof(char), to_write, bin_file);
194		if (len != to_write)
195			goto err;
196
197		/* length of the original regex string (including nul) */
198		to_write = strlen(regex_str) + 1;
199		len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file);
200		if (len != 1)
201			goto err;
202
203		/* original regex string */
204		len = fwrite(regex_str, sizeof(char), to_write, bin_file);
205		if (len != to_write)
206			goto err;
207
208		/* binary F_MODE bits */
209		to_write = mode;
210		len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file);
211		if (len != 1)
212			goto err;
213
214		/* stem for this regex (could be -1) */
215		len = fwrite(&stem_id, sizeof(stem_id), 1, bin_file);
216		if (len != 1)
217			goto err;
218
219		/* does this spec have a metaChar? */
220		to_write = specs[i].hasMetaChars;
221		len = fwrite(&to_write, sizeof(to_write), 1, bin_file);
222		if (len != 1)
223			goto err;
224
225		/* For SELINUX_COMPILED_FCONTEXT_PREFIX_LEN */
226		to_write = prefix_len;
227		len = fwrite(&to_write, sizeof(to_write), 1, bin_file);
228		if (len != 1)
229			goto err;
230
231		/* Write regex related data */
232		rc = regex_writef(re, bin_file, do_write_precompregex);
233		if (rc < 0)
234			goto err;
235	}
236
237	rc = 0;
238out:
239	fclose(bin_file);
240	return rc;
241err:
242	rc = -1;
243	goto out;
244}
245
246static void free_specs(struct saved_data *data)
247{
248	struct spec *specs = data->spec_arr;
249	unsigned int num_entries = data->nspec;
250	unsigned int i;
251
252	for (i = 0; i < num_entries; i++) {
253		free(specs[i].lr.ctx_raw);
254		free(specs[i].lr.ctx_trans);
255		free(specs[i].regex_str);
256		free(specs[i].type_str);
257		regex_data_free(specs[i].regex);
258	}
259	free(specs);
260
261	num_entries = data->num_stems;
262	for (i = 0; i < num_entries; i++)
263		free(data->stem_arr[i].buf);
264	free(data->stem_arr);
265
266	memset(data, 0, sizeof(*data));
267}
268
269static __attribute__ ((__noreturn__)) void usage(const char *progname)
270{
271	fprintf(stderr,
272	    "usage: %s [-o out_file] [-p policy_file] fc_file\n"
273	    "Where:\n\t"
274	    "-o       Optional file name of the PCRE formatted binary\n\t"
275	    "         file to be output. If not specified the default\n\t"
276	    "         will be fc_file with the .bin suffix appended.\n\t"
277	    "-p       Optional binary policy file that will be used to\n\t"
278	    "         validate contexts defined in the fc_file.\n\t"
279	    "-r       Omit precompiled regular expressions from the output.\n\t"
280	    "         (PCRE2 only. Compiled PCRE2 regular expressions are\n\t"
281	    "         not portable across architectures. Use this flag\n\t"
282	    "         if you know that you build for an incompatible\n\t"
283	    "         architecture to save space. When linked against\n\t"
284	    "         PCRE1 this flag is ignored.)\n\t"
285	    "-i       Print regular expression info end exit. That is, back\n\t"
286	    "         end version and architecture identifier.\n\t"
287	    "         Arch identifier format (PCRE2):\n\t"
288	    "         <pointer width>-<size type width>-<endianness>, e.g.,\n\t"
289	    "         \"8-8-el\" for x86_64.\n\t"
290	    "fc_file  The text based file contexts file to be processed.\n",
291	    progname);
292		exit(EXIT_FAILURE);
293}
294
295int main(int argc, char *argv[])
296{
297	const char *path = NULL;
298	const char *out_file = NULL;
299	int do_write_precompregex = 1;
300	char stack_path[PATH_MAX + 1];
301	char *tmp = NULL;
302	int fd, rc, opt;
303	FILE *policy_fp = NULL;
304	struct stat buf;
305	struct selabel_handle *rec = NULL;
306	struct saved_data *data = NULL;
307
308	if (argc < 2)
309		usage(argv[0]);
310
311	while ((opt = getopt(argc, argv, "io:p:r")) > 0) {
312		switch (opt) {
313		case 'o':
314			out_file = optarg;
315			break;
316		case 'p':
317			policy_file = optarg;
318			break;
319		case 'r':
320			do_write_precompregex = 0;
321			break;
322		case 'i':
323			printf("%s (%s)\n", regex_version(),
324					regex_arch_string());
325			return 0;
326		default:
327			usage(argv[0]);
328		}
329	}
330
331	if (optind >= argc)
332		usage(argv[0]);
333
334	path = argv[optind];
335	if (stat(path, &buf) < 0) {
336		fprintf(stderr, "%s: could not stat: %s: %s\n", argv[0], path, strerror(errno));
337		exit(EXIT_FAILURE);
338	}
339
340	/* Open binary policy if supplied. */
341	if (policy_file) {
342		policy_fp = fopen(policy_file, "r");
343
344		if (!policy_fp) {
345			fprintf(stderr, "%s: failed to open %s: %s\n",
346				argv[0], policy_file, strerror(errno));
347			exit(EXIT_FAILURE);
348		}
349
350		if (sepol_set_policydb_from_file(policy_fp) < 0) {
351			fprintf(stderr, "%s: failed to load policy from %s\n",
352				argv[0], policy_file);
353			fclose(policy_fp);
354			exit(EXIT_FAILURE);
355		}
356	}
357
358	/* Generate dummy handle for process_line() function */
359	rec = (struct selabel_handle *)calloc(1, sizeof(*rec));
360	if (!rec) {
361		fprintf(stderr, "%s: calloc failed: %s\n", argv[0], strerror(errno));
362		if (policy_fp)
363			fclose(policy_fp);
364		exit(EXIT_FAILURE);
365	}
366	rec->backend = SELABEL_CTX_FILE;
367
368	/* Need to set validation on to get the bin file generated by the
369	 * process_line function, however as the bin file being generated
370	 * may not be related to the currently loaded policy (that it
371	 * would be validated against), then set callback to ignore any
372	 * validation - unless the -p option is used in which case if an
373	 * error is detected, the process will be aborted. */
374	rec->validating = 1;
375	selinux_set_callback(SELINUX_CB_VALIDATE,
376			    (union selinux_callback)&validate_context);
377
378	data = (struct saved_data *)calloc(1, sizeof(*data));
379	if (!data) {
380		fprintf(stderr, "%s: calloc failed: %s\n", argv[0], strerror(errno));
381		free(rec);
382		if (policy_fp)
383			fclose(policy_fp);
384		exit(EXIT_FAILURE);
385	}
386
387	rec->data = data;
388
389	rc = process_file(rec, path);
390	if (rc < 0) {
391		fprintf(stderr, "%s: process_file failed\n", argv[0]);
392		goto err;
393	}
394
395	rc = sort_specs(data);
396	if (rc) {
397		fprintf(stderr, "%s: sort_specs failed\n", argv[0]);
398		goto err;
399	}
400
401	if (out_file)
402		rc = snprintf(stack_path, sizeof(stack_path), "%s", out_file);
403	else
404		rc = snprintf(stack_path, sizeof(stack_path), "%s.bin", path);
405
406	if (rc < 0 || rc >= (int)sizeof(stack_path)) {
407		fprintf(stderr, "%s: snprintf failed\n", argv[0]);
408		goto err;
409	}
410
411	tmp = malloc(strlen(stack_path) + 7);
412	if (!tmp) {
413		fprintf(stderr, "%s: malloc failed: %s\n", argv[0], strerror(errno));
414		goto err;
415	}
416
417	rc = sprintf(tmp, "%sXXXXXX", stack_path);
418	if (rc < 0) {
419		fprintf(stderr, "%s: sprintf failed\n", argv[0]);
420		goto err;
421	}
422
423	fd  = mkstemp(tmp);
424	if (fd < 0) {
425		fprintf(stderr, "%s: mkstemp %s failed: %s\n", argv[0], tmp, strerror(errno));
426		goto err;
427	}
428
429	rc = fchmod(fd, buf.st_mode);
430	if (rc < 0) {
431		fprintf(stderr, "%s: fchmod %s failed: %s\n", argv[0], tmp, strerror(errno));
432		goto err_unlink;
433	}
434
435	rc = write_binary_file(data, fd, do_write_precompregex);
436	if (rc < 0) {
437		fprintf(stderr, "%s: write_binary_file %s failed\n", argv[0], tmp);
438		goto err_unlink;
439	}
440
441	rc = rename(tmp, stack_path);
442	if (rc < 0) {
443		fprintf(stderr, "%s: rename %s -> %s failed: %s\n", argv[0], tmp, stack_path, strerror(errno));
444		goto err_unlink;
445	}
446
447	rc = 0;
448out:
449	if (policy_fp)
450		fclose(policy_fp);
451
452	free_specs(data);
453	free(rec);
454	free(data);
455	free(tmp);
456	return rc;
457
458err_unlink:
459	unlink(tmp);
460err:
461	rc = -1;
462	goto out;
463}
464