Coverage Report

Created: 2025-07-11 06:58

/src/sudo/lib/util/lbuf.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * SPDX-License-Identifier: ISC
3
 *
4
 * Copyright (c) 2007-2015, 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 <ctype.h>
27
#include <errno.h>
28
#include <limits.h>
29
#include <stdlib.h>
30
#include <string.h>
31
32
#include <sudo_compat.h>
33
#include <sudo_debug.h>
34
#include <sudo_lbuf.h>
35
#include <sudo_util.h>
36
37
void
38
sudo_lbuf_init_v1(struct sudo_lbuf *lbuf, sudo_lbuf_output_t output,
39
    unsigned int indent, const char *continuation, int cols)
40
28
{
41
28
    debug_decl(sudo_lbuf_init, SUDO_DEBUG_UTIL);
42
43
28
    if (cols < 0)
44
0
  cols = 0;
45
46
28
    lbuf->output = output;
47
28
    lbuf->continuation = continuation;
48
28
    lbuf->indent = indent;
49
28
    lbuf->cols = (unsigned short)cols;
50
28
    lbuf->error = 0;
51
28
    lbuf->len = 0;
52
28
    lbuf->size = 0;
53
28
    lbuf->buf = NULL;
54
55
28
    debug_return;
56
28
}
57
58
void
59
sudo_lbuf_destroy_v1(struct sudo_lbuf *lbuf)
60
16
{
61
16
    debug_decl(sudo_lbuf_destroy, SUDO_DEBUG_UTIL);
62
63
16
    free(lbuf->buf);
64
16
    lbuf->error = 0;
65
16
    lbuf->len = 0;
66
16
    lbuf->size = 0;
67
16
    lbuf->buf = NULL;
68
69
16
    debug_return;
70
16
}
71
72
static bool
73
sudo_lbuf_expand(struct sudo_lbuf *lbuf, unsigned int extra)
74
1.32k
{
75
1.32k
    debug_decl(sudo_lbuf_expand, SUDO_DEBUG_UTIL);
76
77
1.32k
    if (lbuf->len + extra + 1 <= lbuf->len) {
78
0
  errno = ENOMEM;
79
0
  sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
80
0
      "integer overflow updating lbuf->len");
81
0
  lbuf->error = 1;
82
0
  debug_return_bool(false);
83
0
    }
84
85
1.32k
    if (lbuf->len + extra + 1 > lbuf->size) {
86
16
  const size_t size = lbuf->len + extra + 1;
87
16
  size_t new_size = sudo_pow2_roundup(size);
88
16
  char *new_buf;
89
90
16
  if (new_size > UINT_MAX || new_size < lbuf->size) {
91
0
      errno = ENOMEM;
92
0
      sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
93
0
    "integer overflow updating lbuf->size");
94
0
      lbuf->error = 1;
95
0
      debug_return_bool(false);
96
0
  }
97
16
  if (new_size < 1024)
98
16
      new_size = 1024;
99
16
  if ((new_buf = realloc(lbuf->buf, new_size)) == NULL) {
100
0
      sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
101
0
    "unable to allocate memory");
102
0
      lbuf->error = 1;
103
0
      debug_return_bool(false);
104
0
  }
105
16
  lbuf->buf = new_buf;
106
16
  lbuf->size = (unsigned int)new_size;
107
16
    }
108
1.32k
    debug_return_bool(true);
109
1.32k
}
110
111
/*
112
 * Escape a character in octal form (#0n) and store it as a string
113
 * in buf, which must have at least 6 bytes available.
114
 * Returns the length of buf, not counting the terminating NUL byte.
115
 */
116
static unsigned int
117
escape(char ch, char *buf)
118
0
{
119
0
    unsigned char uch = (unsigned char)ch;
120
0
    const unsigned int len = uch < 0100 ? (uch < 010 ? 3 : 4) : 5;
121
122
    /* Work backwards from the least significant digit to most significant. */
123
0
    switch (len) {
124
0
    case 5:
125
0
  buf[4] = (uch & 7) + '0';
126
0
  uch >>= 3;
127
0
  FALLTHROUGH;
128
0
    case 4:
129
0
  buf[3] = (uch & 7) + '0';
130
0
  uch >>= 3;
131
0
  FALLTHROUGH;
132
0
    case 3:
133
0
  buf[2] = (uch & 7) + '0';
134
0
  buf[1] = '0';
135
0
  buf[0] = '#';
136
0
  break;
137
0
    }
138
0
    buf[len] = '\0';
139
140
0
    return len;
141
0
}
142
143
/*
144
 * Parse the format and append strings, only %s and %% escapes are supported.
145
 * Any non-printable characters are escaped in octal as #0nn.
146
 */
147
bool
148
sudo_lbuf_append_esc_v1(struct sudo_lbuf *lbuf, int flags, const char * restrict fmt, ...)
149
0
{
150
0
    unsigned int saved_len = lbuf->len;
151
0
    bool ret = false;
152
0
    const char *s;
153
0
    va_list ap;
154
0
    debug_decl(sudo_lbuf_append_esc, SUDO_DEBUG_UTIL);
155
156
0
    if (sudo_lbuf_error(lbuf))
157
0
  debug_return_bool(false);
158
159
0
#define should_escape(ch) \
160
0
    ((ISSET(flags, LBUF_ESC_CNTRL) && iscntrl((unsigned char)ch)) || \
161
0
    (ISSET(flags, LBUF_ESC_BLANK) && isblank((unsigned char)ch)))
162
0
#define should_quote(ch) \
163
0
    (ISSET(flags, LBUF_ESC_QUOTE) && (ch == '\'' || ch == '\\'))
164
165
0
    va_start(ap, fmt);
166
0
    while (*fmt != '\0') {
167
0
  if (fmt[0] == '%' && fmt[1] == 's') {
168
0
      if ((s = va_arg(ap, char *)) == NULL)
169
0
    s = "(NULL)";
170
0
      while (*s != '\0') {
171
0
    if (should_escape(*s)) {
172
0
        if (!sudo_lbuf_expand(lbuf, sizeof("#0177") - 1))
173
0
      goto done;
174
0
        lbuf->len += escape(*s++, lbuf->buf + lbuf->len);
175
0
        continue;
176
0
    }
177
0
    if (should_quote(*s)) {
178
0
        if (!sudo_lbuf_expand(lbuf, 2))
179
0
      goto done;
180
0
        lbuf->buf[lbuf->len++] = '\\';
181
0
        lbuf->buf[lbuf->len++] = *s++;
182
0
        continue;
183
0
    }
184
0
    if (!sudo_lbuf_expand(lbuf, 1))
185
0
        goto done;
186
0
    lbuf->buf[lbuf->len++] = *s++;
187
0
      }
188
0
      fmt += 2;
189
0
      continue;
190
0
  }
191
0
  if (should_escape(*fmt)) {
192
0
      if (!sudo_lbuf_expand(lbuf, sizeof("#0177") - 1))
193
0
    goto done;
194
0
      if (*fmt == '\'') {
195
0
    lbuf->buf[lbuf->len++] = '\\';
196
0
    lbuf->buf[lbuf->len++] = *fmt++;
197
0
      } else {
198
0
    lbuf->len += escape(*fmt++, lbuf->buf + lbuf->len);
199
0
      }
200
0
      continue;
201
0
  }
202
0
  if (!sudo_lbuf_expand(lbuf, 1))
203
0
      goto done;
204
0
  lbuf->buf[lbuf->len++] = *fmt++;
205
0
    }
206
0
    ret = true;
207
208
0
done:
209
0
    if (!ret)
210
0
  lbuf->len = saved_len;
211
0
    if (lbuf->size != 0)
212
0
  lbuf->buf[lbuf->len] = '\0';
213
0
    va_end(ap);
214
215
0
    debug_return_bool(ret);
216
0
}
217
218
/*
219
 * Parse the format and append strings, only %s and %% escapes are supported.
220
 * Any characters in set are quoted with a backslash.
221
 */
222
bool
223
sudo_lbuf_append_quoted_v1(struct sudo_lbuf *lbuf, const char *set, const char * restrict fmt, ...)
224
0
{
225
0
    unsigned int saved_len = lbuf->len;
226
0
    bool ret = false;
227
0
    const char *cp, *s;
228
0
    va_list ap;
229
0
    unsigned int len;
230
0
    debug_decl(sudo_lbuf_append_quoted, SUDO_DEBUG_UTIL);
231
232
0
    if (sudo_lbuf_error(lbuf))
233
0
  debug_return_bool(false);
234
235
0
    va_start(ap, fmt);
236
0
    while (*fmt != '\0') {
237
0
  if (fmt[0] == '%' && fmt[1] == 's') {
238
0
      if ((s = va_arg(ap, char *)) == NULL)
239
0
    s = "(NULL)";
240
0
      while ((cp = strpbrk(s, set)) != NULL) {
241
0
    len = (unsigned int)(cp - s);
242
0
    if (!sudo_lbuf_expand(lbuf, len + 2))
243
0
        goto done;
244
0
    memcpy(lbuf->buf + lbuf->len, s, len);
245
0
    lbuf->len += len;
246
0
    lbuf->buf[lbuf->len++] = '\\';
247
0
    lbuf->buf[lbuf->len++] = *cp;
248
0
    s = cp + 1;
249
0
      }
250
0
      if (*s != '\0') {
251
0
    len = (unsigned int)strlen(s);
252
0
    if (!sudo_lbuf_expand(lbuf, len))
253
0
        goto done;
254
0
    memcpy(lbuf->buf + lbuf->len, s, len);
255
0
    lbuf->len += len;
256
0
      }
257
0
      fmt += 2;
258
0
      continue;
259
0
  }
260
0
  if (!sudo_lbuf_expand(lbuf, 2))
261
0
      goto done;
262
0
  if (strchr(set, *fmt) != NULL)
263
0
      lbuf->buf[lbuf->len++] = '\\';
264
0
  lbuf->buf[lbuf->len++] = *fmt++;
265
0
    }
266
0
    ret = true;
267
268
0
done:
269
0
    if (!ret)
270
0
  lbuf->len = saved_len;
271
0
    if (lbuf->size != 0)
272
0
  lbuf->buf[lbuf->len] = '\0';
273
0
    va_end(ap);
274
275
0
    debug_return_bool(ret);
276
0
}
277
278
/*
279
 * Parse the format and append strings, only %s, %n$s and %% escapes are supported.
280
 */
281
bool
282
sudo_lbuf_append_v1(struct sudo_lbuf *lbuf, const char * restrict fmt, ...)
283
32
{
284
32
    unsigned int saved_len = lbuf->len;
285
32
    bool ret = false;
286
32
    va_list ap;
287
32
    const char *s;
288
32
    unsigned int len;
289
32
    debug_decl(sudo_lbuf_append, SUDO_DEBUG_UTIL);
290
291
32
    if (sudo_lbuf_error(lbuf))
292
0
  debug_return_bool(false);
293
294
32
    va_start(ap, fmt);
295
1.35k
    while (*fmt != '\0') {
296
1.32k
  if (fmt[0] == '%' && isdigit((unsigned char)fmt[1])) {
297
0
      const char *num_start = fmt + 1;
298
0
      const char *num_end = num_start;
299
0
      int arg_num;
300
      /* Find the end of the numeric part */
301
0
      while (isdigit((unsigned char)*num_end))
302
0
    num_end++;
303
0
      if (num_end[0] == '$' && num_end[1] == 's' && num_end > num_start) {
304
    /* Convert the numeric part to an integer */
305
0
    char numbuf[STRLEN_MAX_SIGNED(int) + 1];
306
0
    len = (unsigned int)(num_end - num_start);
307
0
    if (len >= sizeof(numbuf)) {
308
0
        errno = EINVAL;
309
0
        sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
310
0
      "integer overflow parsing $n");
311
0
        lbuf->error = 1;
312
0
        goto done;
313
0
    }
314
0
    memcpy(numbuf, num_start, len);
315
0
    numbuf[len] = '\0';
316
0
    arg_num = atoi(numbuf);
317
0
    if (arg_num > 0) {
318
0
        va_list arg_copy;
319
0
        va_copy(arg_copy, ap);
320
0
        for (int i = 1; i < arg_num; i++) {
321
0
      (void)va_arg(arg_copy, char *);
322
0
        }
323
0
        if ((s = va_arg(arg_copy, char *)) == NULL)
324
0
      s = "(NULL)";
325
0
        len = (unsigned int)strlen(s);
326
0
        if (!sudo_lbuf_expand(lbuf, len)) {
327
0
      va_end(arg_copy);
328
0
      goto done;
329
0
        }
330
0
        memcpy(lbuf->buf + lbuf->len, s, len);
331
0
        lbuf->len += len;
332
0
        fmt = num_end + 2;
333
0
        va_end(arg_copy);
334
0
        continue;
335
0
    }
336
0
      }
337
0
  }
338
1.32k
  if (fmt[0] == '%' && fmt[1] == 's') {
339
56
      if ((s = va_arg(ap, char *)) == NULL)
340
0
    s = "(NULL)";
341
56
      len = (unsigned int)strlen(s);
342
56
      if (!sudo_lbuf_expand(lbuf, len))
343
0
    goto done;
344
56
      memcpy(lbuf->buf + lbuf->len, s, len);
345
56
      lbuf->len += len;
346
56
      fmt += 2;
347
56
      continue;
348
56
  }
349
1.26k
  if (!sudo_lbuf_expand(lbuf, 1))
350
0
      goto done;
351
1.26k
  lbuf->buf[lbuf->len++] = *fmt++;
352
1.26k
    }
353
32
    ret = true;
354
355
32
done:
356
32
    if (!ret)
357
0
  lbuf->len = saved_len;
358
32
    if (lbuf->size != 0)
359
32
  lbuf->buf[lbuf->len] = '\0';
360
32
    va_end(ap);
361
362
32
    debug_return_bool(ret);
363
32
}
364
365
/* XXX - check output function return value */
366
static void
367
sudo_lbuf_println(struct sudo_lbuf *lbuf, char *line, size_t len)
368
0
{
369
0
    char *cp, save;
370
0
    size_t i, have, contlen = 0;
371
0
    unsigned int indent = lbuf->indent;
372
0
    bool is_comment = false;
373
0
    debug_decl(sudo_lbuf_println, SUDO_DEBUG_UTIL);
374
375
    /* Comment lines don't use continuation and only indent is for "# " */
376
0
    if (line[0] == '#' && isblank((unsigned char)line[1])) {
377
0
  is_comment = true;
378
0
  indent = 2;
379
0
    }
380
0
    if (lbuf->continuation != NULL && !is_comment)
381
0
  contlen = strlen(lbuf->continuation);
382
383
    /*
384
     * Print the buffer, splitting the line as needed on a word
385
     * boundary.
386
     */
387
0
    cp = line;
388
0
    have = lbuf->cols;
389
0
    while (cp != NULL && *cp != '\0') {
390
0
  char *ep = NULL;
391
0
  size_t need = len - (size_t)(cp - line);
392
393
0
  if (need > have) {
394
0
      have -= contlen;    /* subtract for continuation char */
395
0
      if ((ep = memrchr(cp, ' ', have)) == NULL)
396
0
    ep = memchr(cp + have, ' ', need - have);
397
0
      if (ep != NULL)
398
0
    need = (size_t)(ep - cp);
399
0
  }
400
0
  if (cp != line) {
401
0
      if (is_comment) {
402
0
    lbuf->output("# ");
403
0
      } else {
404
    /* indent continued lines */
405
    /* XXX - build up string instead? */
406
0
    for (i = 0; i < indent; i++)
407
0
        lbuf->output(" ");
408
0
      }
409
0
  }
410
  /* NUL-terminate cp for the output function and restore afterwards */
411
0
  save = cp[need];
412
0
  cp[need] = '\0';
413
0
  lbuf->output(cp);
414
0
  cp[need] = save;
415
0
  cp = ep;
416
417
  /*
418
   * If there is more to print, reset have, increment cp past
419
   * the whitespace, and print a line continuation char if needed.
420
   */
421
0
  if (cp != NULL) {
422
0
      have = lbuf->cols - indent;
423
0
      ep = line + len;
424
0
      while (cp < ep && isblank((unsigned char)*cp)) {
425
0
    cp++;
426
0
      }
427
0
      if (contlen)
428
0
    lbuf->output(lbuf->continuation);
429
0
  }
430
0
  lbuf->output("\n");
431
0
    }
432
433
0
    debug_return;
434
0
}
435
436
/*
437
 * Print the buffer with word wrap based on the tty width.
438
 * The lbuf is reset on return.
439
 * XXX - check output function return value
440
 */
441
void
442
sudo_lbuf_print_v1(struct sudo_lbuf *lbuf)
443
16
{
444
16
    char *cp, *ep;
445
16
    size_t len;
446
16
    debug_decl(sudo_lbuf_print, SUDO_DEBUG_UTIL);
447
448
16
    if (lbuf->buf == NULL || lbuf->len == 0)
449
8
  goto done;
450
451
    /* For very small widths just give up... */
452
8
    len = lbuf->continuation ? strlen(lbuf->continuation) : 0;
453
8
    if (lbuf->cols <= lbuf->indent + len + 20) {
454
8
  lbuf->buf[lbuf->len] = '\0';
455
8
  lbuf->output(lbuf->buf);
456
8
  if (lbuf->buf[lbuf->len - 1] != '\n')
457
0
      lbuf->output("\n");
458
8
  goto done;
459
8
    }
460
461
    /* Print each line in the buffer */
462
0
    for (cp = lbuf->buf; cp != NULL && *cp != '\0'; ) {
463
0
  if (*cp == '\n') {
464
0
      lbuf->output("\n");
465
0
      cp++;
466
0
  } else {
467
0
      len = lbuf->len - (size_t)(cp - lbuf->buf);
468
0
      if ((ep = memchr(cp, '\n', len)) != NULL)
469
0
    len = (size_t)(ep - cp);
470
0
      if (len)
471
0
    sudo_lbuf_println(lbuf, cp, len);
472
0
      cp = ep ? ep + 1 : NULL;
473
0
  }
474
0
    }
475
476
16
done:
477
16
    lbuf->len = 0;    /* reset the buffer for reuse. */
478
16
    lbuf->error = 0;
479
480
16
    debug_return;
481
16
}
482
483
bool
484
sudo_lbuf_error_v1(struct sudo_lbuf *lbuf)
485
88
{
486
88
    if (lbuf != NULL && lbuf->error != 0)
487
0
  return true;
488
88
    return false;
489
88
}
490
491
void
492
sudo_lbuf_clearerr_v1(struct sudo_lbuf *lbuf)
493
0
{
494
0
    if (lbuf != NULL)
495
0
  lbuf->error = 0;
496
0
}