Coverage Report

Created: 2025-02-26 06:46

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