1 /*
2 * stunnel TLS offloading and load-balancing proxy
3 * Copyright (C) 1998-2021 Michal Trojnara <Michal.Trojnara@stunnel.org>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, see <http://www.gnu.org/licenses>.
17 *
18 * Linking stunnel statically or dynamically with other modules is making
19 * a combined work based on stunnel. Thus, the terms and conditions of
20 * the GNU General Public License cover the whole combination.
21 *
22 * In addition, as a special exception, the copyright holder of stunnel
23 * gives you permission to combine stunnel with free software programs or
24 * libraries that are released under the GNU LGPL and with code included
25 * in the standard release of OpenSSL under the OpenSSL License (or
26 * modified versions of such code, with unchanged license). You may copy
27 * and distribute such a system following the terms of the GNU GPL for
28 * stunnel and the licenses of the other code concerned.
29 *
30 * Note that people who make modified versions of stunnel are not obligated
31 * to grant this special exception for their modified versions; it is their
32 * choice whether to do so. The GNU General Public License gives permission
33 * to release a modified version without this exception; this exception
34 * also makes it possible to release a modified version which carries
35 * forward this exception.
36 */
37
38 #include "common.h"
39 #include "prototypes.h"
40
41 /* Uncomment to see allocation sources in core dumps */
42 /* #define DEBUG_PADDING 64 */
43
44 /* reportedly, malloc does not always return 16-byte aligned addresses
45 * for 64-bit targets as specified by
46 * https://msdn.microsoft.com/en-us/library/6ewkz86d.aspx */
47 #ifdef USE_WIN32
48 #define system_malloc(n) _aligned_malloc((n),16)
49 #define system_realloc(p,n) _aligned_realloc((p),(n),16)
50 #define system_free(p) _aligned_free(p)
51 #else
52 #define system_malloc(n) malloc(n)
53 #define system_realloc(p,n) realloc((p),(n))
54 #define system_free(p) free(p)
55 #endif
56
57 #define CANARY_INITIALIZED 0x0000c0ded0000000LL
58 #define CANARY_UNINTIALIZED 0x0000abadbabe0000LL
59 #define MAGIC_ALLOCATED 0x0000a110c8ed0000LL
60 #define MAGIC_DEALLOCATED 0x0000defec8ed0000LL
61
62 /* most platforms require allocations to be aligned */
63 #ifdef _MSC_VER
64 __declspec(align(16))
65 #endif
66 struct alloc_list_struct {
67 ALLOC_LIST *prev, *next;
68 TLS_DATA *tls;
69 size_t size;
70 const char *alloc_file, *free_file;
71 int alloc_line, free_line;
72 #ifdef DEBUG_PADDING
73 char debug[DEBUG_PADDING];
74 #endif
75 uint64_t valid_canary, magic;
76 #ifdef __GNUC__
77 } __attribute__((aligned(16)));
78 #else
79 #ifndef MSC_VER
80 uint64_t :0; /* align the structure */
81 #endif
82 };
83 #endif
84
85 #define LEAK_TABLE_SIZE 997
86 typedef struct {
87 const char *alloc_file;
88 int alloc_line;
89 int num, max;
90 } LEAK_ENTRY;
91 NOEXPORT LEAK_ENTRY leak_hash_table[LEAK_TABLE_SIZE],
92 *leak_results[LEAK_TABLE_SIZE];
93 NOEXPORT int leak_result_num=0;
94
95 #ifdef USE_WIN32
96 NOEXPORT LPTSTR str_vtprintf(LPCTSTR, va_list);
97 #endif /* USE_WIN32 */
98
99 NOEXPORT void *str_realloc_internal_debug(void *, size_t, const char *, int);
100
101 NOEXPORT ALLOC_LIST *get_alloc_list_ptr(void *, const char *, int);
102 NOEXPORT void str_leak_debug(const ALLOC_LIST *, int);
103
104 NOEXPORT LEAK_ENTRY *leak_search(const ALLOC_LIST *);
105 NOEXPORT void leak_report();
106 NOEXPORT long leak_threshold();
107
108 TLS_DATA *ui_tls;
109 NOEXPORT uint8_t canary[10]; /* 80-bit canary value */
110 NOEXPORT volatile uint64_t canary_initialized=CANARY_UNINTIALIZED;
111
112 /**************************************** string manipulation functions */
113
114 #ifndef va_copy
115 #ifdef __va_copy
116 #define va_copy(dst, src) __va_copy((dst), (src))
117 #else /* __va_copy */
118 #define va_copy(dst, src) memcpy(&(dst), &(src), sizeof(va_list))
119 #endif /* __va_copy */
120 #endif /* va_copy */
121
str_dup_debug(const char * str,const char * file,int line)122 char *str_dup_debug(const char *str, const char *file, int line) {
123 char *retval;
124
125 if(!str)
126 return NULL;
127 retval=str_alloc_debug(strlen(str)+1, file, line);
128 strcpy(retval, str);
129 return retval;
130 }
131
str_dup_detached_debug(const char * str,const char * file,int line)132 char *str_dup_detached_debug(const char *str, const char *file, int line) {
133 char *retval;
134
135 if(!str)
136 return NULL;
137 retval=str_alloc_detached_debug(strlen(str)+1, file, line);
138 strcpy(retval, str);
139 return retval;
140 }
141
str_printf(const char * format,...)142 char *str_printf(const char *format, ...) {
143 char *txt;
144 va_list arglist;
145
146 va_start(arglist, format);
147 txt=str_vprintf(format, arglist);
148 va_end(arglist);
149 return txt;
150 }
151
152 #ifdef __GNUC__
153 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
154 #pragma GCC diagnostic push
155 #endif /* __GNUC__>=4.6 */
156 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
157 #endif /* __GNUC__ */
str_vprintf(const char * format,va_list start_ap)158 char *str_vprintf(const char *format, va_list start_ap) {
159 int n;
160 size_t size=32;
161 char *p;
162 va_list ap;
163
164 p=str_alloc(size);
165 for(;;) {
166 va_copy(ap, start_ap);
167 n=vsnprintf(p, size, format, ap);
168 if(n>-1 && n<(int)size)
169 return p;
170 if(n>-1) /* glibc 2.1 */
171 size=(size_t)n+1; /* precisely what is needed */
172 else /* glibc 2.0, WIN32, etc. */
173 size*=2; /* twice the old size */
174 p=str_realloc(p, size);
175 }
176 }
177 #ifdef __GNUC__
178 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
179 #pragma GCC diagnostic pop
180 #endif /* __GNUC__>=4.6 */
181 #endif /* __GNUC__ */
182
183 #ifdef USE_WIN32
184
str_tprintf(LPCTSTR format,...)185 LPTSTR str_tprintf(LPCTSTR format, ...) {
186 LPTSTR txt;
187 va_list arglist;
188
189 va_start(arglist, format);
190 txt=str_vtprintf(format, arglist);
191 va_end(arglist);
192 return txt;
193 }
194
str_vtprintf(LPCTSTR format,va_list start_ap)195 NOEXPORT LPTSTR str_vtprintf(LPCTSTR format, va_list start_ap) {
196 int n;
197 size_t size=32;
198 LPTSTR p;
199 va_list ap;
200
201 p=str_alloc(size*sizeof(TCHAR));
202 for(;;) {
203 va_copy(ap, start_ap);
204 n=_vsntprintf(p, size, format, ap);
205 if(n>-1 && n<(int)size)
206 return p;
207 size*=2;
208 p=str_realloc(p, size*sizeof(TCHAR));
209 }
210 }
211
212 #endif
213
214 /**************************************** memory allocation wrappers */
215
str_init(TLS_DATA * tls_data)216 void str_init(TLS_DATA *tls_data) {
217 tls_data->alloc_head=NULL;
218 tls_data->alloc_bytes=tls_data->alloc_blocks=0;
219 }
220
str_cleanup(TLS_DATA * tls_data)221 void str_cleanup(TLS_DATA *tls_data) {
222 /* free all attached allocations */
223 while(tls_data->alloc_head) /* str_free macro requires an lvalue */
224 str_free_expression(tls_data->alloc_head+1);
225 }
226
str_canary_init()227 void str_canary_init() {
228 if(canary_initialized!=CANARY_UNINTIALIZED)
229 return; /* prevent double initialization on config reload */
230 RAND_bytes(canary, (int)sizeof canary);
231 /* an error would reduce the effectiveness of canaries */
232 /* this is nothing critical, so the return value is ignored here */
233 canary_initialized=CANARY_INITIALIZED; /* after RAND_bytes */
234 }
235
str_stats()236 void str_stats() {
237 TLS_DATA *tls_data;
238 ALLOC_LIST *alloc_list;
239 int i=0;
240
241 if(!tls_initialized)
242 fatal("str not initialized");
243 leak_report();
244 tls_data=tls_get();
245 if(!tls_data || (!tls_data->alloc_blocks && !tls_data->alloc_bytes))
246 return; /* skip if no data is allocated */
247 s_log(LOG_DEBUG, "str_stats: %lu block(s), "
248 "%lu data byte(s), %lu control byte(s)",
249 (unsigned long)tls_data->alloc_blocks,
250 (unsigned long)tls_data->alloc_bytes,
251 (unsigned long)(tls_data->alloc_blocks*
252 (sizeof(ALLOC_LIST)+sizeof canary)));
253 for(alloc_list=tls_data->alloc_head; alloc_list; alloc_list=alloc_list->next) {
254 if(++i>10) /* limit the number of results */
255 break;
256 s_log(LOG_DEBUG, "str_stats: %lu byte(s) at %s:%d",
257 (unsigned long)alloc_list->size,
258 alloc_list->alloc_file, alloc_list->alloc_line);
259 }
260 }
261
str_alloc_debug(size_t size,const char * file,int line)262 void *str_alloc_debug(size_t size, const char *file, int line) {
263 TLS_DATA *tls_data;
264 ALLOC_LIST *alloc_list;
265
266 if(!tls_initialized)
267 fatal_debug("str not initialized", file, line);
268 tls_data=tls_get();
269 if(!tls_data) {
270 tls_data=tls_alloc(NULL, NULL, "alloc");
271 s_log(LOG_CRIT, "INTERNAL ERROR: Uninitialized TLS at %s, line %d",
272 file, line);
273 }
274
275 alloc_list=(ALLOC_LIST *)str_alloc_detached_debug(size, file, line)-1;
276 alloc_list->prev=NULL;
277 alloc_list->next=tls_data->alloc_head;
278 alloc_list->tls=tls_data;
279 if(tls_data->alloc_head)
280 tls_data->alloc_head->prev=alloc_list;
281 tls_data->alloc_head=alloc_list;
282 tls_data->alloc_bytes+=size;
283 tls_data->alloc_blocks++;
284
285 return alloc_list+1;
286 }
287
str_alloc_detached_debug(size_t size,const char * file,int line)288 void *str_alloc_detached_debug(size_t size, const char *file, int line) {
289 ALLOC_LIST *alloc_list;
290
291 #if 0
292 printf("allocating %lu bytes at %s:%d\n", (unsigned long)size, file, line);
293 #endif
294 alloc_list=system_malloc(sizeof(ALLOC_LIST)+size+sizeof canary);
295 if(!alloc_list)
296 fatal_debug("Out of memory", file, line);
297 memset(alloc_list, 0, sizeof(ALLOC_LIST)+size+sizeof canary);
298 alloc_list->prev=NULL; /* for debugging */
299 alloc_list->next=NULL; /* for debugging */
300 alloc_list->tls=NULL;
301 alloc_list->size=size;
302 alloc_list->alloc_file=file;
303 alloc_list->alloc_line=line;
304 alloc_list->free_file="none";
305 alloc_list->free_line=0;
306 #ifdef DEBUG_PADDING
307 snprintf(alloc_list->debug+1, DEBUG_PADDING-1, "ALLOC_%lu@%s:%d",
308 (unsigned long)size, file, line);
309 #endif
310 alloc_list->valid_canary=canary_initialized; /* before memcpy */
311 memcpy((uint8_t *)(alloc_list+1)+size, canary, sizeof canary);
312 alloc_list->magic=MAGIC_ALLOCATED;
313 str_leak_debug(alloc_list, 1);
314
315 return alloc_list+1;
316 }
317
str_realloc_debug(void * ptr,size_t size,const char * file,int line)318 void *str_realloc_debug(void *ptr, size_t size, const char *file, int line) {
319 if(ptr)
320 return str_realloc_internal_debug(ptr, size, file, line);
321 else
322 return str_alloc_debug(size, file, line);
323 }
324
str_realloc_detached_debug(void * ptr,size_t size,const char * file,int line)325 void *str_realloc_detached_debug(void *ptr, size_t size, const char *file, int line) {
326 if(ptr)
327 return str_realloc_internal_debug(ptr, size, file, line);
328 else
329 return str_alloc_detached_debug(size, file, line);
330 }
331
str_realloc_internal_debug(void * ptr,size_t size,const char * file,int line)332 NOEXPORT void *str_realloc_internal_debug(void *ptr, size_t size, const char *file, int line) {
333 ALLOC_LIST *prev_alloc_list, *alloc_list;
334
335 prev_alloc_list=get_alloc_list_ptr(ptr, file, line);
336 str_leak_debug(prev_alloc_list, -1);
337 if(prev_alloc_list->size>size) /* shrinking the allocation */
338 memset((uint8_t *)ptr+size, 0, prev_alloc_list->size-size); /* paranoia */
339 alloc_list=system_realloc(prev_alloc_list, sizeof(ALLOC_LIST)+size+sizeof canary);
340 if(!alloc_list)
341 fatal_debug("Out of memory", file, line);
342 ptr=alloc_list+1;
343 if(size>alloc_list->size) /* growing the allocation */
344 memset((uint8_t *)ptr+alloc_list->size, 0, size-alloc_list->size);
345 if(alloc_list->tls) { /* not detached */
346 /* refresh possibly invalidated linked list pointers */
347 if(alloc_list->tls->alloc_head==prev_alloc_list)
348 alloc_list->tls->alloc_head=alloc_list;
349 if(alloc_list->next)
350 alloc_list->next->prev=alloc_list;
351 if(alloc_list->prev)
352 alloc_list->prev->next=alloc_list;
353 /* update statistics while the old size is still available */
354 alloc_list->tls->alloc_bytes+=size-alloc_list->size;
355 }
356 alloc_list->size=size;
357 alloc_list->alloc_file=file;
358 alloc_list->alloc_line=line;
359 alloc_list->free_file="none";
360 alloc_list->free_line=0;
361 #ifdef DEBUG_PADDING
362 snprintf(alloc_list->debug+1, DEBUG_PADDING-1, "ALLOC_%lu@%s:%d",
363 (unsigned long)size, file, line);
364 #endif
365 alloc_list->valid_canary=canary_initialized; /* before memcpy */
366 memcpy((uint8_t *)ptr+size, canary, sizeof canary);
367 str_leak_debug(alloc_list, 1);
368 return ptr;
369 }
370
371 /* detach from thread automatic deallocation list */
372 /* it has no effect if the allocation is already detached */
str_detach_debug(void * ptr,const char * file,int line)373 void str_detach_debug(void *ptr, const char *file, int line) {
374 ALLOC_LIST *alloc_list;
375
376 if(!ptr) /* do not attempt to free null pointers */
377 return;
378 alloc_list=get_alloc_list_ptr(ptr, file, line);
379 if(alloc_list->tls) { /* not detached */
380 /* remove from linked list */
381 if(alloc_list->tls->alloc_head==alloc_list)
382 alloc_list->tls->alloc_head=alloc_list->next;
383 if(alloc_list->next)
384 alloc_list->next->prev=alloc_list->prev;
385 if(alloc_list->prev)
386 alloc_list->prev->next=alloc_list->next;
387 /* update statistics */
388 alloc_list->tls->alloc_bytes-=alloc_list->size;
389 alloc_list->tls->alloc_blocks--;
390 /* clear pointers */
391 alloc_list->next=NULL;
392 alloc_list->prev=NULL;
393 alloc_list->tls=NULL;
394 }
395 }
396
str_free_debug(void * ptr,const char * file,int line)397 void str_free_debug(void *ptr, const char *file, int line) {
398 ALLOC_LIST *alloc_list;
399
400 if(!ptr) /* do not attempt to free null pointers */
401 return;
402 alloc_list=(ALLOC_LIST *)ptr-1;
403 if(alloc_list->magic==MAGIC_DEALLOCATED) { /* double free */
404 /* this may (unlikely) log garbage instead of file names */
405 s_log(LOG_CRIT, "INTERNAL ERROR: Double free attempt: "
406 "ptr=%p alloc=%s:%d free#1=%s:%d free#2=%s:%d",
407 ptr,
408 alloc_list->alloc_file, alloc_list->alloc_line,
409 alloc_list->free_file, alloc_list->free_line,
410 file, line);
411 return;
412 }
413 str_detach_debug(ptr, file, line);
414 str_leak_debug(alloc_list, -1);
415 alloc_list->free_file=file;
416 alloc_list->free_line=line;
417 alloc_list->magic=MAGIC_DEALLOCATED; /* detect double free attempts */
418 memset(ptr, 0, alloc_list->size+sizeof canary); /* paranoia */
419 system_free(alloc_list);
420 }
421
get_alloc_list_ptr(void * ptr,const char * file,int line)422 NOEXPORT ALLOC_LIST *get_alloc_list_ptr(void *ptr, const char *file, int line) {
423 ALLOC_LIST *alloc_list;
424
425 if(!tls_initialized)
426 fatal_debug("str not initialized", file, line);
427 alloc_list=(ALLOC_LIST *)ptr-1;
428 if(alloc_list->magic!=MAGIC_ALLOCATED) /* not allocated by str_alloc() */
429 fatal_debug("Bad magic", file, line); /* LOL */
430 if(alloc_list->tls /* not detached */ && alloc_list->tls!=tls_get())
431 fatal_debug("Memory allocated in a different thread", file, line);
432 if(alloc_list->valid_canary!=CANARY_UNINTIALIZED &&
433 safe_memcmp((uint8_t *)ptr+alloc_list->size, canary, sizeof canary))
434 fatal_debug("Dead canary", file, line); /* LOL */
435 return alloc_list;
436 }
437
438 /**************************************** memory leak detection */
439
str_leak_debug(const ALLOC_LIST * alloc_list,int change)440 NOEXPORT void str_leak_debug(const ALLOC_LIST *alloc_list, int change) {
441 static size_t entries=0;
442 LEAK_ENTRY *entry;
443 int new_entry;
444 int allocations;
445
446 if(service_options.log_level<LOG_DEBUG) /* performance optimization */
447 return;
448 #ifdef USE_OS_THREADS
449 if(!stunnel_locks[STUNNEL_LOCKS-1]) /* threads not initialized */
450 return;
451 #endif /* USE_OS_THREADS */
452 if(!number_of_sections) /* configuration file not initialized */
453 return;
454
455 entry=leak_search(alloc_list);
456 /* the race condition may lead to false positives, which is handled later */
457 new_entry=entry->alloc_line!=alloc_list->alloc_line ||
458 entry->alloc_file!=alloc_list->alloc_file;
459
460 if(new_entry) { /* the file:line pair was encountered for the first time */
461 CRYPTO_THREAD_write_lock(stunnel_locks[LOCK_LEAK_HASH]);
462 entry=leak_search(alloc_list); /* the list may have changed */
463 if(entry->alloc_line==0) {
464 if(entries>LEAK_TABLE_SIZE-100) { /* this should never happen */
465 CRYPTO_THREAD_unlock(stunnel_locks[LOCK_LEAK_HASH]);
466 return;
467 }
468 entries++;
469 entry->alloc_line=alloc_list->alloc_line;
470 entry->alloc_file=alloc_list->alloc_file;
471 }
472 CRYPTO_THREAD_unlock(stunnel_locks[LOCK_LEAK_HASH]);
473 }
474
475 /* for performance we try to avoid calling CRYPTO_atomic_add() here */
476 #ifdef USE_OS_THREADS
477 #if defined(__GNUC__) && defined(__ATOMIC_ACQ_REL)
478 if(__atomic_is_lock_free(sizeof entry->num, &entry->num))
479 allocations=__atomic_add_fetch(&entry->num, change, __ATOMIC_ACQ_REL);
480 else
481 CRYPTO_atomic_add(&entry->num, change, &allocations,
482 stunnel_locks[LOCK_LEAK_HASH]);
483 #elif defined(_MSC_VER)
484 allocations=InterlockedExchangeAdd(&entry->num, change)+change;
485 #else /* atomic add not directly supported by the compiler */
486 CRYPTO_atomic_add(&entry->num, change, &allocations,
487 stunnel_locks[LOCK_LEAK_HASH]);
488 #endif
489 #else /* USE_OS_THREADS */
490 allocations=(entry->num+=change);
491 #endif /* USE_OS_THREADS */
492
493 if(allocations<=leak_threshold()) /* leak not detected */
494 return;
495 if(allocations<=entry->max) /* not the biggest leak for this entry */
496 return;
497 if(entry->max) { /* not the first time we found a leak for this entry */
498 entry->max=allocations; /* just update the value */
499 return;
500 }
501 /* we *may* need to allocate a new leak_results entry */
502 /* locking is slow, so we try to avoid it if possible */
503 CRYPTO_THREAD_write_lock(stunnel_locks[LOCK_LEAK_RESULTS]);
504 if(entry->max==0) /* the table may have changed */
505 leak_results[leak_result_num++]=entry;
506 entry->max=allocations;
507 CRYPTO_THREAD_unlock(stunnel_locks[LOCK_LEAK_RESULTS]);
508 }
509
510 /* O(1) hash table lookup */
leak_search(const ALLOC_LIST * alloc_list)511 NOEXPORT LEAK_ENTRY *leak_search(const ALLOC_LIST *alloc_list) {
512 /* a trivial hash based on source file name *address* and line number */
513 unsigned i=((unsigned)(uintptr_t)alloc_list->alloc_file+
514 (unsigned)alloc_list->alloc_line)%LEAK_TABLE_SIZE;
515
516 while(!(leak_hash_table[i].alloc_line==0 ||
517 (leak_hash_table[i].alloc_line==alloc_list->alloc_line &&
518 leak_hash_table[i].alloc_file==alloc_list->alloc_file)))
519 i=(i+1)%LEAK_TABLE_SIZE;
520 return leak_hash_table+i;
521 }
522
leak_table_utilization()523 void leak_table_utilization() {
524 int i, utilization=0;
525
526 for(i=0; i<LEAK_TABLE_SIZE; ++i) {
527 if(leak_hash_table[i].alloc_line) {
528 ++utilization;
529 #if 0
530 s_log(LOG_DEBUG, "Leak hash entry %d: %s:%d", i,
531 leak_hash_table[i].alloc_file, leak_hash_table[i].alloc_line);
532 #endif
533 }
534 }
535 s_log(LOG_DEBUG, "Leak detection table utilization: %d/%d, %02.2f%%",
536 utilization, LEAK_TABLE_SIZE, 100.0*utilization/LEAK_TABLE_SIZE);
537 }
538
539 /* report identified leaks */
leak_report()540 NOEXPORT void leak_report() {
541 int i;
542 long limit;
543
544 limit=leak_threshold();
545
546 CRYPTO_THREAD_read_lock(stunnel_locks[LOCK_LEAK_RESULTS]);
547 for(i=0; i<leak_result_num; ++i)
548 if(leak_results[i] /* an officious compiler could reorder code */ &&
549 leak_results[i]->max>limit /* the limit could have changed */)
550 s_log(LOG_WARNING, "Possible memory leak at %s:%d: %d allocations",
551 leak_results[i]->alloc_file, leak_results[i]->alloc_line,
552 leak_results[i]->max);
553 CRYPTO_THREAD_unlock(stunnel_locks[LOCK_LEAK_RESULTS]);
554 }
555
leak_threshold()556 NOEXPORT long leak_threshold() {
557 long limit;
558
559 limit=10000*((int)number_of_sections+1);
560 #ifndef USE_FORK
561 limit+=100*num_clients;
562 #endif
563 return limit;
564 }
565
566 /**************************************** memcmp() replacement */
567
568 /* a version of memcmp() with execution time not dependent on data values */
569 /* it does *not* allow testing whether s1 is greater or lesser than s2 */
safe_memcmp(const void * s1,const void * s2,size_t n)570 int safe_memcmp(const void *s1, const void *s2, size_t n) {
571 #ifdef _WIN64
572 typedef unsigned long long TL;
573 #else
574 typedef unsigned long TL;
575 #endif
576 typedef unsigned char TS;
577 TL r=0, *pl1, *pl2;
578 TS *ps1, *ps2;
579 int n1=(int)((uintptr_t)s1&(sizeof(TL)-1)); /* unaligned bytes in s1 */
580 int n2=(int)((uintptr_t)s2&(sizeof(TL)-1)); /* unaligned bytes in s2 */
581
582 if(n1 || n2) { /* either pointer unaligned */
583 ps1=(TS *)s1;
584 ps2=(TS *)s2;
585 } else { /* both pointers aligned -> compare full words */
586 pl1=(TL *)s1;
587 pl2=(TL *)s2;
588 while(n>=sizeof(TL)) {
589 n-=sizeof(TL);
590 r|=(*pl1++)^(*pl2++);
591 }
592 ps1=(TS *)pl1;
593 ps2=(TS *)pl2;
594 }
595 while(n--)
596 r|=(*ps1++)^(*ps2++);
597 return r!=0;
598 }
599
600 /* end of str.c */
601