Skip to content

Commit

Permalink
Randomly seed a hash secret to mitigate ease of DOS attack
Browse files Browse the repository at this point in the history
Without a random hash seed it is easy for an attacker to generate
strings which will result in the same hash. This devolves to O(n**2)
time for the hash insertion and is increased even more by the fact that
a string must be compared with all strings in the bucket until the right
string is found.

This attack is done by creating a function that essentially is our
hashing function backward. We hash our target string, `t`. We then
use random 3 character sequences (in our case graphemes) and plug them
into our backward hashing function along with the hash for our target
`t`. The backward hash and the random character sequence are stored in
the dictionary and the process is repeated until we have a very large
number of backward hash's and random 3 grapheme prefixes.

We can then use this dictionary to construct successively longer strings
(or short if we so desire) which are the same hash as our target string
`t`.

This has been fixed in most programming languages (Python, Ruby, Perl),
and several CVE's have been issues over the years for this exploit.

It may also be a good idea to later implement a stronger hashing
function. Many languages are now using SipHash which is meant to protect
against an attacker discovering a hash secret remotely. This change
decreases the ease of this attack and makes the hash secret
unpredictable.

Randomness source:
We prefer function calls rather than reading from /dev/urandom. Reasons
include: not having to open a file descriptor and that /dev/urandom may
not exist if we are in a chroot. Since we don't want to stop startup of
MoarVM, if we have to fallback to /dev/urandom since the OS doesn't
support the function call we continue starting MoarVM normally.
Linux, FreeBSD, OpenBSD and MacOS all use system provided random calls
to get the data rather than having to open /dev/urandom. All these OS's
guarantee these to be non-blocking, though MacOS's documentation does
not comment on it.
Whether the calls block is primarily a concern during very early boot
which is why Python 3 makes sure to use non-blocking calls when their
hash secret is seeded an interpreter start up.

If not available we fall back to using /dev/urandom on Unix like OS's.
This change was tested on Linux both pre-addition of `getrandom()` and
after, as well as Solaris, FreeBSD and OpenBSD. CI testing for Windows
and MacOS look good. All Unix are supported due to /dev/urandom fallback
and Windows is supported to version 95 with the API we use.
  • Loading branch information
samcv committed Apr 25, 2018
1 parent ef4e6fe commit b2eb9e1
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 3 deletions.
2 changes: 1 addition & 1 deletion 3rdparty/uthash.h
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ do {
do { \
unsigned _hj_i,_hj_j,_hj_k; \
unsigned char *_hj_key=(unsigned char*)(key); \
hashv = 0xfeedbeef; \
hashv = tc->instance->hashSecret; \
_hj_i = _hj_j = 0x9e3779b9; \
_hj_k = (unsigned)(keylen); \
while (_hj_k >= 12) { \
Expand Down
2 changes: 2 additions & 0 deletions build/Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ OBJECTS = src/core/callsite@obj@ \
src/instrument/crossthreadwrite@obj@ \
src/instrument/line_coverage@obj@ \
src/platform/sys@obj@ \
src/platform/random@obj@ \
src/moar@obj@ \
@platform@ \
@jit_obj@
Expand Down Expand Up @@ -387,6 +388,7 @@ HEADERS = src/moar.h \
src/platform/sys.h \
src/platform/setjmp.h \
src/platform/memmem.h \
src/platform/random.h \
src/jit/graph.h \
src/jit/label.h \
src/jit/expr.h \
Expand Down
4 changes: 4 additions & 0 deletions src/core/instance.h
Original file line number Diff line number Diff line change
Expand Up @@ -508,4 +508,8 @@ struct MVMInstance {

/* Flag for if NFA debugging is enabled. */
MVMint8 nfa_debug_enabled;

/* Hash Secret which is used as the hash seed. This is to avoid denial of
* service type attacks. */
MVMuint32 hashSecret;
};
9 changes: 8 additions & 1 deletion src/moar.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "moar.h"
#include <platform/threads.h>

#include "platform/random.h"
#include "platform/time.h"
#if defined(_MSC_VER)
#define snprintf _snprintf
#endif
Expand Down Expand Up @@ -84,12 +85,18 @@ MVMInstance * MVM_vm_create_instance(void) {
char *jit_log, *jit_expr_disable, *jit_disable, *jit_bytecode_dir, *jit_last_frame, *jit_last_bb;
char *dynvar_log;
int init_stat;
MVMuint32 hashSecret;
MVMuint64 now = MVM_platform_now();


/* Set up instance data structure. */
instance = MVM_calloc(1, sizeof(MVMInstance));

/* Create the main thread's ThreadContext and stash it. */
instance->main_thread = MVM_tc_create(NULL, instance);
MVM_getrandom(instance->main_thread, &hashSecret, sizeof(MVMuint32));
instance->hashSecret ^= now;
instance->hashSecret ^= MVM_proc_getpid(instance->main_thread) * now;
instance->main_thread->thread_id = 1;

/* Next thread to be created gets ID 2 (the main thread got ID 1). */
Expand Down
142 changes: 142 additions & 0 deletions src/platform/random.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/* Get random numbers from OS. Returns 1 if it succeeded and otherwise 0
* Does not block. Designed for getting small amounts of random data at a time */
#include <stddef.h>
/* Solaris has both getrandom and getentropy. We use getrandom since getentropy
* can block. Solaris has had getrandom() and getentropy() since 11.3 */
#if defined(__sun)
#include <sys/random.h>
/* On solaris, _GRND_ENTROPY is defined if getentropy/getrandom are available */
#if defined(_GRND_ENTROPY)
#define MVM_random_use_getrandom 1
#endif
#endif
/* Linux added getrandom to kernel in 3.17 */
#if defined(__linux__)
#include <sys/syscall.h>
#if defined(SYS_getrandom)
/* With glibc you are supposed to declare _GNU_SOURCE to use the
* syscall function */
#define _GNU_SOURCE
#define GRND_NONBLOCK 0x01
#include <unistd.h>
#define MVM_random_use_getrandom_syscall 1
#else
#define MVM_random_use_urandom 1
#endif
#endif
/* FreeBSD added it with SVN revision 331279 Wed Mar 21, 2018
* This coorasponds to __FreeBSD_version version identifier: 1200061.
* https://svnweb.freebsd.org/base?view=revision&revision=r331279 */
#if defined(__FreeBSD__)
#include <osreldate.h>
#if __FreeBSD_version >= 1200061
#include <sys/random.h>
#define MVM_random_use_getrandom
#endif
#endif
/* OpenBSD's getentropy never blocks and always succeeds. OpenBSD has had
* getentropy() since 5.6 */
#if defined(__OpenBSD__)
#include <sys/param.h>
#if OpenBSD >= 201301
#define MVM_random_use_getentropy
#endif
#endif
/* MacOS has had getentropy() since 10.12 */
#if defined(__APPLE__)
#include <AvailabilityMacros.h>
#include <Availability.h>
#if !defined(MAC_OS_X_VERSION_10_12)
#define MAC_OS_X_VERSION_10_12 101200
#endif
//#include <AvailabilityMacros.h>
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12
#include <sys/random.h>
#define MVM_random_use_getentropy 1
#endif
#endif
/* Other info:
* NetBSD: I have not found evidence it has getentropy() or getrandom()
* Note: Uses __NetBSD_Version__ included from file <sys/param.h>.
* All BSD's should support arc4random
* AIX is a unix but has no arc4random, does have /dev/urandom */
#include "moar.h"

#if defined(MVM_random_use_getrandom_syscall)
/* getrandom() was added to glibc much later than it was added to the kernel. Since
* we detect the presence of the system call to decide whether to use this,
* just use the syscall instead since the wrapper is not guaranteed to exist.*/
MVMint32 MVM_getrandom (MVMThreadContext *tc, void *out, size_t size) {
return syscall(SYS_getrandom, out, size, GRND_NONBLOCK) <= 0 ? 0 : 1;
}
#elif defined(MVM_random_use_getrandom)
/* Call the getrandom() wrapper in Solaris and FreeBSD since they were
* added at the same time as getentropy() and this allows us to avoid blocking. */
MVMint32 MVM_getrandom (MVMThreadContext *tc, void *out, size_t size) {
return getrandom(out, size, GRND_NONBLOCK) <= 0 ? 0 : 1;
}

#elif defined(MVM_random_use_getentropy)
MVMint32 MVM_getrandom (MVMThreadContext *tc, void *out, size_t size) {
return getentropy(out, size) < 0 ? 0 : 1;
}

#elif defined(_WIN32)
#include <windows.h>
#include <wincrypt.h>
typedef BOOL (WINAPI *CRYPTACQUIRECONTEXTA)(HCRYPTPROV *phProv,\
LPCSTR pszContainer, LPCSTR pszProvider, DWORD dwProvType,\
DWORD dwFlags );
typedef BOOL (WINAPI *CRYPTGENRANDOM)(HCRYPTPROV hProv, DWORD dwLen,\
BYTE *pbBuffer );
/* This is needed to so pCryptGenRandom() can be called. */
static CRYPTGENRANDOM pCryptGenRandom = NULL;
static HCRYPTPROV hCryptContext = 0;
static int win32_urandom_init(void) {
HINSTANCE hAdvAPI32 = NULL;
/* This is needed to so pCryptAcquireContext() can be called. */
CRYPTACQUIRECONTEXTA pCryptAcquireContext = NULL;
/* Get Module Handle to CryptoAPI */
hAdvAPI32 = GetModuleHandle("advapi32.dll");
if (hAdvAPI32 == NULL) return 0;
/* Check the pointers to the CryptoAPI functions. These shouldn't fail
* but makes sure we won't have problems getting the context or getting
* random. */
if (!GetProcAddress(hAdvAPI32, "CryptAcquireContextA")
|| !GetProcAddress(hAdvAPI32, "CryptGenRandom")) {
return 0;
}
/* Get the pCrypt Context */
if (!pCryptAcquireContext(&hCryptContext, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
return 0;

return 1;
}
MVMint32 MVM_getrandom (MVMThreadContext *tc, void *out, size_t size) {
if (!hCryptContext) {
int rtrn = win32_urandom_init();
if (!rtrn) return 0;
}
if (!pCryptGenRandom(hCryptContext, (DWORD)size, (BYTE*)out)) {
return 0;
}
return 1;
}
#else
#include <unistd.h>
MVMint32 MVM_getrandom (MVMThreadContext *tc, void *out, size_t size) {
int fd = open("/dev/urandom", O_RDONLY);
ssize_t num_read = 0;
if (fd < 0 || (num_read = read(fd, out, size) <= 0)) {
if (fd) close(fd);
#if defined(BSD)
#include <stdlib.h>
arc4random_buf(out, size);
return 1;
#else
return 0;
#endif
}
return 1;
}
#endif
1 change: 1 addition & 0 deletions src/platform/random.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MVMint32 MVM_getrandom (MVMThreadContext *tc, void *out, size_t size);
2 changes: 1 addition & 1 deletion src/strings/ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -2898,7 +2898,7 @@ void MVM_string_compute_hash_code(MVMThreadContext *tc, MVMString *s) {
MVMuint32 graphs_remaining = MVM_string_graphs(tc, s);

/* Initialize hash state. */
MVMuint32 hashv = 0xfeedbeef;
MVMuint32 hashv = tc->instance->hashSecret;
MVMuint32 _hj_i, _hj_j;
_hj_i = _hj_j = 0x9e3779b9;

Expand Down

0 comments on commit b2eb9e1

Please sign in to comment.