Coverage Report

Created: 2025-11-05 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/sudo/lib/eventlog/parse_json.c
Line
Count
Source
1
/*
2
 * SPDX-License-Identifier: ISC
3
 *
4
 * Copyright (c) 2020-2023 Todd C. Miller <Todd.Miller@sudo.ws>
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include <config.h>
20
21
#include <stdio.h>
22
#include <stdlib.h>
23
#ifdef HAVE_STDBOOL_H
24
# include <stdbool.h>
25
#else
26
# include <compat/stdbool.h>
27
#endif /* HAVE_STDBOOL_H */
28
#include <string.h>
29
#include <unistd.h>
30
#include <ctype.h>
31
#include <limits.h>
32
#include <fcntl.h>
33
#include <time.h>
34
35
#include <sudo_compat.h>
36
#include <sudo_debug.h>
37
#include <sudo_eventlog.h>
38
#include <sudo_fatal.h>
39
#include <sudo_gettext.h>
40
#include <sudo_util.h>
41
42
#include <parse_json.h>
43
44
struct json_stack {
45
    unsigned int depth;
46
    unsigned int maxdepth;
47
    struct eventlog_json_object *frames[64];
48
};
49
3.22k
#define JSON_STACK_INTIALIZER(s) { 0, nitems((s).frames) };
50
51
static char *iolog_file;
52
53
static bool
54
json_store_columns(struct json_item *item, struct eventlog *evlog)
55
381
{
56
381
    debug_decl(json_store_columns, SUDO_DEBUG_UTIL);
57
58
381
    if (item->u.number < 1 || item->u.number > INT_MAX) {
59
118
  sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
60
118
      "tty cols %lld: out of range", item->u.number);
61
118
  evlog->columns = 0;
62
118
  debug_return_bool(false);
63
118
    }
64
65
263
    evlog->columns = (int)item->u.number;
66
263
    debug_return_bool(true);
67
263
}
68
69
static bool
70
json_store_command(struct json_item *item, struct eventlog *evlog)
71
194
{
72
194
    debug_decl(json_store_command, SUDO_DEBUG_UTIL);
73
74
    /*
75
     * Note: struct eventlog must store command + args.
76
     *       We don't have argv yet so we append the args later.
77
     */
78
194
    free(evlog->command);
79
194
    evlog->command = item->u.string;
80
194
    item->u.string = NULL;
81
194
    debug_return_bool(true);
82
194
}
83
84
static bool
85
json_store_dumped_core(struct json_item *item, struct eventlog *evlog)
86
194
{
87
194
    debug_decl(json_store_dumped_core, SUDO_DEBUG_UTIL);
88
89
194
    evlog->dumped_core = item->u.boolean;
90
194
    debug_return_bool(true);
91
194
}
92
93
static bool
94
json_store_exit_value(struct json_item *item, struct eventlog *evlog)
95
367
{
96
367
    debug_decl(json_store_exit_value, SUDO_DEBUG_UTIL);
97
98
367
    if (item->u.number < 0 || item->u.number > INT_MAX) {
99
114
  sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
100
114
      "exit value %lld: out of range", item->u.number);
101
114
  evlog->exit_value = -1;
102
114
  debug_return_bool(false);
103
114
    }
104
105
253
    evlog->exit_value = (int)item->u.number;
106
253
    debug_return_bool(true);
107
253
}
108
109
static bool
110
json_store_iolog_file(struct json_item *item, struct eventlog *evlog)
111
367
{
112
367
    debug_decl(json_store_iolog_file, SUDO_DEBUG_UTIL);
113
114
    /* Do set evlog->iolog_file directly, it is a substring of iolog_path. */
115
367
    free(iolog_file);
116
367
    iolog_file = item->u.string;
117
367
    item->u.string = NULL;
118
367
    debug_return_bool(true);
119
367
}
120
121
static bool
122
json_store_iolog_path(struct json_item *item, struct eventlog *evlog)
123
366
{
124
366
    debug_decl(json_store_iolog_path, SUDO_DEBUG_UTIL);
125
126
366
    free(evlog->iolog_path);
127
366
    evlog->iolog_path = item->u.string;
128
366
    item->u.string = NULL;
129
366
    debug_return_bool(true);
130
366
}
131
132
static bool
133
json_store_lines(struct json_item *item, struct eventlog *evlog)
134
386
{
135
386
    debug_decl(json_store_lines, SUDO_DEBUG_UTIL);
136
137
386
    if (item->u.number < 1 || item->u.number > INT_MAX) {
138
124
  sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
139
124
      "tty lines %lld: out of range", item->u.number);
140
124
  evlog->lines = 0;
141
124
  debug_return_bool(false);
142
124
    }
143
144
262
    evlog->lines = (int)item->u.number;
145
262
    debug_return_bool(true);
146
262
}
147
148
static bool
149
json_store_peeraddr(struct json_item *item, struct eventlog *evlog)
150
195
{
151
195
    debug_decl(json_store_peeraddr, SUDO_DEBUG_UTIL);
152
153
195
    free(evlog->peeraddr);
154
195
    evlog->peeraddr = item->u.string;
155
195
    item->u.string = NULL;
156
195
    debug_return_bool(true);
157
195
}
158
159
static char **
160
json_array_to_strvec(struct eventlog_json_object *array)
161
2.65k
{
162
2.65k
    struct json_item *item;
163
2.65k
    size_t len = 0;
164
2.65k
    char **ret;
165
2.65k
    debug_decl(json_array_to_strvec, SUDO_DEBUG_UTIL);
166
167
2.77k
    TAILQ_FOREACH(item, &array->items, entries) {
168
  /* Can only convert arrays of string. */
169
2.77k
  if (item->type != JSON_STRING) {
170
2
      sudo_warnx(U_("expected JSON_STRING, got %d"), item->type);
171
2
      debug_return_ptr(NULL);
172
2
  }
173
  /* Prevent integer overflow. */
174
2.77k
  if (++len == INT_MAX) {
175
0
      sudo_warnx("%s", U_("JSON_ARRAY too large"));
176
0
      debug_return_ptr(NULL);
177
0
  }
178
2.77k
    }
179
2.65k
    if ((ret = reallocarray(NULL, len + 1, sizeof(char *))) == NULL) {
180
0
  sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
181
0
  debug_return_ptr(NULL);
182
0
    }
183
2.65k
    len = 0;
184
2.77k
    TAILQ_FOREACH(item, &array->items, entries) {
185
2.77k
  ret[len++] = item->u.string;
186
2.77k
  item->u.string = NULL;
187
2.77k
    }
188
2.65k
    ret[len] = NULL;
189
190
2.65k
    debug_return_ptr(ret);
191
2.65k
}
192
193
static bool
194
json_store_submitenv(struct json_item *item, struct eventlog *evlog)
195
614
{
196
614
    size_t i;
197
614
    debug_decl(json_store_submitenv, SUDO_DEBUG_UTIL);
198
199
614
    if (evlog->submitenv != NULL) {
200
972
  for (i = 0; evlog->submitenv[i] != NULL; i++)
201
396
      free(evlog->submitenv[i]);
202
576
  free(evlog->submitenv);
203
576
    }
204
614
    evlog->submitenv = json_array_to_strvec(&item->u.child);
205
206
614
    debug_return_bool(evlog->submitenv != NULL);
207
614
}
208
209
static bool
210
json_store_runargv(struct json_item *item, struct eventlog *evlog)
211
614
{
212
614
    size_t i;
213
614
    debug_decl(json_store_runargv, SUDO_DEBUG_UTIL);
214
215
614
    if (evlog->runargv != NULL) {
216
972
  for (i = 0; evlog->runargv[i] != NULL; i++)
217
396
      free(evlog->runargv[i]);
218
576
  free(evlog->runargv);
219
576
    }
220
614
    evlog->runargv = json_array_to_strvec(&item->u.child);
221
222
614
    debug_return_bool(evlog->runargv != NULL);
223
614
}
224
225
static bool
226
json_store_runenv(struct json_item *item, struct eventlog *evlog)
227
813
{
228
813
    size_t i;
229
813
    debug_decl(json_store_runenv, SUDO_DEBUG_UTIL);
230
231
813
    if (evlog->runenv != NULL) {
232
1.34k
  for (i = 0; evlog->runenv[i] != NULL; i++)
233
582
      free(evlog->runenv[i]);
234
762
  free(evlog->runenv);
235
762
    }
236
813
    evlog->runenv = json_array_to_strvec(&item->u.child);
237
238
813
    debug_return_bool(evlog->runenv != NULL);
239
813
}
240
241
static bool
242
json_store_runenv_override(struct json_item *item, struct eventlog *evlog)
243
614
{
244
614
    size_t i;
245
614
    debug_decl(json_store_runenv_override, SUDO_DEBUG_UTIL);
246
247
614
    if (evlog->env_add != NULL) {
248
972
  for (i = 0; evlog->env_add[i] != NULL; i++)
249
396
      free(evlog->env_add[i]);
250
576
  free(evlog->env_add);
251
576
    }
252
614
    evlog->env_add = json_array_to_strvec(&item->u.child);
253
254
614
    debug_return_bool(evlog->env_add != NULL);
255
614
}
256
257
static bool
258
json_store_rungid(struct json_item *item, struct eventlog *evlog)
259
194
{
260
194
    debug_decl(json_store_rungid, SUDO_DEBUG_UTIL);
261
262
194
    evlog->rungid = (gid_t)item->u.number;
263
194
    debug_return_bool(true);
264
194
}
265
266
static bool
267
json_store_rungroup(struct json_item *item, struct eventlog *evlog)
268
194
{
269
194
    debug_decl(json_store_rungroup, SUDO_DEBUG_UTIL);
270
271
194
    free(evlog->rungroup);
272
194
    evlog->rungroup = item->u.string;
273
194
    item->u.string = NULL;
274
194
    debug_return_bool(true);
275
194
}
276
277
static bool
278
json_store_runuid(struct json_item *item, struct eventlog *evlog)
279
194
{
280
194
    debug_decl(json_store_runuid, SUDO_DEBUG_UTIL);
281
282
194
    evlog->runuid = (uid_t)item->u.number;
283
194
    debug_return_bool(true);
284
194
}
285
286
static bool
287
json_store_runuser(struct json_item *item, struct eventlog *evlog)
288
194
{
289
194
    debug_decl(json_store_runuser, SUDO_DEBUG_UTIL);
290
291
194
    free(evlog->runuser);
292
194
    evlog->runuser = item->u.string;
293
194
    item->u.string = NULL;
294
194
    debug_return_bool(true);
295
194
}
296
297
static bool
298
json_store_runchroot(struct json_item *item, struct eventlog *evlog)
299
194
{
300
194
    debug_decl(json_store_runchroot, SUDO_DEBUG_UTIL);
301
302
194
    free(evlog->runchroot);
303
194
    evlog->runchroot = item->u.string;
304
194
    item->u.string = NULL;
305
194
    debug_return_bool(true);
306
194
}
307
308
static bool
309
json_store_runcwd(struct json_item *item, struct eventlog *evlog)
310
194
{
311
194
    debug_decl(json_store_runcwd, SUDO_DEBUG_UTIL);
312
313
194
    free(evlog->runcwd);
314
194
    evlog->runcwd = item->u.string;
315
194
    item->u.string = NULL;
316
194
    debug_return_bool(true);
317
194
}
318
319
static bool
320
json_store_signal(struct json_item *item, struct eventlog *evlog)
321
194
{
322
194
    debug_decl(json_store_signal, SUDO_DEBUG_UTIL);
323
324
194
    free(evlog->signal_name);
325
194
    evlog->signal_name = item->u.string;
326
194
    item->u.string = NULL;
327
194
    debug_return_bool(true);
328
194
}
329
330
static bool
331
json_store_source(struct json_item *item, struct eventlog *evlog)
332
194
{
333
194
    debug_decl(json_store_source, SUDO_DEBUG_UTIL);
334
335
194
    free(evlog->source);
336
194
    evlog->source = item->u.string;
337
194
    item->u.string = NULL;
338
194
    debug_return_bool(true);
339
194
}
340
341
static bool
342
json_store_submitcwd(struct json_item *item, struct eventlog *evlog)
343
194
{
344
194
    debug_decl(json_store_submitcwd, SUDO_DEBUG_UTIL);
345
346
194
    free(evlog->cwd);
347
194
    evlog->cwd = item->u.string;
348
194
    item->u.string = NULL;
349
194
    debug_return_bool(true);
350
194
}
351
352
static bool
353
json_store_submithost(struct json_item *item, struct eventlog *evlog)
354
194
{
355
194
    debug_decl(json_store_submithost, SUDO_DEBUG_UTIL);
356
357
194
    free(evlog->submithost);
358
194
    evlog->submithost = item->u.string;
359
194
    item->u.string = NULL;
360
194
    debug_return_bool(true);
361
194
}
362
363
static bool
364
json_store_submituser(struct json_item *item, struct eventlog *evlog)
365
194
{
366
194
    debug_decl(json_store_submituser, SUDO_DEBUG_UTIL);
367
368
194
    free(evlog->submituser);
369
194
    evlog->submituser = item->u.string;
370
194
    item->u.string = NULL;
371
194
    debug_return_bool(true);
372
194
}
373
374
static bool
375
json_store_submitgroup(struct json_item *item, struct eventlog *evlog)
376
194
{
377
194
    debug_decl(json_store_submitgroup, SUDO_DEBUG_UTIL);
378
379
194
    free(evlog->submitgroup);
380
194
    evlog->submitgroup = item->u.string;
381
194
    item->u.string = NULL;
382
194
    debug_return_bool(true);
383
194
}
384
385
static bool
386
json_store_timespec(struct json_item *item, struct timespec *ts)
387
1.75k
{
388
1.75k
    struct eventlog_json_object *object;
389
1.75k
    debug_decl(json_store_timespec, SUDO_DEBUG_UTIL);
390
391
1.75k
    object = &item->u.child;
392
4.31k
    TAILQ_FOREACH(item, &object->items, entries) {
393
4.31k
  if (item->type != JSON_NUMBER)
394
1.74k
      continue;
395
2.56k
  if (strcmp(item->name, "seconds") == 0) {
396
582
      ts->tv_sec = (time_t)item->u.number;
397
582
      continue;
398
582
  }
399
1.98k
  if (strcmp(item->name, "nanoseconds") == 0) {
400
711
      ts->tv_nsec = (long)item->u.number;
401
711
      continue;
402
711
  }
403
1.98k
    }
404
1.75k
    debug_return_bool(true);
405
1.75k
}
406
407
static bool
408
json_store_iolog_offset(struct json_item *item, struct eventlog *evlog)
409
582
{
410
582
    return json_store_timespec(item, &evlog->iolog_offset);
411
582
}
412
413
static bool
414
json_store_run_time(struct json_item *item, struct eventlog *evlog)
415
582
{
416
582
    return json_store_timespec(item, &evlog->run_time);
417
582
}
418
419
static bool
420
json_store_timestamp(struct json_item *item, struct eventlog *evlog)
421
586
{
422
586
    return json_store_timespec(item, &evlog->event_time);
423
586
}
424
425
static bool
426
json_store_ttyname(struct json_item *item, struct eventlog *evlog)
427
194
{
428
194
    debug_decl(json_store_ttyname, SUDO_DEBUG_UTIL);
429
430
194
    free(evlog->ttyname);
431
194
    evlog->ttyname = item->u.string;
432
194
    item->u.string = NULL;
433
194
    debug_return_bool(true);
434
194
}
435
436
static bool
437
json_store_uuid(struct json_item *item, struct eventlog *evlog)
438
412
{
439
412
    bool ret = false;
440
412
    debug_decl(json_store_uuid, SUDO_DEBUG_UTIL);
441
442
412
    if (strlen(item->u.string) == sizeof(evlog->uuid_str) - 1) {
443
380
  memcpy(evlog->uuid_str, item->u.string, sizeof(evlog->uuid_str));
444
380
  ret = true;
445
380
    }
446
412
    free(item->u.string);
447
412
    item->u.string = NULL;
448
412
    debug_return_bool(ret);
449
412
}
450
451
static struct evlog_json_key {
452
    const char *name;
453
    enum json_value_type type;
454
    bool (*setter)(struct json_item *, struct eventlog *);
455
} evlog_json_keys[] = {
456
    { "columns", JSON_NUMBER, json_store_columns },
457
    { "command", JSON_STRING, json_store_command },
458
    { "dumped_core", JSON_BOOL, json_store_dumped_core },
459
    { "exit_value", JSON_NUMBER, json_store_exit_value },
460
    { "iolog_file", JSON_STRING, json_store_iolog_file },
461
    { "iolog_path", JSON_STRING, json_store_iolog_path },
462
    { "iolog_offset", JSON_OBJECT, json_store_iolog_offset },
463
    { "lines", JSON_NUMBER, json_store_lines },
464
    { "peeraddr", JSON_STRING, json_store_peeraddr },
465
    { "run_time", JSON_OBJECT, json_store_run_time },
466
    { "runargv", JSON_ARRAY, json_store_runargv },
467
    { "runenv", JSON_ARRAY, json_store_runenv },
468
    { "runenv_override", JSON_ARRAY, json_store_runenv_override },
469
    { "rungid", JSON_ID, json_store_rungid },
470
    { "rungroup", JSON_STRING, json_store_rungroup },
471
    { "runuid", JSON_ID, json_store_runuid },
472
    { "runuser", JSON_STRING, json_store_runuser },
473
    { "runchroot", JSON_STRING, json_store_runchroot },
474
    { "runcwd", JSON_STRING, json_store_runcwd },
475
    { "source", JSON_STRING, json_store_source },
476
    { "signal", JSON_STRING, json_store_signal },
477
    { "submitcwd", JSON_STRING, json_store_submitcwd },
478
    { "submitenv", JSON_ARRAY, json_store_submitenv },
479
    { "submithost", JSON_STRING, json_store_submithost },
480
    { "submitgroup", JSON_STRING, json_store_submitgroup },
481
    { "submituser", JSON_STRING, json_store_submituser },
482
    { "timestamp", JSON_OBJECT, json_store_timestamp },
483
    { "ttyname", JSON_STRING, json_store_ttyname },
484
    { "uuid", JSON_STRING, json_store_uuid },
485
    { NULL }
486
};
487
488
static struct json_item *
489
new_json_item(enum json_value_type type, char *name, unsigned int lineno)
490
35.1k
{
491
35.1k
    struct json_item *item;
492
35.1k
    debug_decl(new_json_item, SUDO_DEBUG_UTIL);
493
494
35.1k
    if ((item = malloc(sizeof(*item))) == NULL)  {
495
0
  sudo_warnx(U_("%s: %s"), __func__,
496
0
      U_("unable to allocate memory"));
497
0
  debug_return_ptr(NULL);
498
0
    }
499
35.1k
    item->name = name;
500
35.1k
    item->type = type;
501
35.1k
    item->lineno = lineno;
502
503
35.1k
    debug_return_ptr(item);
504
35.1k
}
505
506
static char *
507
json_parse_string(char **strp)
508
23.0k
{
509
23.0k
    char *dst, *end, *ret, *src = *strp + 1;
510
23.0k
    size_t len;
511
23.0k
    debug_decl(json_parse_string, SUDO_DEBUG_UTIL);
512
513
4.86M
    for (end = src; *end != '"' && *end != '\0'; end++) {
514
4.83M
  if (end[0] == '\\' && end[1] == '"')
515
355
      end++;
516
4.83M
    }
517
23.0k
    if (*end != '"') {
518
87
  sudo_warnx("%s", U_("missing double quote in name"));
519
87
  debug_return_str(NULL);
520
87
    }
521
22.9k
    len = (size_t)(end - src);
522
523
    /* Copy string, flattening escaped chars. */
524
22.9k
    dst = ret = malloc(len + 1);
525
22.9k
    if (dst == NULL) {
526
0
  sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
527
0
  debug_return_str(NULL);
528
0
    }
529
4.82M
    while (src < end) {
530
4.79M
  int ch = *src++;
531
4.79M
  if (ch == '\\') {
532
2.87k
      switch (*src) {
533
198
      case 'b':
534
198
    ch = '\b';
535
198
    break;
536
194
      case 'f':
537
194
    ch = '\f';
538
194
    break;
539
202
      case 'n':
540
202
    ch = '\n';
541
202
    break;
542
194
      case 'r':
543
194
    ch = '\r';
544
194
    break;
545
194
      case 't':
546
194
    ch = '\t';
547
194
    break;
548
1.55k
      case 'u':
549
    /* Only currently handles 8-bit ASCII. */
550
1.55k
    if (src[1] == '0' && src[2] == '0') {
551
986
        ch = sudo_hexchar(&src[3]);
552
986
        if (ch != -1) {
553
264
      src += 4;
554
264
      break;
555
264
        }
556
986
    }
557
    /* Not in \u00XX format. */
558
1.29k
    FALLTHROUGH;
559
1.45k
      case '"':
560
1.60k
      case '\\':
561
1.62k
      default:
562
    /* Note: a bare \ at the end of a string will be removed. */
563
1.62k
    ch = *src;
564
1.62k
    break;
565
2.87k
      }
566
2.87k
      src++;
567
2.87k
  }
568
4.79M
  *dst++ = (char)ch;
569
4.79M
    }
570
22.9k
    *dst = '\0';
571
572
    /* Trim trailing whitespace. */
573
23.1k
    do {
574
23.1k
  end++;
575
23.1k
    } while (isspace((unsigned char)*end));
576
22.9k
    *strp = end;
577
578
22.9k
    debug_return_str(ret);
579
22.9k
}
580
581
static void
582
free_json_items(struct json_item_list *items)
583
17.7k
{
584
17.7k
    struct json_item *item;
585
17.7k
    debug_decl(free_json_items, SUDO_DEBUG_UTIL);
586
587
52.8k
    while ((item = TAILQ_FIRST(items)) != NULL) {
588
35.0k
  TAILQ_REMOVE(items, item, entries);
589
35.0k
  switch (item->type) {
590
7.79k
  case JSON_STRING:
591
7.79k
      free(item->u.string);
592
7.79k
      break;
593
8.29k
  case JSON_ARRAY:
594
14.5k
  case JSON_OBJECT:
595
14.5k
      free_json_items(&item->u.child.items);
596
14.5k
      break;
597
0
  case JSON_ID:
598
8.74k
  case JSON_NUMBER:
599
11.3k
  case JSON_BOOL:
600
12.7k
  case JSON_NULL:
601
      /* Nothing to free. */
602
12.7k
      break;
603
0
  default:
604
0
      sudo_warnx("%s: internal error, invalid JSON type %d",
605
0
    __func__, item->type);
606
0
      break;
607
35.0k
  }
608
35.0k
  free(item->name);
609
35.0k
  free(item);
610
35.0k
    }
611
612
17.7k
    debug_return;
613
17.7k
}
614
615
void
616
eventlog_json_free(struct eventlog_json_object *root)
617
3.22k
{
618
3.22k
    debug_decl(eventlog_json_free, SUDO_DEBUG_UTIL);
619
3.22k
    if (root != NULL) {
620
3.22k
  free_json_items(&root->items);
621
3.22k
  free(root);
622
3.22k
    }
623
3.22k
    debug_return;
624
3.22k
}
625
626
bool
627
eventlog_json_parse(struct eventlog_json_object *object, struct eventlog *evlog)
628
1.89k
{
629
1.89k
    struct json_item *item;
630
1.89k
    bool ret = false;
631
1.89k
    debug_decl(eventlog_json_parse, SUDO_DEBUG_UTIL);
632
633
    /* First object holds all the actual data. */
634
1.89k
    item = TAILQ_FIRST(&object->items);
635
1.89k
    if (item == NULL) {
636
16
  sudo_warnx("%s", U_("missing JSON_OBJECT"));
637
16
  goto done;
638
16
    }
639
1.88k
    if (item->type != JSON_OBJECT) {
640
0
  sudo_warnx(U_("expected JSON_OBJECT, got %d"), item->type);
641
0
  goto done;
642
0
    }
643
1.88k
    object = &item->u.child;
644
645
10.3k
    TAILQ_FOREACH(item, &object->items, entries) {
646
10.3k
  struct evlog_json_key *key;
647
648
  /* expecting key:value pairs */
649
10.3k
  if (item->name == NULL) {
650
12
      sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
651
12
    "%s: missing object name", __func__);
652
12
      goto done;
653
12
  }
654
655
  /* lookup name */
656
154k
  for (key = evlog_json_keys; key->name != NULL; key++) {
657
154k
      if (strcmp(item->name, key->name) == 0)
658
9.80k
    break;
659
154k
  }
660
10.3k
  if (key->name == NULL) {
661
506
      sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
662
506
    "%s: unknown key %s", __func__, item->name);
663
9.80k
  } else if (key->type != item->type &&
664
399
    (key->type != JSON_ID || item->type != JSON_NUMBER)) {
665
11
      sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
666
11
    "%s: key mismatch %s type %d, expected %d", __func__,
667
11
    item->name, item->type, key->type);
668
11
      goto done;
669
9.78k
  } else {
670
      /* Matched name and type. */
671
9.78k
      if (!key->setter(item, evlog)) {
672
390
    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
673
390
        "unable to store %s", key->name);
674
390
    goto done;
675
390
      }
676
9.78k
  }
677
10.3k
    }
678
679
    /*
680
     * iolog_file must be a substring of iolog_path.
681
     */
682
1.46k
    if (iolog_file != NULL && evlog->iolog_path != NULL) {
683
172
  const size_t filelen = strlen(iolog_file);
684
172
  const size_t pathlen = strlen(evlog->iolog_path);
685
172
  if (filelen <= pathlen) {
686
142
      const char *cp = &evlog->iolog_path[pathlen - filelen];
687
142
      if (strcmp(cp, iolog_file) == 0) {
688
4
    evlog->iolog_file = cp;
689
4
      }
690
142
  }
691
172
    }
692
693
1.46k
    ret = true;
694
695
1.89k
done:
696
1.89k
    free(iolog_file);
697
1.89k
    iolog_file = NULL;
698
699
1.89k
    debug_return_bool(ret);
700
1.89k
}
701
702
static bool
703
json_insert_bool(struct json_item_list *items, char *name, bool value,
704
    unsigned int lineno)
705
2.64k
{
706
2.64k
    struct json_item *item;
707
2.64k
    debug_decl(json_insert_bool, SUDO_DEBUG_UTIL);
708
709
2.64k
    if ((item = new_json_item(JSON_BOOL, name, lineno)) == NULL)
710
0
  debug_return_bool(false);
711
2.64k
    item->u.boolean = value;
712
2.64k
    TAILQ_INSERT_TAIL(items, item, entries);
713
714
2.64k
    debug_return_bool(true);
715
2.64k
}
716
717
static bool
718
json_insert_null(struct json_item_list *items, char *name, unsigned int lineno)
719
1.34k
{
720
1.34k
    struct json_item *item;
721
1.34k
    debug_decl(json_insert_null, SUDO_DEBUG_UTIL);
722
723
1.34k
    if ((item = new_json_item(JSON_NULL, name, lineno)) == NULL)
724
0
  debug_return_bool(false);
725
1.34k
    TAILQ_INSERT_TAIL(items, item, entries);
726
727
1.34k
    debug_return_bool(true);
728
1.34k
}
729
730
static bool
731
json_insert_num(struct json_item_list *items, char *name, long long value,
732
    unsigned int lineno)
733
8.74k
{
734
8.74k
    struct json_item *item;
735
8.74k
    debug_decl(json_insert_num, SUDO_DEBUG_UTIL);
736
737
8.74k
    if ((item = new_json_item(JSON_NUMBER, name, lineno)) == NULL)
738
0
  debug_return_bool(false);
739
8.74k
    item->u.number = value;
740
8.74k
    TAILQ_INSERT_TAIL(items, item, entries);
741
742
8.74k
    debug_return_bool(true);
743
8.74k
}
744
745
static bool
746
json_insert_str(struct json_item_list *items, char *name, char **strp,
747
    unsigned int lineno)
748
7.82k
{
749
7.82k
    struct json_item *item;
750
7.82k
    debug_decl(json_insert_str, SUDO_DEBUG_UTIL);
751
752
7.82k
    if ((item = new_json_item(JSON_STRING, name, lineno)) == NULL)
753
0
  debug_return_bool(false);
754
7.82k
    item->u.string = json_parse_string(strp);
755
7.82k
    if (item->u.string == NULL) {
756
28
  free(item);
757
28
  debug_return_bool(false);
758
28
    }
759
7.79k
    TAILQ_INSERT_TAIL(items, item, entries);
760
761
7.79k
    debug_return_bool(true);
762
7.79k
}
763
764
static struct eventlog_json_object *
765
json_stack_push(struct json_stack *stack, struct json_item_list *items,
766
    struct eventlog_json_object *frame, enum json_value_type type, char *name,
767
    unsigned int lineno)
768
14.5k
{
769
14.5k
    struct json_item *item;
770
14.5k
    debug_decl(json_stack_push, SUDO_DEBUG_UTIL);
771
772
    /* We limit the stack size rather than expanding it. */
773
14.5k
    if (stack->depth >= stack->maxdepth) {
774
7
  sudo_warnx(U_("json stack exhausted (max %u frames)"), stack->maxdepth);
775
7
  debug_return_ptr(NULL);
776
7
    }
777
778
    /* Allocate a new item and insert it into the list. */
779
14.5k
    if ((item = new_json_item(type, name, lineno)) == NULL)
780
0
  debug_return_ptr(NULL);
781
14.5k
    TAILQ_INIT(&item->u.child.items);
782
14.5k
    item->u.child.parent = item;
783
14.5k
    TAILQ_INSERT_TAIL(items, item, entries);
784
785
    /* Push the current frame onto the stack (depth check performed above). */
786
14.5k
    stack->frames[stack->depth++] = frame;
787
788
    /* Return the new frame */
789
14.5k
    debug_return_ptr(&item->u.child);
790
14.5k
}
791
792
/* Only expect a value if a name is defined or we are in an array. */
793
36.0k
#define expect_value (name != NULL || (frame->parent != NULL && frame->parent->type == JSON_ARRAY))
794
795
struct eventlog_json_object *
796
eventlog_json_read(FILE *fp, const char *filename)
797
3.22k
{
798
3.22k
    struct eventlog_json_object *frame, *root;
799
3.22k
    struct json_stack stack = JSON_STACK_INTIALIZER(stack);
800
3.22k
    unsigned int lineno = 0;
801
3.22k
    char *name = NULL;
802
3.22k
    char *cp, *line = NULL;
803
3.22k
    size_t len, linesize = 0;
804
3.22k
    ssize_t linelen;
805
3.22k
    bool saw_comma = false;
806
3.22k
    long long num;
807
3.22k
    char ch;
808
3.22k
    debug_decl(eventlog_json_read, SUDO_DEBUG_UTIL);
809
810
3.22k
    root = malloc(sizeof(*root));
811
3.22k
    if (root == NULL)
812
0
  goto bad;
813
814
3.22k
    root->parent = NULL;
815
3.22k
    TAILQ_INIT(&root->items);
816
817
3.22k
    frame = root;
818
6.76k
    while ((linelen = getdelim(&line, &linesize, '\n', fp)) != -1) {
819
4.25k
  char *ep = line + linelen - 1;
820
4.25k
  cp = line;
821
822
4.25k
  lineno++;
823
824
  /* Trim trailing whitespace. */
825
5.13k
  while (ep > cp && isspace((unsigned char)*ep))
826
887
      ep--;
827
4.25k
  ep[1] = '\0';
828
829
64.3k
  for (;;) {
830
64.3k
      const char *errstr;
831
832
      /* Trim leading whitespace, skip blank lines. */
833
64.3k
      while (isspace((unsigned char)*cp))
834
1.33k
    cp++;
835
836
      /* Check for comma separator and strip it out. */
837
64.3k
      if (*cp == ',') {
838
23.8k
    saw_comma = true;
839
23.8k
    cp++;
840
23.8k
    while (isspace((unsigned char)*cp))
841
201
        cp++;
842
23.8k
      }
843
844
      /* End of line? */
845
64.3k
      if (*cp == '\0')
846
3.54k
    break;
847
848
60.7k
      switch (*cp) {
849
6.26k
      case '{':
850
6.26k
    if (name == NULL && frame->parent != NULL) {
851
2
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
852
2
      U_("objects must consist of name:value pairs"));
853
2
        goto bad;
854
2
    }
855
6.26k
    if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
856
7
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
857
7
      U_("missing separator between values"));
858
7
        goto bad;
859
7
    }
860
6.25k
    cp++;
861
6.25k
    saw_comma = false;
862
6.25k
    frame = json_stack_push(&stack, &frame->items, frame,
863
6.25k
        JSON_OBJECT, name, lineno);
864
6.25k
    if (frame == NULL)
865
3
        goto bad;
866
6.25k
    name = NULL;
867
6.25k
    break;
868
4.61k
      case '}':
869
4.61k
    if (stack.depth == 0 || frame->parent == NULL ||
870
4.61k
      frame->parent->type != JSON_OBJECT) {
871
9
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
872
9
      U_("unmatched close brace"));
873
9
        goto bad;
874
9
    }
875
4.60k
    cp++;
876
4.60k
    frame = stack.frames[--stack.depth];
877
4.60k
    saw_comma = false;
878
4.60k
    break;
879
8.29k
      case '[':
880
8.29k
    if (frame->parent == NULL) {
881
        /* Must have an enclosing object. */
882
1
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
883
1
      U_("unexpected array"));
884
1
        goto bad;
885
1
    }
886
8.29k
    if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
887
1
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
888
1
      U_("missing separator between values"));
889
1
        goto bad;
890
1
    }
891
8.29k
    cp++;
892
8.29k
    saw_comma = false;
893
8.29k
    frame = json_stack_push(&stack, &frame->items, frame,
894
8.29k
        JSON_ARRAY, name, lineno);
895
8.29k
    if (frame == NULL)
896
4
        goto bad;
897
8.29k
    name = NULL;
898
8.29k
    break;
899
5.42k
      case ']':
900
5.42k
    if (stack.depth == 0 || frame->parent == NULL ||
901
5.42k
      frame->parent->type != JSON_ARRAY) {
902
2
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
903
2
      U_("unmatched close bracket"));
904
2
        goto bad;
905
2
    }
906
5.42k
    cp++;
907
5.42k
    frame = stack.frames[--stack.depth];
908
5.42k
    saw_comma = false;
909
5.42k
    break;
910
23.0k
      case '"':
911
23.0k
    if (frame->parent == NULL) {
912
        /* Must have an enclosing object. */
913
2
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
914
2
      U_("unexpected string"));
915
2
        goto bad;
916
2
    }
917
918
23.0k
    if (!expect_value) {
919
        /* Parse "name": */
920
15.2k
        if ((name = json_parse_string(&cp)) == NULL)
921
59
      goto bad;
922
        /* TODO: allow colon on next line? */
923
15.1k
        if (*cp != ':') {
924
187
      sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
925
187
          U_("missing colon after name"));
926
187
      goto bad;
927
187
        }
928
14.9k
        cp++;
929
14.9k
    } else {
930
7.84k
        if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
931
15
      sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
932
15
          U_("missing separator between values"));
933
15
      goto bad;
934
15
        }
935
7.82k
        saw_comma = false;
936
7.82k
        if (!json_insert_str(&frame->items, name, &cp, lineno))
937
28
      goto bad;
938
7.79k
        name = NULL;
939
7.79k
    }
940
22.7k
    break;
941
22.7k
      case 't':
942
1.46k
    if (strncmp(cp, "true", sizeof("true") - 1) != 0)
943
27
        goto parse_error;
944
1.44k
    if (!expect_value) {
945
2
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
946
2
      U_("unexpected boolean"));
947
2
        goto bad;
948
2
    }
949
1.43k
    cp += sizeof("true") - 1;
950
1.43k
    if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
951
12
        goto parse_error;
952
1.42k
    if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
953
16
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
954
16
      U_("missing separator between values"));
955
16
        goto bad;
956
16
    }
957
1.41k
    saw_comma = false;
958
959
1.41k
    if (!json_insert_bool(&frame->items, name, true, lineno))
960
0
        goto bad;
961
1.41k
    name = NULL;
962
1.41k
    break;
963
1.30k
      case 'f':
964
1.30k
    if (strncmp(cp, "false", sizeof("false") - 1) != 0)
965
37
        goto parse_error;
966
1.26k
    if (!expect_value) {
967
2
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
968
2
      U_("unexpected boolean"));
969
2
        goto bad;
970
2
    }
971
1.26k
    cp += sizeof("false") - 1;
972
1.26k
    if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
973
14
        goto parse_error;
974
1.24k
    if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
975
15
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
976
15
      U_("missing separator between values"));
977
15
        goto bad;
978
15
    }
979
1.23k
    saw_comma = false;
980
981
1.23k
    if (!json_insert_bool(&frame->items, name, false, lineno))
982
0
        goto bad;
983
1.23k
    name = NULL;
984
1.23k
    break;
985
1.40k
      case 'n':
986
1.40k
    if (strncmp(cp, "null", sizeof("null") - 1) != 0)
987
28
        goto parse_error;
988
1.37k
    if (!expect_value) {
989
2
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
990
2
      U_("unexpected null"));
991
2
        goto bad;
992
2
    }
993
1.37k
    cp += sizeof("null") - 1;
994
1.37k
    if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
995
12
        goto parse_error;
996
1.36k
    if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
997
17
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
998
17
      U_("missing separator between values"));
999
17
        goto bad;
1000
17
    }
1001
1.34k
    saw_comma = false;
1002
1003
1.34k
    if (!json_insert_null(&frame->items, name, lineno))
1004
0
        goto bad;
1005
1.34k
    name = NULL;
1006
1.34k
    break;
1007
5.31k
      case '+': case '-': case '0': case '1': case '2': case '3':
1008
8.93k
      case '4': case '5': case '6': case '7': case '8': case '9':
1009
8.93k
    if (!expect_value) {
1010
15
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
1011
15
      U_("unexpected number"));
1012
15
        goto bad;
1013
15
    }
1014
    /* XXX - strtonumx() would be simpler here. */
1015
8.91k
    len = strcspn(cp, " \f\n\r\t\v,");
1016
8.91k
    ch = cp[len];
1017
8.91k
    cp[len] = '\0';
1018
8.91k
    if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
1019
9
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
1020
9
      U_("missing separator between values"));
1021
9
        goto bad;
1022
9
    }
1023
8.90k
    saw_comma = false;
1024
8.90k
    num = sudo_strtonum(cp, LLONG_MIN, LLONG_MAX, &errstr);
1025
8.90k
    if (errstr != NULL) {
1026
157
        sudo_warnx("%s:%u:%td: %s: %s", filename, lineno, cp - line,
1027
157
      cp, U_(errstr));
1028
157
        goto bad;
1029
157
    }
1030
8.74k
    cp += len;
1031
8.74k
    *cp = ch;
1032
1033
8.74k
    if (!json_insert_num(&frame->items, name, num, lineno))
1034
0
        goto bad;
1035
8.74k
    name = NULL;
1036
8.74k
    break;
1037
18
      default:
1038
18
    goto parse_error;
1039
60.7k
      }
1040
60.7k
  }
1041
4.25k
    }
1042
2.51k
    if (stack.depth != 0) {
1043
620
  frame = stack.frames[stack.depth - 1];
1044
620
  if (frame->parent == NULL || frame->parent->type == JSON_OBJECT) {
1045
547
      sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
1046
547
    U_("unmatched close brace"));
1047
547
  } else {
1048
73
      sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
1049
73
    U_("unmatched close bracket"));
1050
73
  }
1051
620
  goto bad;
1052
620
    }
1053
1054
1.89k
    goto done;
1055
1056
1.89k
parse_error:
1057
148
    sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, U_("parse error"));
1058
1.32k
bad:
1059
1.32k
    eventlog_json_free(root);
1060
1.32k
    root = NULL;
1061
3.22k
done:
1062
3.22k
    free(line);
1063
3.22k
    free(name);
1064
1065
3.22k
    debug_return_ptr(root);
1066
3.22k
}