diff --git a/COPYING b/COPYING index 333ed40..d3899a9 100644 --- a/COPYING +++ b/COPYING @@ -368,6 +368,7 @@ Files: src/arc4random_unix.h src/arc4random_win.h src/closefrom.c + src/freezero.c src/getentropy_aix.c src/getentropy_bsd.c src/getentropy_hpux.c @@ -378,6 +379,7 @@ Files: src/getentropy_win.c src/readpassphrase.c src/reallocarray.c + src/recallocarray.c src/strlcat.c src/strlcpy.c Copyright: @@ -389,7 +391,7 @@ Copyright: Todd C. Miller Copyright © 2004 Ted Unangst Copyright © 2008 Damien Miller - Copyright © 2008 Otto Moerbeek + Copyright © 2008, 2010-2011, 2016-2017 Otto Moerbeek Copyright © 2013 Markus Friedl Copyright © 2014 Bob Beck Copyright © 2014 Brent Cook diff --git a/include/bsd/stdlib.h b/include/bsd/stdlib.h index ea3251d..2bee974 100644 --- a/include/bsd/stdlib.h +++ b/include/bsd/stdlib.h @@ -75,6 +75,8 @@ void *reallocf(void *ptr, size_t size); (defined(__GLIBC__) && (!__GLIBC_PREREQ(2, 26) || !defined(_GNU_SOURCE))) void *reallocarray(void *ptr, size_t nmemb, size_t size); #endif +void *recallocarray(void *ptr, size_t oldnmemb, size_t nmemb, size_t size); +void freezero(void *ptr, size_t size); long long strtonum(const char *nptr, long long minval, long long maxval, const char **errstr); diff --git a/man/Makefile.am b/man/Makefile.am index e3b27da..4c7ed05 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -165,6 +165,7 @@ dist_man_MANS = \ fgetln.3bsd \ fgetwln.3bsd \ flopen.3bsd \ + freezero.3bsd \ fmtcheck.3bsd \ fparseln.3bsd \ fpurge.3bsd \ @@ -195,6 +196,7 @@ dist_man_MANS = \ readpassphrase.3bsd \ reallocarray.3bsd \ reallocf.3bsd \ + recallocarray.3bsd \ setmode.3bsd \ setproctitle.3bsd \ setproctitle_init.3bsd \ diff --git a/man/freezero.3bsd b/man/freezero.3bsd new file mode 100644 index 0000000..09471db --- /dev/null +++ b/man/freezero.3bsd @@ -0,0 +1 @@ +.so man3/reallocarray.3bsd diff --git a/man/reallocarray.3bsd b/man/reallocarray.3bsd index 1a37d33..704466c 100644 --- a/man/reallocarray.3bsd +++ b/man/reallocarray.3bsd @@ -30,13 +30,15 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $OpenBSD: malloc.3,v 1.78 2014/05/01 18:41:59 jmc Exp $ +.\" $OpenBSD: malloc.3,v 1.126 2019/09/14 13:16:50 otto Exp $ .\" -.Dd $Mdocdate: May 1 2014 $ +.Dd $Mdocdate: September 14 2019 $ .Dt REALLOCARRAY 3bsd .Os .Sh NAME -.Nm reallocarray +.Nm reallocarray , +.Nm recallocarray , +.Nm freezero .Nd memory allocation and deallocation .Sh LIBRARY .ds str-Lb-libbsd Utility functions from BSD systems (libbsd, \-lbsd) @@ -49,59 +51,245 @@ for include usage.) .Ft void * .Fn reallocarray "void *ptr" "size_t nmemb" "size_t size" +.Ft void * +.Fn recallocarray "void *ptr" "size_t oldnmemb" "size_t nmemb" "size_t size" +.Ft void +.Fn freezero "void *ptr" "size_t size" .Sh DESCRIPTION .Pp -When using -.Fn malloc -be careful to avoid the following idiom: -.Bd -literal -offset indent -if ((p = malloc(num * size)) == NULL) - err(1, "malloc"); -.Ed -.Pp -The multiplication may lead to an integer overflow, which can -be avoided using the extension -.Fn reallocarray , -as follows: -.Bd -literal -offset indent -if ((p = reallocarray(NULL, num, size)) == NULL) - err(1, "malloc"); -.Ed -.Pp -Alternatively -.Fn calloc -is a more portable solution which comes with the cost of clearing memory. -.Pp -If -.Fn malloc -must be used, be sure to test for overflow: -.Bd -literal -offset indent -if (size && num > SIZE_MAX / size) { - errno = ENOMEM; - err(1, "overflow"); -} -.Ed -.Pp -The use of +Designed for safe allocation of arrays, +the .Fn reallocarray -or -.Fn calloc -is strongly encouraged when allocating multiple sized objects -in order to avoid possible integer overflows. +function is similar to +.Fn realloc +except it operates on +.Fa nmemb +members of size +.Fa size +and checks for integer overflow in the calculation +.Fa nmemb +* +.Fa size . +.Pp +Used for the allocation of memory holding sensitive data, +the +.Fn recallocarray +function guarantees that memory becoming unallocated is explicitly +.Em discarded , +meaning cached free objects are cleared with +.Xr explicit_bzero 3 . +.Pp +The +.Fn recallocarray +function is similar to +.Fn reallocarray +except it ensures newly allocated memory is cleared similar to +.Fn calloc . +If +.Fa ptr +is +.Dv NULL , +.Fa oldnmemb +is ignored and the call is equivalent to +.Fn calloc . +If +.Fa ptr +is not +.Dv NULL , +.Fa oldnmemb +must be a value such that +.Fa oldnmemb +* +.Fa size +is the size of the earlier allocation that returned +.Fa ptr , +otherwise the behavior is undefined. +The +.Fn freezero +function is similar to the +.Fn free +function except it ensures memory is explicitly discarded. +If +.Fa ptr +is +.Dv NULL , +no action occurs. +If +.Fa ptr +is not +.Dv NULL , +the +.Fa size +argument must be equal to or smaller than the size of the earlier allocation +that returned +.Fa ptr . +.Fn freezero +guarantees the memory range starting at +.Fa ptr +with length +.Fa size +is discarded while deallocating the whole object originally allocated. .Sh RETURN VALUES The .Fn reallocarray -function returns a pointer to the allocated space if successful; otherwise, +and +.Fn recallocarray +functions return a pointer to the allocated space if successful; otherwise, a null pointer is returned and .Va errno is set to .Er ENOMEM . +.Pp +If multiplying +.Fa nmemb +and +.Fa size +results in integer overflow, +.Fn reallocarray +and +.Fn recallocarray +return +.Dv NULL +and set +.Va errno +to +.Er ENOMEM . +.Pp +If +.Fa ptr +is not +.Dv NULL +and multiplying +.Fa oldnmemb +and +.Fa size +results in integer overflow +.Fn recallocarray +returns +.Dv NULL +and sets +.Va errno +to +.Er EINVAL . +.Sh IDIOMS +Consider +.Fn calloc +or the extensions +.Fn reallocarray +and +.Fn recallocarray +when there is multiplication in the +.Fa size +argument of +.Fn malloc +or +.Fn realloc . +For example, avoid this common idiom as it may lead to integer overflow: +.Bd -literal -offset indent +if ((p = malloc(num * size)) == NULL) + err(1, NULL); +.Ed +.Pp +A drop-in replacement is +.Fn reallocarray : +.Bd -literal -offset indent +if ((p = reallocarray(NULL, num, size)) == NULL) + err(1, NULL); +.Ed +.Pp +Alternatively, +.Fn calloc +may be used at the cost of initialization overhead. +.Pp +When using +.Fn realloc , +be careful to avoid the following idiom: +.Bd -literal -offset indent +size += 50; +if ((p = realloc(p, size)) == NULL) + return (NULL); +.Ed +.Pp +Do not adjust the variable describing how much memory has been allocated +until the allocation has been successful. +This can cause aberrant program behavior if the incorrect size value is used. +In most cases, the above sample will also result in a leak of memory. +As stated earlier, a return value of +.Dv NULL +indicates that the old object still remains allocated. +Better code looks like this: +.Bd -literal -offset indent +newsize = size + 50; +if ((newp = realloc(p, newsize)) == NULL) { + free(p); + p = NULL; + size = 0; + return (NULL); +} +p = newp; +size = newsize; +.Ed +.Pp +As with +.Fn malloc , +it is important to ensure the new size value will not overflow; +i.e. avoid allocations like the following: +.Bd -literal -offset indent +if ((newp = realloc(p, num * size)) == NULL) { + ... +.Ed +.Pp +Instead, use +.Fn reallocarray : +.Bd -literal -offset indent +if ((newp = reallocarray(p, num, size)) == NULL) { + ... +.Ed +.Pp +Calling +.Fn realloc +with a +.Dv NULL +.Fa ptr +is equivalent to calling +.Fn malloc . +Instead of this idiom: +.Bd -literal -offset indent +if (p == NULL) + newp = malloc(newsize); +else + newp = realloc(p, newsize); +.Ed +.Pp +Use the following: +.Bd -literal -offset indent +newp = realloc(p, newsize); +.Ed +.Pp +The +.Fn recallocarray +function should be used for resizing objects containing sensitive data like +keys. +To avoid leaking information, +it guarantees memory is cleared before placing it on the internal free list. +Deallocation of such an object should be done by calling +.Fn freezero . + .Sh SEE ALSO .Xr malloc 3 , .Xr calloc 3 , .Xr alloca 3 .Sh HISTORY +The .Fn reallocarray -appeared in +function appeared in .Ox 5.6 , -glibc 2.26. +and glibc 2.26. +The +.Fn recallocarray +function appeared in +.Ox 6.1 . +The +.Fn freezero +function appeared in +.Ox 6.2 . diff --git a/man/recallocarray.3bsd b/man/recallocarray.3bsd new file mode 100644 index 0000000..09471db --- /dev/null +++ b/man/recallocarray.3bsd @@ -0,0 +1 @@ +.so man3/reallocarray.3bsd diff --git a/src/Makefile.am b/src/Makefile.am index c4229cf..75cdecd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -86,6 +86,7 @@ libbsd_la_SOURCES = \ expand_number.c \ explicit_bzero.c \ fgetln.c \ + freezero.c \ fgetwln.c \ flopen.c \ fmtcheck.c \ @@ -111,6 +112,7 @@ libbsd_la_SOURCES = \ readpassphrase.c \ reallocarray.c \ reallocf.c \ + recallocarray.c \ setmode.c \ setproctitle.c \ strlcat.c \ diff --git a/src/freezero.c b/src/freezero.c new file mode 100644 index 0000000..c565f43 --- /dev/null +++ b/src/freezero.c @@ -0,0 +1,30 @@ +/* $OpenBSD: malloc.c,v 1.267 2020/11/23 15:42:11 otto Exp $ */ +/* + * Copyright (c) 2008, 2010, 2011, 2016 Otto Moerbeek + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +void +freezero(void *ptr, size_t sz) +{ + /* This is legal. */ + if (ptr == NULL) + return; + + explicit_bzero(ptr, sz); + free(ptr); +} diff --git a/src/libbsd.map b/src/libbsd.map index 1416c6f..f499f8e 100644 --- a/src/libbsd.map +++ b/src/libbsd.map @@ -184,4 +184,7 @@ LIBBSD_0.10.0 { LIBBSD_0.11.0 { strnvisx; + + recallocarray; + freezero; } LIBBSD_0.10.0; diff --git a/src/recallocarray.c b/src/recallocarray.c new file mode 100644 index 0000000..84d736b --- /dev/null +++ b/src/recallocarray.c @@ -0,0 +1,80 @@ +/* $OpenBSD: recallocarray.c,v 1.1 2017/03/06 18:44:21 otto Exp $ */ +/* + * Copyright (c) 2008, 2017 Otto Moerbeek + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +/* + * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX + * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW + */ +#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) + +void * +recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size) +{ + size_t oldsize, newsize; + void *newptr; + + if (ptr == NULL) + return calloc(newnmemb, size); + + if ((newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + newnmemb > 0 && SIZE_MAX / newnmemb < size) { + errno = ENOMEM; + return NULL; + } + newsize = newnmemb * size; + + if ((oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + oldnmemb > 0 && SIZE_MAX / oldnmemb < size) { + errno = EINVAL; + return NULL; + } + oldsize = oldnmemb * size; + + /* + * Don't bother too much if we're shrinking just a bit, + * we do not shrink for series of small steps, oh well. + */ + if (newsize <= oldsize) { + size_t d = oldsize - newsize; + + if (d < oldsize / 2 && d < (size_t)getpagesize()) { + memset((char *)ptr + newsize, 0, d); + return ptr; + } + } + + newptr = malloc(newsize); + if (newptr == NULL) + return NULL; + + if (newsize > oldsize) { + memcpy(newptr, ptr, oldsize); + memset((char *)newptr + oldsize, 0, newsize - oldsize); + } else + memcpy(newptr, ptr, newsize); + + explicit_bzero(ptr, oldsize); + free(ptr); + + return newptr; +}