Coverage Report

Created: 2025-07-12 06:12

/src/lighttpd1.4/src/ck.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * ck - C11 Annex K wrappers  (selected functions; not complete)
3
 *
4
 * ck is also an abbreviation for "check".
5
 * These are validating, checking functions.
6
 *
7
 * Copyright(c) 2016 Glenn Strauss gstrauss()gluelogic.com  All rights reserved
8
 * License: BSD 3-clause (same as lighttpd)
9
 */
10
#ifndef __STDC_WANT_LIB_EXT1__
11
#define __STDC_WANT_LIB_EXT1__ 1
12
#endif
13
#ifndef _XOPEN_SOURCE
14
#define _XOPEN_SOURCE 700
15
#endif
16
#ifndef _NETBSD_SOURCE
17
#define _NETBSD_SOURCE
18
#endif
19
#ifdef __OpenBSD__
20
#ifndef _BSD_SOURCE
21
#define _BSD_SOURCE
22
#endif
23
#endif
24
#include "first.h"
25
26
#include "ck.h"
27
28
#include <stdlib.h>     /* abort() getenv() getenv_s()
29
                         * calloc() malloc() realloc() */
30
#include <string.h>     /* memcpy() memset() memset_s() explicit_bzero()
31
                         * memset_explicit()
32
                         * strerror() strerror_r() strerror_s() strlen() */
33
34
#ifdef __STDC_LIB_EXT1__
35
#ifndef HAVE_MEMSET_S
36
#define HAVE_MEMSET_S
37
#endif
38
#else
39
#include <errno.h>
40
#include <stdio.h>      /* snprintf() */
41
#endif
42
43
#ifndef HAVE_MEMSET_S
44
45
#ifdef _WIN32
46
#define VC_EXTRALEAN
47
#define WIN32_LEAN_AND_MEAN
48
#include <windows.h>    /* SecureZeroMemory() */
49
/*(Windows XP and later provide SecureZeroMemory())*/
50
#define HAVE_SECUREZEROMEMORY
51
#else /* !_WIN32 */
52
#ifdef HAVE_SIGNAL
53
#include <signal.h>     /* sig_atomic_t */
54
#else
55
typedef int sig_atomic_t;
56
#endif
57
/*#include <plasma/plasma_membar.h>*/  /* plasma_membar_ccfence() */
58
#endif
59
60
#endif /* !HAVE_MEMSET_S */
61
62
63
#if !defined(HAVE_MEMSET_S)        \
64
 && !defined(HAVE_MEMSET_EXPLICIT) \
65
 && !defined(HAVE_EXPLICIT_BZERO)  \
66
 && !defined(HAVE_EXPLICIT_MEMSET) \
67
 && !defined(HAVE_SECUREZEROMEMORY)
68
69
typedef void *(*ck_memclear_func_t)(void *, int, size_t);
70
extern volatile ck_memclear_func_t ck_memclear_func;
71
volatile ck_memclear_func_t ck_memclear_func = memset;
72
73
#ifdef HAVE_WEAK_SYMBOLS
74
/* it seems weak functions are never inlined, even for static builds */
75
__attribute__((__weak__))
76
void ck_memclear_s_hook (void *buf, rsize_t len);
77
void ck_memclear_s_hook (void *buf    __attribute_unused__,
78
                         rsize_t len  __attribute_unused__)
79
{
80
    /*(application might define func to call OPENSSL_cleanse(), if available)*/
81
    (void)(buf); /* UNUSED */
82
    (void)(len); /* UNUSED */
83
}
84
#endif /* HAVE_WEAK_SYMBOLS */
85
86
static void *
87
ck_memset_compat(void *s, int c, size_t n)
88
{
89
    /* attempt to inhibit compiler/linker heuristics which might elide memset()
90
     * - insert compiler optimization fences around memset()
91
     * - access s through volatile pointer at volatile index after memset()
92
     * - pass s to weak (overridable) func to create additional data dependency
93
     */
94
95
    if (0 == n)    /*(must check n > 0 since s[0] will be accessed)*/
96
        return s;
97
98
    static volatile sig_atomic_t vzero;
99
    volatile unsigned char *vs = (volatile unsigned char *)s;
100
    do {
101
        /*plasma_membar_ccfence();*/
102
        ck_memclear_func(s, c, n);
103
        /*plasma_membar_ccfence();*/
104
    } while (vs[vzero] != c);
105
106
  #ifdef HAVE_WEAK_SYMBOLS
107
    ck_memclear_s_hook(s, n);
108
  #endif
109
110
    return s;
111
}
112
113
#endif
114
115
116
errno_t
117
ck_memclear_s (void * const s, const rsize_t smax, rsize_t n)
118
0
{
119
  #ifdef HAVE_MEMSET_S
120
121
    return memset_s(s, smax, 0, n);
122
123
  #else
124
125
0
    if (NULL == s)
126
        /* runtime constraint violation */
127
0
        return EINVAL;
128
0
    if (RSIZE_MAX < smax)
129
        /* runtime constraint violation */
130
0
        return E2BIG;
131
132
0
    errno_t rc = 0;
133
0
    if (RSIZE_MAX < n) {
134
        /* runtime constraint violation */
135
0
        rc = EINVAL;
136
0
        n = smax;
137
0
    }
138
0
    if (smax < n) {
139
        /* runtime constraint violation */
140
0
        rc = EOVERFLOW;
141
0
        n = smax;
142
0
    }
143
144
0
   #if defined(HAVE_EXPLICIT_BZERO)
145
0
    explicit_bzero(s, n);
146
   #elif defined(HAVE_MEMSET_EXPLICIT) /* C23: __STDC_VERSION__-0 >= 202311L */
147
    memset_explicit(s, 0, n);
148
   #elif defined(HAVE_EXPLICIT_MEMSET)
149
    explicit_memset(s, 0, n);
150
   #elif defined(HAVE_SECUREZEROMEMORY)
151
    SecureZeroMemory(s, n);
152
   #else
153
    ck_memset_compat(s, 0, n);
154
   #endif
155
156
0
    return rc;
157
158
0
  #endif
159
0
}
160
161
162
#if 0 /*(not currently used in lighttpd; lighttpd process env is stable)*/
163
errno_t
164
ck_getenv_s (size_t * const restrict len,
165
             char * const restrict value, const rsize_t maxsize,
166
             const char * const restrict name)
167
{
168
  #ifdef __STDC_LIB_EXT1__
169
170
    return getenv_s(len, value, maxsize, name);
171
172
  #else
173
174
    if (NULL == name || RSIZE_MAX < maxsize || (0 != maxsize && NULL == value)){
175
        /* runtime constraint violation */
176
        if (NULL != len)
177
            *len = 0;
178
        if (NULL != value && maxsize)
179
            *value = '\0';
180
        return EINVAL;
181
    }
182
183
    const char * const v = getenv(name);
184
    if (NULL != v) {
185
        const size_t vlen = strlen(v);
186
        if (NULL != len)
187
            *len = vlen;
188
        if (vlen < maxsize) {
189
            memcpy(value, v, vlen+1);
190
            return 0;
191
        }
192
        else {
193
            if (maxsize)
194
                *value = '\0';
195
            return ERANGE;
196
        }
197
    }
198
    else {
199
        if (NULL != len)
200
            *len = 0;
201
        if (maxsize)
202
            *value = '\0';
203
      #ifdef ENODATA
204
        return ENODATA;
205
      #else
206
        return ENOENT;
207
      #endif
208
    }
209
210
  #endif
211
}
212
#endif
213
214
215
errno_t
216
ck_strerror_s (char * const s, const rsize_t maxsize, const errno_t errnum)
217
0
{
218
  #ifdef __STDC_LIB_EXT1__
219
220
    return strerror_s(s, maxsize, errnum);
221
222
  #else
223
224
0
    if (NULL == s || 0 == maxsize || RSIZE_MAX < maxsize) {
225
        /* runtime constraint violation */
226
0
        return EINVAL;
227
0
    }
228
229
    /*(HAVE_STRERROR_R defined after tests by configure.ac or SConstruct)*/
230
  #if !defined(HAVE_STRERROR_R) && !defined(HAVE_CONFIG_H)
231
  #define HAVE_STRERROR_R 1
232
  #endif /*(assume strerror_r() available if no config.h)*/
233
234
0
  #ifdef HAVE_STRERROR_R
235
0
    char buf[1024];
236
0
   #if defined(_GNU_SOURCE) && defined(__GLIBC__)
237
0
    const char *errstr = strerror_r(errnum,buf,sizeof(buf));
238
   #else /* XSI-compliant strerror_r() */
239
    const char *errstr = (0 == strerror_r(errnum,buf,sizeof(buf))) ? buf : NULL;
240
   #endif
241
  #else /* !HAVE_STRERROR_R */
242
    const char *errstr = strerror(errnum);
243
  #endif
244
0
    if (NULL != errstr) {
245
0
        const size_t errlen = strlen(errstr);
246
0
        if (errlen < maxsize) {
247
0
            memcpy(s, errstr, errlen+1);
248
0
            return 0;
249
0
        }
250
0
        else {
251
0
            memcpy(s, errstr, maxsize-1);
252
0
            s[maxsize-1] = '\0';
253
            /*(fall through; not enough space to store entire error string)*/
254
0
        }
255
0
    }
256
0
    else {
257
0
        if ((rsize_t)snprintf(s, maxsize, "Unknown error %d", errnum) < maxsize)
258
0
            return 0;
259
        /*(else fall through; not enough space to store entire error string)*/
260
0
    }
261
262
    /*(not enough space to store entire error string)*/
263
0
    if (maxsize > 3)
264
0
        memcpy(s+maxsize-4, "...", 4);
265
0
    return ERANGE;
266
267
0
  #endif
268
0
}
269
270
271
int
272
ck_memeq_const_time (const void *a, size_t alen, const void *b, size_t blen)
273
0
{
274
    /* constant time memory compare for equality */
275
    /* rounds to next multiple of 64 to avoid potentially leaking exact
276
     * string lengths when subject to high precision timing attacks
277
     */
278
    /* Note: some libs provide similar funcs but might not obscure length, e.g.
279
     * OpenSSL:
280
     *   int CRYPTO_memcmp(const void * in_a, const void * in_b, size_t len)
281
     * Note: some OS provide similar funcs but might not obscure length, e.g.
282
     * OpenBSD: int timingsafe_bcmp(const void *b1, const void *b2, size_t len)
283
     * NetBSD: int consttime_memequal(void *b1, void *b2, size_t len)
284
     */
285
0
    const volatile unsigned char * const av =
286
0
      (const unsigned char *)(alen ? a : "");
287
0
    const volatile unsigned char * const bv =
288
0
      (const unsigned char *)(blen ? b : "");
289
0
    size_t lim = ((alen >= blen ? alen : blen) + 0x3F) & ~0x3F;
290
0
    int diff = (alen != blen); /*(never match if string length mismatch)*/
291
0
    alen -= (alen != 0);
292
0
    blen -= (blen != 0);
293
0
    for (size_t i = 0, j = 0; lim; --lim) {
294
0
        diff |= (av[i] ^ bv[j]);
295
0
        i += (i < alen);
296
0
        j += (j < blen);
297
0
    }
298
0
    return (0 == diff);
299
0
}
300
301
302
int
303
ck_memeq_const_time_fixed_len (const void *a, const void *b, const size_t len)
304
0
{
305
    /* constant time memory compare for equality for fixed len (e.g. digests)
306
     * (padding not necessary for digests, which have fixed, defined lengths) */
307
    /* caller should prefer ck_memeq_const_time() if not operating on digests */
308
0
    const volatile unsigned char * const av = (const unsigned char *)a;
309
0
    const volatile unsigned char * const bv = (const unsigned char *)b;
310
0
    int diff = 0;
311
0
    for (size_t i = 0; i < len; ++i) {
312
0
        diff |= (av[i] ^ bv[i]);
313
0
    }
314
0
    return (0 == diff);
315
0
}
316
317
318
void *
319
ck_malloc (size_t nbytes)
320
0
{
321
0
    void *ptr = malloc(nbytes);
322
0
    ck_assert(NULL != ptr);
323
0
    return ptr;
324
0
}
325
326
327
void *
328
ck_calloc (size_t nmemb, size_t elt_sz)
329
0
{
330
0
    void *ptr = calloc(nmemb, elt_sz);
331
0
    ck_assert(NULL != ptr);
332
0
    return ptr;
333
0
}
334
335
336
void *
337
ck_realloc_u32 (void **list, size_t n, size_t x, size_t elt_sz)
338
0
{
339
  #ifdef HAVE_REALLOCARRAY /*(not currently detected by build)*/
340
    ck_assert(x <= UINT32_MAX && n <= UINT32_MAX - x);
341
    void *ptr = reallocarray(*list, n + x, elt_sz);
342
  #else
343
0
    ck_assert(x <= UINT32_MAX && n <= UINT32_MAX - x && n+x <= SIZE_MAX/elt_sz);
344
0
    void *ptr = realloc(*list, (n + x) * elt_sz);
345
0
  #endif
346
0
    ck_assert(NULL != ptr);
347
0
    return (*list = ptr);
348
0
}
349
350
351
352
353
#include <stdio.h>      /* fflush() fprintf() snprintf() */
354
355
#ifdef HAVE_LIBUNWIND
356
#define UNW_LOCAL_ONLY
357
#include <libunwind.h>
358
__attribute_cold__
359
__attribute_noinline__
360
static void
361
ck_backtrace (FILE *fp)
362
{
363
    int rc;
364
    unsigned int frame = 0;
365
    unw_word_t ip;
366
    unw_word_t offset;
367
    unw_cursor_t cursor;
368
    unw_context_t context;
369
    unw_proc_info_t procinfo;
370
    char name[256];
371
372
    rc = unw_getcontext(&context);
373
    if (0 != rc) goto error;
374
    rc = unw_init_local(&cursor, &context);
375
    if (0 != rc) goto error;
376
377
    fprintf(fp, "Backtrace:\n");
378
    while (0 < (rc = unw_step(&cursor))) {
379
        ++frame;
380
        ip = 0;
381
        rc = unw_get_reg(&cursor, UNW_REG_IP, &ip);
382
        if (0 != rc) break;
383
        if (0 == ip) {
384
            /* without an IP the other functions are useless;
385
             * unw_get_proc_name would return UNW_EUNSPEC */
386
            fprintf(fp, "%u: (nil)\n", frame);
387
            continue;
388
        }
389
390
        rc = unw_get_proc_info(&cursor, &procinfo);
391
        if (0 != rc) break;
392
393
        offset = 0;
394
        rc = unw_get_proc_name(&cursor, name, sizeof(name), &offset);
395
        if (0 != rc) {
396
            switch (-rc) {
397
              case UNW_ENOMEM:
398
                memcpy(name + sizeof(name) - 4, "...", 4);
399
                break;
400
              case UNW_ENOINFO:
401
                name[0] = '?';
402
                name[1] = '\0';
403
                break;
404
              default:
405
                snprintf(name, sizeof(name),
406
                         "?? (unw_get_proc_name error %d)", -rc);
407
                break;
408
            }
409
        }
410
411
        fprintf(fp, "%.2u: [%.012lx] (+%04x) %s\n",
412
                frame,(long unsigned)(uintptr_t)ip,(unsigned int)offset,name);
413
    }
414
    if (0 == rc)
415
        return;
416
417
error:
418
    fprintf(fp, "Error while generating backtrace: unwind error %i\n",(int)-rc);
419
}
420
#endif
421
422
423
__attribute_noinline__
424
__attribute_nonnull__()
425
static void
426
ck_bt_stderr (const char *filename, unsigned int line, const char *msg, const char *fmt)
427
0
{
428
0
    fprintf(stderr, fmt, filename, line, msg);
429
  #ifdef HAVE_LIBUNWIND
430
    ck_backtrace(stderr);
431
  #endif
432
0
    fflush(stderr);
433
0
}
434
435
436
void
437
ck_bt (const char *filename, unsigned int line, const char *msg)
438
0
{
439
0
    ck_bt_stderr(filename, line, msg, "%s.%u: %s\n");
440
0
}
441
442
443
__attribute_noreturn__
444
void
445
ck_bt_abort (const char *filename, unsigned int line, const char *msg)
446
0
{
447
0
    ck_bt(filename, line, msg);
448
0
    abort();
449
0
}
450
451
452
__attribute_noreturn__
453
void ck_assert_failed(const char *filename, unsigned int line, const char *msg)
454
0
{
455
    /* same as ck_bt_abort() but add "assertion failed: " prefix here
456
     * to avoid bloating string tables in callers */
457
0
    ck_bt_stderr(filename, line, msg, "%s.%u: assertion failed: %s\n");
458
0
    abort();
459
0
}