diff --git a/.gitignore b/.gitignore index 6898147f..6fb76919 100644 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,8 @@ test/default/shorthash test/default/sign test/default/sodium_core test/default/sodium_utils +test/default/sodium_utils2 +test/default/sodium_utils3 test/default/sodium_version test/default/stream test/default/stream2 diff --git a/builds/msvc/vs2010/test/test.vcxproj b/builds/msvc/vs2010/test/test.vcxproj index c84b2d65..d94f25dc 100644 --- a/builds/msvc/vs2010/test/test.vcxproj +++ b/builds/msvc/vs2010/test/test.vcxproj @@ -187,6 +187,12 @@ true + + true + + + true + true diff --git a/builds/msvc/vs2010/test/test.vcxproj.filters b/builds/msvc/vs2010/test/test.vcxproj.filters index e09eef9b..2047e093 100644 --- a/builds/msvc/vs2010/test/test.vcxproj.filters +++ b/builds/msvc/vs2010/test/test.vcxproj.filters @@ -118,6 +118,12 @@ src + + src + + + src + src diff --git a/builds/msvc/vs2012/test/test.vcxproj b/builds/msvc/vs2012/test/test.vcxproj index 40c2afaf..a57f82c3 100644 --- a/builds/msvc/vs2012/test/test.vcxproj +++ b/builds/msvc/vs2012/test/test.vcxproj @@ -187,6 +187,12 @@ true + + true + + + true + true diff --git a/builds/msvc/vs2012/test/test.vcxproj.filters b/builds/msvc/vs2012/test/test.vcxproj.filters index e09eef9b..2047e093 100644 --- a/builds/msvc/vs2012/test/test.vcxproj.filters +++ b/builds/msvc/vs2012/test/test.vcxproj.filters @@ -118,6 +118,12 @@ src + + src + + + src + src diff --git a/builds/msvc/vs2013/test/test.vcxproj b/builds/msvc/vs2013/test/test.vcxproj index eff09cc2..61a66b89 100644 --- a/builds/msvc/vs2013/test/test.vcxproj +++ b/builds/msvc/vs2013/test/test.vcxproj @@ -187,6 +187,12 @@ true + + true + + + true + true diff --git a/builds/msvc/vs2013/test/test.vcxproj.filters b/builds/msvc/vs2013/test/test.vcxproj.filters index e09eef9b..2047e093 100644 --- a/builds/msvc/vs2013/test/test.vcxproj.filters +++ b/builds/msvc/vs2013/test/test.vcxproj.filters @@ -118,6 +118,12 @@ src + + src + + + src + src diff --git a/configure.ac b/configure.ac index 1ff2bf38..83430352 100644 --- a/configure.ac +++ b/configure.ac @@ -412,7 +412,7 @@ dnl Checks for functions and headers AS_IF([test "x$EMSCRIPTEN" = "x"],[ AC_CHECK_FUNCS([arc4random arc4random_buf]) ]) -AC_CHECK_FUNCS([mlock explicit_bzero posix_memalign]) +AC_CHECK_FUNCS([mlock mprotect explicit_bzero posix_memalign]) AC_SUBST([LIBTOOL_EXTRA_FLAGS]) diff --git a/src/libsodium/include/sodium/utils.h b/src/libsodium/include/sodium/utils.h index 8e09d3de..b0d25cf0 100644 --- a/src/libsodium/include/sodium/utils.h +++ b/src/libsodium/include/sodium/utils.h @@ -43,6 +43,55 @@ int sodium_mlock(void * const addr, const size_t len); SODIUM_EXPORT int sodium_munlock(void * const addr, const size_t len); +/* WARNING: sodium_malloc() and sodium_allocarray() are not general-purpose + * allocation functions. + * + * They return a pointer to a region filled with 0xd0 bytes and immediately + * followed by a guard page. + * As a result, accessing a single byte after the requested allocation size + * will intentionally trigger a segmentation fault. + * + * A canary and an additional guard page placed before the beginning of the + * region may also kill the process if a buffer underflow is detected. + * + * The memory layout is: + * [unprotected region size (read only)][guard page (no access)][unprotected pages (read/write)][guard page (no access)] + * With the layout of the unprotected pages being: + * [optional padding][16-bytes canary][user region] + * + * However: + * - These functions are significantly slower than standard functions + * - Each allocation requires 3 or 4 additional pages + * - The returned address will not be aligned if the allocation size is not + * a multiple of the required alignment. For this reason, these functions + * are designed to store data, such as secret keys and messages. + * They should not be used to store pointers mixed with other types + * in portable code unless extreme care is taken to ensure correct + * pointers alignment. + */ + +SODIUM_EXPORT +void *sodium_malloc(const size_t size); + +SODIUM_EXPORT +void *sodium_allocarray(size_t count, size_t size); + +SODIUM_EXPORT +void sodium_free(void *ptr); + +SODIUM_EXPORT +int sodium_mprotect_noaccess(void *ptr); + +SODIUM_EXPORT +int sodium_mprotect_readonly(void *ptr); + +SODIUM_EXPORT +int sodium_mprotect_readwrite(void *ptr); + +/* -------- */ + +int _sodium_alloc_init(void); + #ifdef __cplusplus } #endif diff --git a/src/libsodium/sodium/core.c b/src/libsodium/sodium/core.c index 652f31e1..615851b1 100644 --- a/src/libsodium/sodium/core.c +++ b/src/libsodium/sodium/core.c @@ -3,6 +3,7 @@ #include "crypto_onetimeauth.h" #include "randombytes.h" #include "runtime.h" +#include "utils.h" static int initialized; @@ -17,6 +18,7 @@ sodium_init(void) return -1; } randombytes_stir(); + _sodium_alloc_init(); initialized = 1; return 0; diff --git a/src/libsodium/sodium/utils.c b/src/libsodium/sodium/utils.c index d751cb86..9152322e 100644 --- a/src/libsodium/sodium/utils.c +++ b/src/libsodium/sodium/utils.c @@ -1,8 +1,10 @@ #ifndef __STDC_WANT_LIB_EXT1__ # define __STDC_WANT_LIB_EXT1__ 1 #endif +#include #include #include +#include #include #include #include @@ -17,8 +19,32 @@ #ifdef _WIN32 # include # include +#else +# include #endif +#define CANARY_SIZE 16U +#define GARBAGE_VALUE 0xd0 + +#ifndef MAP_NOCORE +# define MAP_NOCORE 0 +#endif +#if !defined(MAP_ANON) && defined(MAP_ANONYMOUS) +# define MAP_ANON MAP_ANONYMOUS +#endif +#if defined(_WIN32) || defined(MAP_ANON) || defined(HAVE_POSIX_MEMALIGN) +# define HAVE_ALIGNED_MALLOC +#endif +#if defined(HAVE_MPROTECT) && !(defined(PROT_NONE) && defined(PROT_READ) && defined(PROT_WRITE)) +# undef HAVE_MPROTECT +#endif +#if defined(HAVE_ALIGNED_MALLOC) && (defined(_WIN32) || defined(HAVE_MPROTECT)) +# define HAVE_PAGE_PROTECTION +#endif + +static size_t page_size; +static unsigned char canary[CANARY_SIZE]; + #ifdef HAVE_WEAK_SYMBOLS __attribute__((weak)) void __sodium_dummy_symbol_to_prevent_lto(void * const pnt, const size_t len) @@ -174,3 +200,280 @@ sodium_munlock(void * const addr, const size_t len) return -1; #endif } + +int +_sodium_alloc_init(void) +{ +#if defined(_SC_PAGESIZE) + long page_size_ = sysconf(_SC_PAGESIZE); + if (page_size_ > 0L) { + page_size = (size_t) page_size_; + } +#elif defined(_WIN32) + SYSTEM_INFO si; + GetSystemInfo(&si); + page_size = (size_t) si.dwPageSize; +#endif + if (page_size < CANARY_SIZE) { + abort(); + } + randombytes_buf(canary, sizeof canary); + + return 0; +} + +static inline size_t +_page_round(const size_t size) +{ + const size_t page_mask = page_size - 1U; + + return (size + page_mask) & ~page_mask; +} + +static int +_mprotect_noaccess(void *ptr, size_t size) +{ +#if defined(HAVE_MPROTECT) && defined(HAVE_PAGE_PROTECTION) + return mprotect(ptr, size, PROT_NONE); +#elif defined(_WIN32) + { + DWORD old; + return -(VirtualProtect(ptr, size, PAGE_NOACCESS, &old) == 0); + } +#else + errno = ENOSYS; + return -1; +#endif +} + +static int +_mprotect_readonly(void *ptr, size_t size) +{ +#if defined(HAVE_MPROTECT) && defined(HAVE_PAGE_PROTECTION) + return mprotect(ptr, size, PROT_READ); +#elif defined(_WIN32) + { + DWORD old; + return -(VirtualProtect(ptr, size, PAGE_READONLY, &old) == 0); + } +#else + errno = ENOSYS; + return -1; +#endif +} + +static int +_mprotect_readwrite(void *ptr, size_t size) +{ +#if defined(HAVE_MPROTECT) && defined(HAVE_PAGE_PROTECTION) + return mprotect(ptr, size, PROT_READ | PROT_WRITE); +#elif defined(_WIN32) + { + DWORD old; + return -(VirtualProtect(ptr, size, PAGE_READWRITE, &old) == 0); + } +#else + errno = ENOSYS; + return -1; +#endif +} + +static void +_out_of_bounds(void) +{ +#ifdef SIGSEGV + raise(SIGSEGV); +#elif defined(SIGKILL) + raise(SIGKILL); +#endif + abort(); +} + +static __attribute__((malloc)) unsigned char * +_alloc_aligned(const size_t size) +{ + void *ptr; + +#ifdef MAP_ANON + if ((ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE | MAP_NOCORE, -1, 0)) == MAP_FAILED) { + ptr = NULL; + } +#elif defined(HAVE_POSIX_MEMALIGN) + if (posix_memalign(&ptr, page_size, size) != 0) { + ptr = NULL; + } +#elif defined(_WIN32) + ptr = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); +#elif !defined(HAVE_ALIGNED_MALLOC) + ptr = malloc(size); +#else +# error Bug +#endif + return (unsigned char *) ptr; +} + +static void +_free_aligned(unsigned char * const ptr, const size_t size) +{ +#ifdef MAP_ANON + (void) munmap(ptr, size); +#elif defined(HAVE_POSIX_MEMALIGN) + free(ptr); +#elif defined(_WIN32) + VirtualFree(ptr, 0U, MEM_RELEASE); +#else + free(ptr); +#endif +} + +static unsigned char * +_unprotected_ptr_from_user_ptr(const void *ptr) +{ + uintptr_t unprotected_ptr_u; + unsigned char *canary_ptr; + unsigned char *unprotected_ptr; + size_t page_mask; + + canary_ptr = ((unsigned char *) ptr) - sizeof canary; + page_mask = page_size - 1U; + unprotected_ptr_u = ((uintptr_t) canary_ptr & (uintptr_t) ~page_mask); + if (unprotected_ptr_u <= page_size * 2U) { + abort(); + } + return (unsigned char *) unprotected_ptr_u; +} + +static __attribute__((malloc)) void * +_sodium_malloc(const size_t size) +{ + void *user_ptr; + unsigned char *base_ptr; + unsigned char *canary_ptr; + unsigned char *unprotected_ptr; + size_t page_mask; + size_t size_with_canary; + size_t total_size; + size_t unprotected_size; + + if (size >= SIZE_MAX - page_size * 4U) { + errno = ENOMEM; + return NULL; + } + if (page_size <= sizeof canary || page_size < sizeof unprotected_size) { + abort(); + } + size_with_canary = (sizeof canary) + size; + unprotected_size = _page_round(size_with_canary); + total_size = page_size + page_size + unprotected_size + page_size; + if ((base_ptr = _alloc_aligned(total_size)) == NULL) { + return NULL; + } + unprotected_ptr = base_ptr + page_size * 2U; + _mprotect_noaccess(base_ptr + page_size, page_size); +#ifndef HAVE_PAGE_PROTECTION + memcpy(unprotected_ptr + unprotected_size, canary, sizeof canary); +#endif + _mprotect_noaccess(unprotected_ptr + unprotected_size, page_size); + sodium_mlock(unprotected_ptr, unprotected_size); + page_mask = page_size - 1U; + canary_ptr = unprotected_ptr + _page_round(size_with_canary) - + size_with_canary; + user_ptr = canary_ptr + sizeof canary; + memcpy(canary_ptr, canary, sizeof canary); + memcpy(base_ptr, &unprotected_size, sizeof unprotected_size); + _mprotect_readonly(base_ptr, page_size); + assert(_unprotected_ptr_from_user_ptr(user_ptr) == unprotected_ptr); + + return user_ptr; +} + +__attribute__((malloc)) void * +sodium_malloc(const size_t size) +{ + void *ptr; + + if ((ptr = _sodium_malloc(size)) == NULL) { + return NULL; + } + memset(ptr, (int) GARBAGE_VALUE, size); + + return ptr; +} + +__attribute__((malloc)) void * +sodium_allocarray(size_t count, size_t size) +{ + size_t total_size; + + if (size >= SIZE_MAX / count) { + errno = ENOMEM; + return NULL; + } + total_size = count * size; + + return sodium_malloc(total_size); +} + +void +sodium_free(void *ptr) +{ + unsigned char *base_ptr; + unsigned char *canary_ptr; + unsigned char *unprotected_ptr; + size_t total_size; + size_t unprotected_size; + + if (ptr == NULL) { + return; + } + canary_ptr = ((unsigned char *) ptr) - sizeof canary; + if (sodium_memcmp(canary_ptr, canary, sizeof canary) != 0) { + _out_of_bounds(); + } + unprotected_ptr = _unprotected_ptr_from_user_ptr(ptr); + base_ptr = unprotected_ptr - page_size * 2U; + memcpy(&unprotected_size, base_ptr, sizeof unprotected_size); + total_size = page_size + page_size + unprotected_size + page_size; + _mprotect_readwrite(base_ptr, total_size); +#ifndef HAVE_PAGE_PROTECTION + if (sodium_memcmp(unprotected_ptr + unprotected_size, + canary, sizeof canary) != 0) { + _out_of_bounds(); + } +#endif + sodium_munlock(unprotected_ptr, unprotected_size); + _free_aligned(base_ptr, total_size); +} + +static int +_sodium_mprotect(void *ptr, int (*cb)(void *ptr, size_t size)) +{ + unsigned char *base_ptr; + unsigned char *unprotected_ptr; + size_t unprotected_size; + + unprotected_ptr = _unprotected_ptr_from_user_ptr(ptr); + base_ptr = unprotected_ptr - page_size * 2U; + memcpy(&unprotected_size, base_ptr, sizeof unprotected_size); + + return cb(unprotected_ptr, unprotected_size); +} + +int +sodium_mprotect_noaccess(void *ptr) +{ + return _sodium_mprotect(ptr, _mprotect_noaccess); +} + +int +sodium_mprotect_readonly(void *ptr) +{ + return _sodium_mprotect(ptr, _mprotect_readonly); +} + +int +sodium_mprotect_readwrite(void *ptr) +{ + return _sodium_mprotect(ptr, _mprotect_readwrite); +} diff --git a/test/default/Makefile.am b/test/default/Makefile.am index c25efe1b..cdd44039 100644 --- a/test/default/Makefile.am +++ b/test/default/Makefile.am @@ -49,6 +49,8 @@ EXTRA_DIST = \ sign.exp \ sodium_core.exp \ sodium_utils.exp \ + sodium_utils2.exp \ + sodium_utils3.exp \ sodium_version.exp \ stream.exp \ stream2.exp \ @@ -104,6 +106,8 @@ DISTCLEANFILES = \ sign.res \ sodium_core.res \ sodium_utils.res \ + sodium_utils2.res \ + sodium_utils3.res \ sodium_version.res \ stream.res \ stream2.res \ @@ -167,6 +171,8 @@ TESTS_TARGETS = \ sign \ sodium_core \ sodium_utils \ + sodium_utils2 \ + sodium_utils3 \ sodium_version \ stream \ stream2 \ @@ -322,6 +328,12 @@ sodium_core_LDADD = $(TESTS_LDADD) sodium_utils_SOURCE = cmptest.h sodium_utils.c sodium_utils_LDADD = $(TESTS_LDADD) +sodium_utils2_SOURCE = cmptest.h sodium_utils2.c +sodium_utils2_LDADD = $(TESTS_LDADD) + +sodium_utils3_SOURCE = cmptest.h sodium_utils3.c +sodium_utils3_LDADD = $(TESTS_LDADD) + sodium_version_SOURCE = cmptest.h sodium_version.c sodium_version_LDADD = $(TESTS_LDADD) diff --git a/test/default/sodium_utils2.c b/test/default/sodium_utils2.c new file mode 100644 index 00000000..3555d0b3 --- /dev/null +++ b/test/default/sodium_utils2.c @@ -0,0 +1,70 @@ + +#include + +#include +#include +#include +#include +#include + +#define TEST_NAME "sodium_utils2" +#include "cmptest.h" + +static void +segv_handler(int sig) +{ + printf("Intentional segfault / bus error caught\n"); + printf("OK\n"); +#ifdef SIGSEGV + signal(SIGSEGV, SIG_DFL); +#endif +#ifdef SIGBUS + signal(SIGBUS, SIG_DFL); +#endif +#ifdef SIGABRT + signal(SIGABRT, SIG_DFL); +#endif + exit(0); +} + +int +main(void) +{ + void *buf; + size_t size; + unsigned int i; + + if (sodium_allocarray(SIZE_MAX / 2U + 1U, SIZE_MAX / 2U) != NULL) { + return 1; + } + sodium_free(sodium_malloc(0U)); + sodium_free(NULL); + for (i = 0U; i < 10000U; i++) { + size = randombytes_uniform(100000U); + buf = sodium_malloc(size); + memset(buf, i, size); + sodium_mprotect_readonly(buf); + sodium_free(buf); + } + printf("OK\n"); + +#ifdef SIGSEGV + signal(SIGSEGV, segv_handler); +#endif +#ifdef SIGBUS + signal(SIGBUS, segv_handler); +#endif +#ifdef SIGABRT + signal(SIGABRT, segv_handler); +#endif + size = randombytes_uniform(100000U); + buf = sodium_malloc(size); + sodium_mprotect_readonly(buf); + sodium_mprotect_readwrite(buf); + sodium_memzero(((unsigned char *) buf) + size, 1U); + sodium_mprotect_noaccess(buf); + sodium_free(buf); + printf("Overflow not caught\n"); + + return 0; +} diff --git a/test/default/sodium_utils2.exp b/test/default/sodium_utils2.exp new file mode 100644 index 00000000..f796351d --- /dev/null +++ b/test/default/sodium_utils2.exp @@ -0,0 +1,3 @@ +OK +Intentional segfault / bus error caught +OK diff --git a/test/default/sodium_utils3.c b/test/default/sodium_utils3.c new file mode 100644 index 00000000..d9f1ab68 --- /dev/null +++ b/test/default/sodium_utils3.c @@ -0,0 +1,55 @@ + +#include + +#include +#include +#include +#include +#include + +#define TEST_NAME "sodium_utils3" +#include "cmptest.h" + +static void +segv_handler(int sig) +{ + printf("Intentional segfault / bus error caught\n"); + printf("OK\n"); +#ifdef SIGSEGV + signal(SIGSEGV, SIG_DFL); +#endif +#ifdef SIGBUS + signal(SIGBUS, SIG_DFL); +#endif +#ifdef SIGABRT + signal(SIGABRT, SIG_DFL); +#endif + exit(0); +} + +int +main(void) +{ + void *buf; + size_t size; + +#ifdef SIGSEGV + signal(SIGSEGV, segv_handler); +#endif +#ifdef SIGBUS + signal(SIGBUS, segv_handler); +#endif +#ifdef SIGABRT + signal(SIGABRT, segv_handler); +#endif + size = randombytes_uniform(100000U); + buf = sodium_malloc(size); + sodium_mprotect_noaccess(buf); + sodium_mprotect_readwrite(buf); + sodium_memzero(((unsigned char *) buf) - 8, 8U); + sodium_mprotect_readonly(buf); + sodium_free(buf); + printf("Underflow not caught\n"); + + return 0; +} diff --git a/test/default/sodium_utils3.exp b/test/default/sodium_utils3.exp new file mode 100644 index 00000000..37e114f8 --- /dev/null +++ b/test/default/sodium_utils3.exp @@ -0,0 +1,2 @@ +Intentional segfault / bus error caught +OK