Coverage Report

Created: 2026-08-31 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 **)&regex->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 &regex->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
}