Coverage Report

Created: 2025-10-10 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/sudo/lib/util/fatal.c
Line
Count
Source
1
/*
2
 * SPDX-License-Identifier: ISC
3
 *
4
 * Copyright (c) 2004-2005, 2010-2015, 2017-2018
5
 *  Todd C. Miller <Todd.Miller@sudo.ws>
6
 *
7
 * Permission to use, copy, modify, and distribute this software for any
8
 * purpose with or without fee is hereby granted, provided that the above
9
 * copyright notice and this permission notice appear in all copies.
10
 *
11
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18
 */
19
20
#include <config.h>
21
22
#include <errno.h>
23
#include <netdb.h>
24
#include <stdio.h>
25
#include <stdlib.h>
26
#include <string.h>
27
#ifdef HAVE_STDBOOL_H
28
# include <stdbool.h>
29
#else
30
# include <compat/stdbool.h>
31
#endif /* HAVE_STDBOOL_H */
32
#include <unistd.h>
33
#ifndef HAVE_GETADDRINFO
34
# include <compat/getaddrinfo.h>
35
#endif
36
37
#include <sudo_compat.h>
38
#include <sudo_fatal.h>
39
#include <sudo_gettext.h>
40
#include <sudo_queue.h>
41
#include <sudo_util.h>
42
#include <sudo_plugin.h>
43
44
struct sudo_fatal_callback {
45
    SLIST_ENTRY(sudo_fatal_callback) entries;
46
    void (*func)(void);
47
};
48
SLIST_HEAD(sudo_fatal_callback_list, sudo_fatal_callback);
49
50
static struct sudo_fatal_callback_list callbacks = SLIST_HEAD_INITIALIZER(&callbacks);
51
static sudo_conv_t sudo_warn_conversation;
52
static sudo_warn_setlocale_t sudo_warn_setlocale;
53
static sudo_warn_setlocale_t sudo_warn_setlocale_prev;
54
55
static void warning(const char * restrict errstr, const char * restrict fmt, va_list ap);
56
57
static void
58
do_cleanup(void)
59
0
{
60
0
    struct sudo_fatal_callback *cb;
61
62
    /* Run callbacks, removing them from the list as we go. */
63
0
    while ((cb = SLIST_FIRST(&callbacks)) != NULL) {
64
0
  SLIST_REMOVE_HEAD(&callbacks, entries);
65
0
  cb->func();
66
0
  free(cb);
67
0
    }
68
0
}
69
70
sudo_noreturn void
71
sudo_fatal_nodebug_v1(const char * restrict fmt, ...)
72
0
{
73
0
    va_list ap;
74
75
0
    va_start(ap, fmt);
76
0
    warning(strerror(errno), fmt, ap);
77
0
    va_end(ap);
78
0
    do_cleanup();
79
0
    exit(EXIT_FAILURE);
80
0
}
81
82
sudo_noreturn void
83
sudo_fatalx_nodebug_v1(const char * restrict fmt, ...)
84
0
{
85
0
    va_list ap;
86
87
0
    va_start(ap, fmt);
88
0
    warning(NULL, fmt, ap);
89
0
    va_end(ap);
90
0
    do_cleanup();
91
0
    exit(EXIT_FAILURE);
92
0
}
93
94
sudo_noreturn void
95
sudo_vfatal_nodebug_v1(const char * restrict fmt, va_list ap)
96
0
{
97
0
    warning(strerror(errno), fmt, ap);
98
0
    do_cleanup();
99
0
    exit(EXIT_FAILURE);
100
0
}
101
102
sudo_noreturn void
103
sudo_vfatalx_nodebug_v1(const char * restrict fmt, va_list ap)
104
0
{
105
0
    warning(NULL, fmt, ap);
106
0
    do_cleanup();
107
0
    exit(EXIT_FAILURE);
108
0
}
109
110
void
111
sudo_warn_nodebug_v1(const char * restrict fmt, ...)
112
0
{
113
0
    va_list ap;
114
115
0
    va_start(ap, fmt);
116
0
    warning(strerror(errno), fmt, ap);
117
0
    va_end(ap);
118
0
}
119
120
void
121
sudo_warnx_nodebug_v1(const char * restrict fmt, ...)
122
3.71k
{
123
3.71k
    va_list ap;
124
3.71k
    va_start(ap, fmt);
125
3.71k
    warning(NULL, fmt, ap);
126
3.71k
    va_end(ap);
127
3.71k
}
128
129
void
130
sudo_vwarn_nodebug_v1(const char * restrict fmt, va_list ap)
131
0
{
132
0
    warning(strerror(errno), fmt, ap);
133
0
}
134
135
void
136
sudo_vwarnx_nodebug_v1(const char * restrict fmt, va_list ap)
137
0
{
138
0
    warning(NULL, fmt, ap);
139
0
}
140
141
sudo_noreturn void
142
sudo_gai_fatal_nodebug_v1(int errnum, const char * restrict fmt, ...)
143
0
{
144
0
    va_list ap;
145
146
0
    va_start(ap, fmt);
147
0
    warning(gai_strerror(errnum), fmt, ap);
148
0
    va_end(ap);
149
0
    do_cleanup();
150
0
    exit(EXIT_FAILURE);
151
0
}
152
153
sudo_noreturn void
154
sudo_gai_vfatal_nodebug_v1(int errnum, const char * restrict fmt, va_list ap)
155
0
{
156
0
    warning(gai_strerror(errnum), fmt, ap);
157
0
    do_cleanup();
158
0
    exit(EXIT_FAILURE);
159
0
}
160
161
void
162
sudo_gai_warn_nodebug_v1(int errnum, const char * restrict fmt, ...)
163
1.89k
{
164
1.89k
    va_list ap;
165
166
1.89k
    va_start(ap, fmt);
167
1.89k
    warning(gai_strerror(errnum), fmt, ap);
168
1.89k
    va_end(ap);
169
1.89k
}
170
171
void
172
sudo_gai_vwarn_nodebug_v1(int errnum, const char * restrict fmt, va_list ap)
173
0
{
174
0
    warning(gai_strerror(errnum), fmt, ap);
175
0
}
176
177
static void
178
warning(const char * restrict errstr, const char * restrict fmt, va_list ap)
179
5.60k
{
180
5.60k
    int cookie;
181
5.60k
    const int saved_errno = errno;
182
183
    /* Set user locale if setter was specified. */
184
5.60k
    if (sudo_warn_setlocale != NULL)
185
0
  sudo_warn_setlocale(false, &cookie);
186
187
5.60k
    if (sudo_warn_conversation != NULL) {
188
5.60k
  struct sudo_conv_message msgs[6];
189
5.60k
  char static_buf[1024], *buf = static_buf;
190
5.60k
  int nmsgs = 0;
191
192
  /* Use conversation function. */
193
5.60k
        msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
194
5.60k
  msgs[nmsgs++].msg = getprogname();
195
5.60k
        if (fmt != NULL) {
196
5.60k
    va_list ap2;
197
5.60k
    int buflen;
198
199
    /* Use static buffer if possible, else dynamic. */
200
5.60k
    va_copy(ap2, ap);
201
5.60k
    buflen = vsnprintf(static_buf, sizeof(static_buf), fmt, ap2);
202
5.60k
    va_end(ap2);
203
5.60k
    if (buflen >= ssizeof(static_buf)) {
204
        /* Not enough room in static buf, allocate dynamically. */
205
104
        if (vasprintf(&buf, fmt, ap) == -1)
206
0
      buf = static_buf;
207
104
    }
208
5.60k
    if (buflen > 0) {
209
5.60k
        msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
210
5.60k
        msgs[nmsgs++].msg = ": ";
211
5.60k
        msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
212
5.60k
        msgs[nmsgs++].msg = buf;
213
5.60k
    }
214
5.60k
        }
215
5.60k
        if (errstr != NULL) {
216
1.89k
      msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
217
1.89k
      msgs[nmsgs++].msg = ": ";
218
1.89k
      msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
219
1.89k
      msgs[nmsgs++].msg = errstr;
220
1.89k
        }
221
5.60k
  msgs[nmsgs].msg_type = SUDO_CONV_ERROR_MSG;
222
5.60k
  msgs[nmsgs++].msg = "\n";
223
5.60k
  sudo_warn_conversation(nmsgs, msgs, NULL, NULL);
224
5.60k
  if (buf != static_buf)
225
104
      free(buf);
226
5.60k
    } else {
227
  /* Write to the standard error. */
228
0
        fputs(getprogname(), stderr);
229
0
        if (fmt != NULL) {
230
0
                fputs(": ", stderr);
231
0
                vfprintf(stderr, fmt, ap);
232
0
        }
233
0
        if (errstr != NULL) {
234
0
            fputs(": ", stderr);
235
0
            fputs(errstr, stderr);
236
0
        }
237
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
238
        if (sudo_term_is_raw(fileno(stderr)))
239
            putc('\r', stderr);
240
#endif
241
0
        putc('\n', stderr);
242
0
    }
243
244
    /* Restore old locale as needed. */
245
5.60k
    if (sudo_warn_setlocale != NULL)
246
0
  sudo_warn_setlocale(true, &cookie);
247
248
    /* Do not clobber errno. */
249
5.60k
    errno = saved_errno;
250
5.60k
}
251
252
/*
253
 * Register a callback to be run when sudo_fatal()/sudo_fatalx() is called.
254
 */
255
int
256
sudo_fatal_callback_register_v1(sudo_fatal_callback_t func)
257
0
{
258
0
    struct sudo_fatal_callback *cb;
259
260
    /* Do not register the same callback twice.  */
261
0
    SLIST_FOREACH(cb, &callbacks, entries) {
262
0
  if (func == cb->func)
263
0
      return -1;   /* dupe! */
264
0
    }
265
266
    /* Allocate and insert new callback. */
267
0
    cb = malloc(sizeof(*cb));
268
0
    if (cb == NULL)
269
0
  return -1;
270
0
    cb->func = func;
271
0
    SLIST_INSERT_HEAD(&callbacks, cb, entries);
272
273
0
    return 0;
274
0
}
275
276
/*
277
 * Deregister a sudo_fatal()/sudo_fatalx() callback.
278
 */
279
int
280
sudo_fatal_callback_deregister_v1(sudo_fatal_callback_t func)
281
0
{
282
0
    struct sudo_fatal_callback *cb, *prev = NULL;
283
284
    /* Search for callback and remove if found, dupes are not allowed. */
285
0
    SLIST_FOREACH(cb, &callbacks, entries) {
286
0
  if (cb->func == func) {
287
0
      if (prev == NULL)
288
0
    SLIST_REMOVE_HEAD(&callbacks, entries);
289
0
      else
290
0
    SLIST_REMOVE_AFTER(prev, entries);
291
0
      free(cb);
292
0
      return 0;
293
0
  }
294
0
  prev = cb;
295
0
    }
296
297
0
    return -1;
298
0
}
299
300
/*
301
 * Set the conversation function to use for output instead of the
302
 * standard error.  If conv is NULL, switch back to standard error.
303
 */
304
void
305
sudo_warn_set_conversation_v1(sudo_conv_t conv)
306
3.86k
{
307
3.86k
    sudo_warn_conversation = conv;
308
3.86k
}
309
310
/*
311
 * Set the locale function so the plugin can use a non-default
312
 * locale for user warnings.
313
 */
314
void
315
sudo_warn_set_locale_func_v1(sudo_warn_setlocale_t func)
316
0
{
317
0
    sudo_warn_setlocale_prev = sudo_warn_setlocale;
318
0
    sudo_warn_setlocale = func;
319
0
}
320
321
#ifdef HAVE_LIBINTL_H
322
char *
323
sudo_warn_gettext_v1(const char *domainname, const char *msgid)
324
5.63k
{
325
5.63k
    int cookie;
326
5.63k
    char *msg;
327
328
    /* Set user locale if setter was specified. */
329
5.63k
    if (sudo_warn_setlocale != NULL)
330
0
  sudo_warn_setlocale(false, &cookie);
331
332
5.63k
    msg = dgettext(domainname, msgid);
333
334
    /* Restore old locale as needed. */
335
5.63k
    if (sudo_warn_setlocale != NULL)
336
0
  sudo_warn_setlocale(true, &cookie);
337
338
5.63k
    return msg;
339
5.63k
}
340
#endif /* HAVE_LIBINTL_H */