Coverage Report

Created: 2025-10-10 07:09

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