/src/selinux/libselinux/src/regex.c
Line | Count | Source |
1 | | #include <assert.h> |
2 | | #include <endian.h> |
3 | | #include <pthread.h> |
4 | | #include <stdint.h> |
5 | | #include <stdio.h> |
6 | | #include <string.h> |
7 | | |
8 | | #include "regex.h" |
9 | | #include "label_file.h" |
10 | | #include "selinux_internal.h" |
11 | | |
12 | | #ifdef USE_PCRE2 |
13 | | #define REGEX_ARCH_SIZE_T PCRE2_SIZE |
14 | | #else |
15 | | #define REGEX_ARCH_SIZE_T size_t |
16 | | #endif |
17 | | |
18 | | #ifndef __BYTE_ORDER__ |
19 | | |
20 | | /* If the compiler doesn't define __BYTE_ORDER__, try to use the C |
21 | | * library <endian.h> header definitions. */ |
22 | | #ifndef __BYTE_ORDER |
23 | | #error Neither __BYTE_ORDER__ nor __BYTE_ORDER defined. Unable to determine endianness. |
24 | | #endif |
25 | | |
26 | | #define __ORDER_LITTLE_ENDIAN __LITTLE_ENDIAN |
27 | | #define __ORDER_BIG_ENDIAN __BIG_ENDIAN |
28 | | #define __BYTE_ORDER__ __BYTE_ORDER |
29 | | |
30 | | #endif |
31 | | |
32 | | /** |
33 | | * This constructor function allocates a buffer for a regex_data structure. |
34 | | * The buffer is being initialized with zeroes. |
35 | | */ |
36 | | static struct regex_data *regex_data_create(void); |
37 | | |
38 | | #ifdef USE_PCRE2 |
39 | | static pthread_key_t match_data_key; |
40 | | static pthread_once_t match_data_key_once = PTHREAD_ONCE_INIT; |
41 | | static int match_data_key_alloc_failed = 0; |
42 | | static int match_data_key_created = 0; |
43 | | static pthread_once_t once = PTHREAD_ONCE_INIT; |
44 | | static pcre2_match_context *match_context; |
45 | | static char arch_string_buffer[32]; |
46 | | |
47 | | /* |
48 | | * Limit the number of backtrack steps to prevent catastrophic backtracking |
49 | | * (ReDoS) from crafted file context patterns or lookup keys. 10 million steps |
50 | | * is generous for legitimate file context patterns while bounding worst-case |
51 | | * matching time to milliseconds. |
52 | | */ |
53 | 1 | #define REGEX_MATCH_LIMIT 10000000U |
54 | | |
55 | | static void regex_arch_string_init(void) |
56 | 0 | { |
57 | 0 | char const *endianness; |
58 | 0 | int rc; |
59 | |
|
60 | 0 | if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) |
61 | 0 | endianness = "el"; |
62 | 0 | else if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) |
63 | 0 | endianness = "eb"; |
64 | 0 | else { |
65 | 0 | arch_string_buffer[0] = '\0'; |
66 | 0 | return; |
67 | 0 | } |
68 | | |
69 | 0 | rc = snprintf(arch_string_buffer, sizeof(arch_string_buffer), |
70 | 0 | "%zu-%zu-%s", sizeof(void *), sizeof(REGEX_ARCH_SIZE_T), |
71 | 0 | endianness); |
72 | 0 | if (rc < 0 || (size_t)rc >= sizeof(arch_string_buffer)) { |
73 | 0 | arch_string_buffer[0] = '\0'; |
74 | 0 | return; |
75 | 0 | } |
76 | 0 | } |
77 | | |
78 | | const char *regex_arch_string(void) |
79 | 0 | { |
80 | 0 | __selinux_once(once, regex_arch_string_init); |
81 | |
|
82 | 0 | return arch_string_buffer[0] != '\0' ? arch_string_buffer : NULL; |
83 | 0 | } |
84 | | |
85 | | struct regex_data { |
86 | | pcre2_code *regex; /* compiled regular expression */ |
87 | | }; |
88 | | |
89 | | int regex_prepare_data(struct regex_data **regex, char const *pattern_string, |
90 | | struct regex_error_data *errordata, bool jit) |
91 | 771k | { |
92 | 771k | memset(errordata, 0, sizeof(struct regex_error_data)); |
93 | | |
94 | 771k | *regex = regex_data_create(); |
95 | 771k | if (!(*regex)) |
96 | 0 | return -1; |
97 | | |
98 | 771k | (*regex)->regex = pcre2_compile((PCRE2_SPTR)pattern_string, |
99 | 771k | PCRE2_ZERO_TERMINATED, PCRE2_DOTALL, |
100 | 771k | &errordata->error_code, |
101 | 771k | &errordata->error_offset, NULL); |
102 | 771k | if (!(*regex)->regex) { |
103 | 11.3k | goto err; |
104 | 11.3k | } |
105 | | |
106 | | /* JIT-compile for complete matching only. pcre2_match() uses the JIT |
107 | | * automatically when available, avoiding the interpreter's |
108 | | * susceptibility to catastrophic backtracking. Partial matches fall |
109 | | * back to the interpreter, protected by the match limit. Failures |
110 | | * are non-fatal. */ |
111 | 759k | if (jit) |
112 | 0 | (void)pcre2_jit_compile((*regex)->regex, PCRE2_JIT_COMPLETE); |
113 | | |
114 | 759k | return 0; |
115 | | |
116 | 11.3k | err: |
117 | 11.3k | regex_data_free(*regex); |
118 | 11.3k | *regex = NULL; |
119 | 11.3k | return -1; |
120 | 771k | } |
121 | | |
122 | | char const *regex_version(void) |
123 | 0 | { |
124 | 0 | static char version_buf[256]; |
125 | 0 | size_t len = pcre2_config(PCRE2_CONFIG_VERSION, NULL); |
126 | 0 | if (len <= 0 || len > sizeof(version_buf)) |
127 | 0 | return NULL; |
128 | | |
129 | 0 | pcre2_config(PCRE2_CONFIG_VERSION, version_buf); |
130 | 0 | return version_buf; |
131 | 0 | } |
132 | | |
133 | | int regex_load_mmap(struct mmap_area *mmap_area, struct regex_data **regex, |
134 | | int do_load_precompregex, bool jit, bool *regex_compiled) |
135 | 0 | { |
136 | 0 | int rc; |
137 | 0 | uint32_t data_u32, entry_len; |
138 | |
|
139 | 0 | *regex_compiled = false; |
140 | 0 | rc = next_entry(&data_u32, mmap_area, sizeof(uint32_t)); |
141 | 0 | if (rc < 0) |
142 | 0 | return -1; |
143 | | |
144 | 0 | entry_len = be32toh(data_u32); |
145 | |
|
146 | 0 | if (entry_len > mmap_area->next_len) |
147 | 0 | return -1; |
148 | | |
149 | 0 | if (entry_len && do_load_precompregex) { |
150 | | /* |
151 | | * this should yield exactly one because we store one pattern at |
152 | | * a time |
153 | | */ |
154 | 0 | rc = pcre2_serialize_get_number_of_codes(mmap_area->next_addr); |
155 | 0 | if (rc != 1) |
156 | 0 | return -1; |
157 | | |
158 | 0 | *regex = regex_data_create(); |
159 | 0 | if (!*regex) |
160 | 0 | return -1; |
161 | | |
162 | 0 | rc = pcre2_serialize_decode(&(*regex)->regex, 1, |
163 | 0 | (PCRE2_SPTR)mmap_area->next_addr, |
164 | 0 | NULL); |
165 | 0 | if (rc != 1) |
166 | 0 | goto err; |
167 | | |
168 | 0 | if (jit) |
169 | 0 | (void)pcre2_jit_compile((*regex)->regex, |
170 | 0 | PCRE2_JIT_COMPLETE); |
171 | |
|
172 | 0 | *regex_compiled = true; |
173 | 0 | } |
174 | | |
175 | | /* and skip the decoded bit */ |
176 | 0 | rc = next_entry(NULL, mmap_area, entry_len); |
177 | 0 | if (rc < 0) |
178 | 0 | goto err; |
179 | | |
180 | 0 | return 0; |
181 | 0 | err: |
182 | 0 | regex_data_free(*regex); |
183 | 0 | *regex = NULL; |
184 | 0 | return -1; |
185 | 0 | } |
186 | | |
187 | | int regex_writef(const struct regex_data *regex, FILE *fp, |
188 | | int do_write_precompregex) |
189 | 0 | { |
190 | 0 | int rc = 0; |
191 | 0 | size_t len; |
192 | 0 | PCRE2_SIZE serialized_size; |
193 | 0 | uint32_t to_write = 0, data_u32; |
194 | 0 | PCRE2_UCHAR *bytes = NULL; |
195 | |
|
196 | 0 | if (do_write_precompregex) { |
197 | | /* encode the pattern for serialization */ |
198 | 0 | rc = pcre2_serialize_encode((const pcre2_code **)®ex->regex, |
199 | 0 | 1, &bytes, &serialized_size, NULL); |
200 | 0 | if (rc != 1 || serialized_size >= UINT32_MAX) { |
201 | 0 | rc = -3; |
202 | 0 | goto out; |
203 | 0 | } |
204 | 0 | to_write = serialized_size; |
205 | 0 | } |
206 | | |
207 | | /* write serialized pattern's size */ |
208 | 0 | data_u32 = htobe32(to_write); |
209 | 0 | len = fwrite(&data_u32, sizeof(uint32_t), 1, fp); |
210 | 0 | if (len != 1) { |
211 | 0 | rc = -1; |
212 | 0 | goto out; |
213 | 0 | } |
214 | | |
215 | 0 | if (do_write_precompregex) { |
216 | | /* write serialized pattern */ |
217 | 0 | len = fwrite(bytes, 1, to_write, fp); |
218 | 0 | if (len != to_write) |
219 | 0 | rc = -1; |
220 | 0 | } |
221 | |
|
222 | 0 | out: |
223 | 0 | if (bytes) |
224 | 0 | pcre2_serialize_free(bytes); |
225 | |
|
226 | 0 | return rc; |
227 | 0 | } |
228 | | |
229 | | static void match_data_thread_free(void *ptr) |
230 | 0 | { |
231 | 0 | pcre2_match_data_free(ptr); |
232 | 0 | } |
233 | | |
234 | | static void match_data_key_init(void) |
235 | 1 | { |
236 | 1 | pcre2_match_context *mctx; |
237 | | |
238 | 1 | if (__selinux_key_create(&match_data_key, match_data_thread_free) == 0) |
239 | 1 | match_data_key_created = 1; |
240 | 0 | else |
241 | 0 | match_data_key_alloc_failed = 1; |
242 | | |
243 | 1 | mctx = pcre2_match_context_create(NULL); |
244 | 1 | if (mctx) { |
245 | 1 | pcre2_set_match_limit(mctx, REGEX_MATCH_LIMIT); |
246 | 1 | match_context = mctx; |
247 | 1 | } |
248 | 1 | } |
249 | | |
250 | | static void __attribute__((destructor)) match_data_key_destroy(void) |
251 | 0 | { |
252 | 0 | if (match_data_key_created) { |
253 | 0 | __selinux_key_delete(match_data_key); |
254 | 0 | match_data_key_created = 0; |
255 | 0 | } |
256 | 0 | } |
257 | | |
258 | | void regex_data_free(struct regex_data *regex) |
259 | 782k | { |
260 | 782k | if (regex) { |
261 | 771k | if (regex->regex) |
262 | 771k | pcre2_code_free(regex->regex); |
263 | 771k | free(regex); |
264 | 771k | } |
265 | 782k | } |
266 | | |
267 | | int regex_match(struct regex_data *regex, char const *subject, int partial) |
268 | 251k | { |
269 | 251k | int rc; |
270 | 251k | bool slow; |
271 | 251k | pcre2_match_data *match_data = NULL; |
272 | | |
273 | 251k | __selinux_once(match_data_key_once, match_data_key_init); |
274 | | |
275 | 251k | if (!match_data_key_alloc_failed) { |
276 | 251k | match_data = __selinux_getspecific(match_data_key); |
277 | 251k | if (!match_data) { |
278 | 1 | match_data = pcre2_match_data_create(1, NULL); |
279 | 1 | if (match_data) { |
280 | 1 | __selinux_setspecific(match_data_key, |
281 | 1 | match_data); |
282 | 1 | } |
283 | 1 | } |
284 | 251k | } |
285 | | |
286 | 251k | slow = (match_data_key_alloc_failed || match_data == NULL); |
287 | 251k | if (slow) { |
288 | 0 | match_data = pcre2_match_data_create_from_pattern(regex->regex, |
289 | 0 | NULL); |
290 | 0 | if (!match_data) |
291 | 0 | return REGEX_ERROR; |
292 | 0 | } |
293 | | |
294 | | /* Use pcre2_match() rather than pcre2_jit_match(): pcre2_match() |
295 | | * automatically uses the JIT when the code has been compiled with |
296 | | * pcre2_jit_compile(), while pcre2_jit_match() is known to produce |
297 | | * incorrect results on some platforms (e.g. aarch64). */ |
298 | 251k | rc = pcre2_match(regex->regex, (PCRE2_SPTR)subject, |
299 | 251k | PCRE2_ZERO_TERMINATED, 0, |
300 | 251k | partial ? PCRE2_PARTIAL_SOFT : 0, match_data, |
301 | 251k | match_context); |
302 | | |
303 | 251k | if (slow) |
304 | 251k | pcre2_match_data_free(match_data); |
305 | | |
306 | 251k | if (rc >= 0) |
307 | 224k | return REGEX_MATCH; |
308 | 26.2k | switch (rc) { |
309 | 5.45k | case PCRE2_ERROR_PARTIAL: |
310 | 5.45k | return REGEX_MATCH_PARTIAL; |
311 | 20.6k | case PCRE2_ERROR_NOMATCH: |
312 | 20.7k | case PCRE2_ERROR_MATCHLIMIT: |
313 | 20.7k | return REGEX_NO_MATCH; |
314 | 89 | default: |
315 | 89 | return REGEX_ERROR; |
316 | 26.2k | } |
317 | 26.2k | } |
318 | | |
319 | | /* |
320 | | * TODO Replace this compare function with something that actually compares the |
321 | | * regular expressions. |
322 | | * This compare function basically just compares the binary representations of |
323 | | * the automatons, and because this representation contains pointers and |
324 | | * metadata, it can only return a match if regex1 == regex2. |
325 | | * Preferably, this function would be replaced with an algorithm that computes |
326 | | * the equivalence of the automatons systematically. |
327 | | */ |
328 | | int regex_cmp(const struct regex_data *regex1, const struct regex_data *regex2) |
329 | 0 | { |
330 | 0 | int rc; |
331 | 0 | size_t len1, len2; |
332 | 0 | rc = pcre2_pattern_info(regex1->regex, PCRE2_INFO_SIZE, &len1); |
333 | 0 | assert(rc == 0); |
334 | 0 | rc = pcre2_pattern_info(regex2->regex, PCRE2_INFO_SIZE, &len2); |
335 | 0 | assert(rc == 0); |
336 | 0 | if (len1 != len2 || memcmp(regex1->regex, regex2->regex, len1)) |
337 | 0 | return SELABEL_INCOMPARABLE; |
338 | | |
339 | 0 | return SELABEL_EQUAL; |
340 | 0 | } |
341 | | |
342 | | static struct regex_data *regex_data_create(void) |
343 | 771k | { |
344 | 771k | struct regex_data *regex_data = |
345 | 771k | (struct regex_data *)calloc(1, sizeof(struct regex_data)); |
346 | 771k | return regex_data; |
347 | 771k | } |
348 | | |
349 | | #else // !USE_PCRE2 |
350 | | char const *regex_arch_string(void) |
351 | | { |
352 | | return "N/A"; |
353 | | } |
354 | | |
355 | | /* Prior to version 8.20, libpcre did not have pcre_free_study() */ |
356 | | #if (PCRE_MAJOR < 8 || (PCRE_MAJOR == 8 && PCRE_MINOR < 20)) |
357 | | #define pcre_free_study pcre_free |
358 | | #endif |
359 | | |
360 | | struct regex_data { |
361 | | int owned; /* |
362 | | * non zero if regex and pcre_extra is owned by this |
363 | | * structure and thus must be freed on destruction. |
364 | | */ |
365 | | pcre *regex; /* compiled regular expression */ |
366 | | union { |
367 | | pcre_extra *sd; /* pointer to extra compiled stuff */ |
368 | | pcre_extra lsd; /* used to hold the mmap'd version */ |
369 | | }; |
370 | | }; |
371 | | |
372 | | int regex_prepare_data(struct regex_data **regex, char const *pattern_string, |
373 | | struct regex_error_data *errordata, |
374 | | bool jit __attribute__((unused))) |
375 | | { |
376 | | memset(errordata, 0, sizeof(struct regex_error_data)); |
377 | | |
378 | | *regex = regex_data_create(); |
379 | | if (!(*regex)) |
380 | | return -1; |
381 | | |
382 | | (*regex)->regex = pcre_compile(pattern_string, PCRE_DOTALL, |
383 | | &errordata->error_buffer, |
384 | | &errordata->error_offset, NULL); |
385 | | if (!(*regex)->regex) |
386 | | goto err; |
387 | | |
388 | | (*regex)->owned = 1; |
389 | | |
390 | | (*regex)->sd = pcre_study((*regex)->regex, 0, &errordata->error_buffer); |
391 | | if (!(*regex)->sd && errordata->error_buffer) |
392 | | goto err; |
393 | | |
394 | | return 0; |
395 | | |
396 | | err: |
397 | | regex_data_free(*regex); |
398 | | *regex = NULL; |
399 | | return -1; |
400 | | } |
401 | | |
402 | | char const *regex_version(void) |
403 | | { |
404 | | return pcre_version(); |
405 | | } |
406 | | |
407 | | int regex_load_mmap(struct mmap_area *mmap_area, struct regex_data **regex, |
408 | | int do_load_precompregex __attribute__((unused)), |
409 | | bool jit __attribute__((unused)), bool *regex_compiled) |
410 | | { |
411 | | int rc; |
412 | | uint32_t data_u32, entry_len; |
413 | | size_t info_len; |
414 | | |
415 | | rc = next_entry(&data_u32, mmap_area, sizeof(uint32_t)); |
416 | | if (rc < 0) |
417 | | return -1; |
418 | | |
419 | | entry_len = be32toh(data_u32); |
420 | | if (!entry_len) |
421 | | return -1; |
422 | | |
423 | | *regex = regex_data_create(); |
424 | | if (!(*regex)) |
425 | | return -1; |
426 | | |
427 | | (*regex)->owned = 0; |
428 | | (*regex)->regex = (pcre *)mmap_area->next_addr; |
429 | | rc = next_entry(NULL, mmap_area, entry_len); |
430 | | if (rc < 0) |
431 | | goto err; |
432 | | |
433 | | /* |
434 | | * Check that regex lengths match. pcre_fullinfo() |
435 | | * also validates its magic number. |
436 | | */ |
437 | | rc = pcre_fullinfo((*regex)->regex, NULL, PCRE_INFO_SIZE, &info_len); |
438 | | if (rc < 0 || info_len != entry_len) |
439 | | goto err; |
440 | | |
441 | | rc = next_entry(&data_u32, mmap_area, sizeof(uint32_t)); |
442 | | if (rc < 0) |
443 | | goto err; |
444 | | |
445 | | entry_len = be32toh(data_u32); |
446 | | |
447 | | if (entry_len) { |
448 | | (*regex)->lsd.study_data = (void *)mmap_area->next_addr; |
449 | | (*regex)->lsd.flags |= PCRE_EXTRA_STUDY_DATA; |
450 | | rc = next_entry(NULL, mmap_area, entry_len); |
451 | | if (rc < 0) |
452 | | goto err; |
453 | | |
454 | | /* Check that study data lengths match. */ |
455 | | rc = pcre_fullinfo((*regex)->regex, &(*regex)->lsd, |
456 | | PCRE_INFO_STUDYSIZE, &info_len); |
457 | | if (rc < 0 || info_len != entry_len) |
458 | | goto err; |
459 | | } |
460 | | |
461 | | *regex_compiled = true; |
462 | | return 0; |
463 | | |
464 | | err: |
465 | | regex_data_free(*regex); |
466 | | *regex = NULL; |
467 | | return -1; |
468 | | } |
469 | | |
470 | | static inline const pcre_extra *get_pcre_extra(const struct regex_data *regex) |
471 | | { |
472 | | if (!regex) |
473 | | return NULL; |
474 | | if (regex->owned) { |
475 | | return regex->sd; |
476 | | } else if (regex->lsd.study_data) { |
477 | | return ®ex->lsd; |
478 | | } else { |
479 | | return NULL; |
480 | | } |
481 | | } |
482 | | |
483 | | int regex_writef(const struct regex_data *regex, FILE *fp, |
484 | | int do_write_precompregex __attribute__((unused))) |
485 | | { |
486 | | int rc; |
487 | | size_t len; |
488 | | uint32_t data_u32; |
489 | | size_t size; |
490 | | const pcre_extra *sd = get_pcre_extra(regex); |
491 | | |
492 | | /* determine the size of the pcre data in bytes */ |
493 | | rc = pcre_fullinfo(regex->regex, NULL, PCRE_INFO_SIZE, &size); |
494 | | if (rc < 0 || size >= UINT32_MAX) |
495 | | return -3; |
496 | | |
497 | | /* write the number of bytes in the pcre data */ |
498 | | data_u32 = htobe32(size); |
499 | | len = fwrite(&data_u32, sizeof(uint32_t), 1, fp); |
500 | | if (len != 1) |
501 | | return -1; |
502 | | |
503 | | /* write the actual pcre data as a char array */ |
504 | | len = fwrite(regex->regex, 1, size, fp); |
505 | | if (len != size) |
506 | | return -1; |
507 | | |
508 | | if (sd) { |
509 | | /* determine the size of the pcre study info */ |
510 | | rc = pcre_fullinfo(regex->regex, sd, PCRE_INFO_STUDYSIZE, |
511 | | &size); |
512 | | if (rc < 0 || size >= UINT32_MAX) |
513 | | return -3; |
514 | | } else |
515 | | size = 0; |
516 | | |
517 | | /* write the number of bytes in the pcre study data */ |
518 | | data_u32 = htobe32(size); |
519 | | len = fwrite(&data_u32, sizeof(uint32_t), 1, fp); |
520 | | if (len != 1) |
521 | | return -1; |
522 | | |
523 | | if (sd) { |
524 | | /* write the actual pcre study data as a char array */ |
525 | | len = fwrite(sd->study_data, 1, size, fp); |
526 | | if (len != size) |
527 | | return -1; |
528 | | } |
529 | | |
530 | | return 0; |
531 | | } |
532 | | |
533 | | void regex_data_free(struct regex_data *regex) |
534 | | { |
535 | | if (regex) { |
536 | | if (regex->owned) { |
537 | | if (regex->regex) |
538 | | pcre_free(regex->regex); |
539 | | if (regex->sd) |
540 | | pcre_free_study(regex->sd); |
541 | | } |
542 | | free(regex); |
543 | | } |
544 | | } |
545 | | |
546 | | int regex_match(struct regex_data *regex, char const *subject, int partial) |
547 | | { |
548 | | int rc; |
549 | | |
550 | | rc = pcre_exec(regex->regex, get_pcre_extra(regex), subject, |
551 | | strlen(subject), 0, partial ? PCRE_PARTIAL_SOFT : 0, |
552 | | NULL, 0); |
553 | | switch (rc) { |
554 | | case 0: |
555 | | return REGEX_MATCH; |
556 | | case PCRE_ERROR_PARTIAL: |
557 | | return REGEX_MATCH_PARTIAL; |
558 | | case PCRE_ERROR_NOMATCH: |
559 | | return REGEX_NO_MATCH; |
560 | | default: |
561 | | return REGEX_ERROR; |
562 | | } |
563 | | } |
564 | | |
565 | | /* |
566 | | * TODO Replace this compare function with something that actually compares the |
567 | | * regular expressions. |
568 | | * This compare function basically just compares the binary representations of |
569 | | * the automatons, and because this representation contains pointers and |
570 | | * metadata, it can only return a match if regex1 == regex2. |
571 | | * Preferably, this function would be replaced with an algorithm that computes |
572 | | * the equivalence of the automatons systematically. |
573 | | */ |
574 | | int regex_cmp(const struct regex_data *regex1, const struct regex_data *regex2) |
575 | | { |
576 | | int rc; |
577 | | size_t len1, len2; |
578 | | rc = pcre_fullinfo(regex1->regex, NULL, PCRE_INFO_SIZE, &len1); |
579 | | assert(rc == 0); |
580 | | rc = pcre_fullinfo(regex2->regex, NULL, PCRE_INFO_SIZE, &len2); |
581 | | assert(rc == 0); |
582 | | if (len1 != len2 || memcmp(regex1->regex, regex2->regex, len1)) |
583 | | return SELABEL_INCOMPARABLE; |
584 | | |
585 | | return SELABEL_EQUAL; |
586 | | } |
587 | | |
588 | | static struct regex_data *regex_data_create(void) |
589 | | { |
590 | | return (struct regex_data *)calloc(1, sizeof(struct regex_data)); |
591 | | } |
592 | | |
593 | | #endif |
594 | | |
595 | | void regex_format_error(struct regex_error_data const *error_data, char *buffer, |
596 | | size_t buf_size) |
597 | 11.3k | { |
598 | 11.3k | unsigned the_end_length; |
599 | 11.3k | char *ptr; |
600 | 11.3k | int rc = 0; |
601 | 11.3k | size_t pos = 0; |
602 | | |
603 | 11.3k | if (!buffer || !buf_size) |
604 | 0 | return; |
605 | | |
606 | 11.3k | the_end_length = buf_size > 4 ? 4 : buf_size; |
607 | 11.3k | ptr = &buffer[buf_size - the_end_length]; |
608 | 11.3k | rc = snprintf(buffer, buf_size, "REGEX back-end error: "); |
609 | 11.3k | if (rc < 0) |
610 | | /* |
611 | | * If snprintf fails it constitutes a logical error that needs |
612 | | * fixing. |
613 | | */ |
614 | 0 | abort(); |
615 | | |
616 | 11.3k | pos += rc; |
617 | 11.3k | if (pos >= buf_size) |
618 | 0 | goto truncated; |
619 | | |
620 | | /* Return early if there is no error to format */ |
621 | 11.3k | #ifdef USE_PCRE2 |
622 | 11.3k | if (!error_data->error_code) { |
623 | 0 | rc = snprintf(buffer + pos, buf_size - pos, "no error code"); |
624 | 0 | if (rc < 0) |
625 | 0 | abort(); |
626 | 0 | pos += rc; |
627 | 0 | if (pos >= buf_size) |
628 | 0 | goto truncated; |
629 | 0 | return; |
630 | 0 | } |
631 | | #else |
632 | | if (!error_data->error_buffer) { |
633 | | rc = snprintf(buffer + pos, buf_size - pos, "empty error"); |
634 | | if (rc < 0) |
635 | | abort(); |
636 | | pos += rc; |
637 | | if (pos >= buf_size) |
638 | | goto truncated; |
639 | | return; |
640 | | } |
641 | | #endif |
642 | | |
643 | 11.3k | if (error_data->error_offset > 0) { |
644 | 11.3k | #ifdef USE_PCRE2 |
645 | 11.3k | rc = snprintf(buffer + pos, buf_size - pos, |
646 | 11.3k | "At offset %zu: ", error_data->error_offset); |
647 | | #else |
648 | | rc = snprintf(buffer + pos, buf_size - pos, |
649 | | "At offset %d: ", error_data->error_offset); |
650 | | #endif |
651 | 11.3k | if (rc < 0) |
652 | 0 | abort(); |
653 | 11.3k | pos += rc; |
654 | 11.3k | if (pos >= buf_size) |
655 | 0 | goto truncated; |
656 | 11.3k | } |
657 | | |
658 | 11.3k | #ifdef USE_PCRE2 |
659 | 11.3k | rc = pcre2_get_error_message(error_data->error_code, |
660 | 11.3k | (PCRE2_UCHAR *)(buffer + pos), |
661 | 11.3k | buf_size - pos); |
662 | 11.3k | if (rc == PCRE2_ERROR_NOMEMORY) |
663 | 0 | goto truncated; |
664 | | #else |
665 | | rc = snprintf(buffer + pos, buf_size - pos, "%s", |
666 | | error_data->error_buffer); |
667 | | if (rc < 0) |
668 | | abort(); |
669 | | |
670 | | if ((size_t)rc < strlen(error_data->error_buffer)) |
671 | | goto truncated; |
672 | | #endif |
673 | | |
674 | 11.3k | return; |
675 | | |
676 | 11.3k | truncated: |
677 | | /* replace end of string with "..." to indicate that it was truncated */ |
678 | 0 | switch (the_end_length) { |
679 | | /* no break statements, fall-through is intended */ |
680 | 0 | case 4: |
681 | 0 | *ptr++ = '.'; |
682 | | /* FALLTHRU */ |
683 | 0 | case 3: |
684 | 0 | *ptr++ = '.'; |
685 | | /* FALLTHRU */ |
686 | 0 | case 2: |
687 | 0 | *ptr++ = '.'; |
688 | | /* FALLTHRU */ |
689 | 0 | case 1: |
690 | 0 | *ptr++ = '\0'; |
691 | | /* FALLTHRU */ |
692 | 0 | default: |
693 | 0 | break; |
694 | 0 | } |
695 | 0 | } |