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