Add sodium_{malloc,allocarray,free}() and sodium_mprotect_*()

ptr = sodium_malloc(size) returns a pointer from which exactly "size" bytes
can be accessed.

ptr = sodium_allocarray(count, size) allocates enough storage space for
"count" pointers or scalars of unit size "size".

In both cases, the region is immediately followed by a guard page.
As a result, any attempt to access a memory location after ptr[size - 1] will
immediately trigger a segmentation fault.

The allocated region is mlock()ed and filled with 0xd0 bytes.

A read-only page with the size, a guard page, as well as a canary are
placed before the returned pointer.

The canary is checked by sodium_free(); as a result, altering data right
before ptr is likely to cause sodium_free() to kill the process.

sodium_free() munlock()s the region and fills it with zeros before
actually calling free().

sodium_mprotect_noaccess(), sodium_mprotect_readonly() and
sodium_mprotect_readwrite() can be used to change the protection on the set
of allocated pages.

Reverting the protection to read+write is not required before calling
sodium_free().
This commit is contained in:
Frank Denis 2014-07-26 15:52:02 -07:00
parent 729a54f4a2
commit 473e1718cc
16 changed files with 535 additions and 1 deletions

2
.gitignore vendored
View File

@ -100,6 +100,8 @@ test/default/shorthash
test/default/sign test/default/sign
test/default/sodium_core test/default/sodium_core
test/default/sodium_utils test/default/sodium_utils
test/default/sodium_utils2
test/default/sodium_utils3
test/default/sodium_version test/default/sodium_version
test/default/stream test/default/stream
test/default/stream2 test/default/stream2

View File

@ -187,6 +187,12 @@
<ClCompile Include="..\..\..\..\test\default\sodium_utils.c"> <ClCompile Include="..\..\..\..\test\default\sodium_utils.c">
<ExcludedFromBuild>true</ExcludedFromBuild> <ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_utils2.c">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_utils3.c">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_version.c"> <ClCompile Include="..\..\..\..\test\default\sodium_version.c">
<ExcludedFromBuild>true</ExcludedFromBuild> <ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile> </ClCompile>

View File

@ -118,6 +118,12 @@
<ClCompile Include="..\..\..\..\test\default\sodium_utils.c"> <ClCompile Include="..\..\..\..\test\default\sodium_utils.c">
<Filter>src</Filter> <Filter>src</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_utils2.c">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_utils3.c">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_version.c"> <ClCompile Include="..\..\..\..\test\default\sodium_version.c">
<Filter>src</Filter> <Filter>src</Filter>
</ClCompile> </ClCompile>

View File

@ -187,6 +187,12 @@
<ClCompile Include="..\..\..\..\test\default\sodium_utils.c"> <ClCompile Include="..\..\..\..\test\default\sodium_utils.c">
<ExcludedFromBuild>true</ExcludedFromBuild> <ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_utils2.c">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_utils3.c">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_version.c"> <ClCompile Include="..\..\..\..\test\default\sodium_version.c">
<ExcludedFromBuild>true</ExcludedFromBuild> <ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile> </ClCompile>

View File

@ -118,6 +118,12 @@
<ClCompile Include="..\..\..\..\test\default\sodium_utils.c"> <ClCompile Include="..\..\..\..\test\default\sodium_utils.c">
<Filter>src</Filter> <Filter>src</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_utils2.c">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_utils3.c">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_version.c"> <ClCompile Include="..\..\..\..\test\default\sodium_version.c">
<Filter>src</Filter> <Filter>src</Filter>
</ClCompile> </ClCompile>

View File

@ -187,6 +187,12 @@
<ClCompile Include="..\..\..\..\test\default\sodium_utils.c"> <ClCompile Include="..\..\..\..\test\default\sodium_utils.c">
<ExcludedFromBuild>true</ExcludedFromBuild> <ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_utils2.c">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_utils3.c">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_version.c"> <ClCompile Include="..\..\..\..\test\default\sodium_version.c">
<ExcludedFromBuild>true</ExcludedFromBuild> <ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile> </ClCompile>

View File

@ -118,6 +118,12 @@
<ClCompile Include="..\..\..\..\test\default\sodium_utils.c"> <ClCompile Include="..\..\..\..\test\default\sodium_utils.c">
<Filter>src</Filter> <Filter>src</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_utils2.c">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_utils3.c">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\test\default\sodium_version.c"> <ClCompile Include="..\..\..\..\test\default\sodium_version.c">
<Filter>src</Filter> <Filter>src</Filter>
</ClCompile> </ClCompile>

View File

@ -412,7 +412,7 @@ dnl Checks for functions and headers
AS_IF([test "x$EMSCRIPTEN" = "x"],[ AS_IF([test "x$EMSCRIPTEN" = "x"],[
AC_CHECK_FUNCS([arc4random arc4random_buf]) 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]) AC_SUBST([LIBTOOL_EXTRA_FLAGS])

View File

@ -43,6 +43,55 @@ int sodium_mlock(void * const addr, const size_t len);
SODIUM_EXPORT SODIUM_EXPORT
int sodium_munlock(void * const addr, const size_t len); 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 #ifdef __cplusplus
} }
#endif #endif

View File

@ -3,6 +3,7 @@
#include "crypto_onetimeauth.h" #include "crypto_onetimeauth.h"
#include "randombytes.h" #include "randombytes.h"
#include "runtime.h" #include "runtime.h"
#include "utils.h"
static int initialized; static int initialized;
@ -17,6 +18,7 @@ sodium_init(void)
return -1; return -1;
} }
randombytes_stir(); randombytes_stir();
_sodium_alloc_init();
initialized = 1; initialized = 1;
return 0; return 0;

View File

@ -1,8 +1,10 @@
#ifndef __STDC_WANT_LIB_EXT1__ #ifndef __STDC_WANT_LIB_EXT1__
# define __STDC_WANT_LIB_EXT1__ 1 # define __STDC_WANT_LIB_EXT1__ 1
#endif #endif
#include <assert.h>
#include <errno.h> #include <errno.h>
#include <limits.h> #include <limits.h>
#include <signal.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
@ -17,8 +19,32 @@
#ifdef _WIN32 #ifdef _WIN32
# include <windows.h> # include <windows.h>
# include <wincrypt.h> # include <wincrypt.h>
#else
# include <unistd.h>
#endif #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 #ifdef HAVE_WEAK_SYMBOLS
__attribute__((weak)) void __attribute__((weak)) void
__sodium_dummy_symbol_to_prevent_lto(void * const pnt, const size_t len) __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; return -1;
#endif #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);
}

View File

@ -49,6 +49,8 @@ EXTRA_DIST = \
sign.exp \ sign.exp \
sodium_core.exp \ sodium_core.exp \
sodium_utils.exp \ sodium_utils.exp \
sodium_utils2.exp \
sodium_utils3.exp \
sodium_version.exp \ sodium_version.exp \
stream.exp \ stream.exp \
stream2.exp \ stream2.exp \
@ -104,6 +106,8 @@ DISTCLEANFILES = \
sign.res \ sign.res \
sodium_core.res \ sodium_core.res \
sodium_utils.res \ sodium_utils.res \
sodium_utils2.res \
sodium_utils3.res \
sodium_version.res \ sodium_version.res \
stream.res \ stream.res \
stream2.res \ stream2.res \
@ -167,6 +171,8 @@ TESTS_TARGETS = \
sign \ sign \
sodium_core \ sodium_core \
sodium_utils \ sodium_utils \
sodium_utils2 \
sodium_utils3 \
sodium_version \ sodium_version \
stream \ stream \
stream2 \ stream2 \
@ -322,6 +328,12 @@ sodium_core_LDADD = $(TESTS_LDADD)
sodium_utils_SOURCE = cmptest.h sodium_utils.c sodium_utils_SOURCE = cmptest.h sodium_utils.c
sodium_utils_LDADD = $(TESTS_LDADD) 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_SOURCE = cmptest.h sodium_version.c
sodium_version_LDADD = $(TESTS_LDADD) sodium_version_LDADD = $(TESTS_LDADD)

View File

@ -0,0 +1,70 @@
#include <sys/types.h>
#include <limits.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <string.h>
#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;
}

View File

@ -0,0 +1,3 @@
OK
Intentional segfault / bus error caught
OK

View File

@ -0,0 +1,55 @@
#include <sys/types.h>
#include <limits.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <string.h>
#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;
}

View File

@ -0,0 +1,2 @@
Intentional segfault / bus error caught
OK