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