From f852788934e025920370069e1d7b1ae7347dec0a Mon Sep 17 00:00:00 2001 From: Mathias Krause Date: Tue, 8 Nov 2022 11:51:01 +0100 Subject: [PATCH 1/3] sljit/protexec: fix out-of-bounds stack write In create_tempfile() we look for a suitable place to put the temporary file into and, among others, look at $TMPDIR. If the value of this environment variable exceeds the bounds of the local tmp_name[] buffer, we ignore it. However, we still change the value of 'tmp_name_len' which leads to follow-up errors. On debug builds this can lead to hitting the assertion as can be seen below: $ TMPDIR=$(perl -e 'print "A"x1024') ./bin/array_access Assertion failed at sljit_src/sljitProtExecAllocator.c:147 Aborted For non-debug builds, however, this can lead to a memory corruption, by abusing the fact that we change a trailing '/' to '\0' later on. With a sufficiently high enough value for 'tmp_name_len' this can corrupt stack frames up in the call chain. Fix this by setting 'tmp_name_len' only if value it is based on is found to be valid -- just like it was prior to commit 98323bd82218. Fixes: 98323bd82218 ("protexec: refactor create_tempfile() (#37)") Signed-off-by: Mathias Krause --- src/sljit/sljitProtExecAllocator.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/sljit/sljitProtExecAllocator.c b/src/sljit/sljitProtExecAllocator.c index 915411fbe..10641bcd7 100644 --- a/src/sljit/sljitProtExecAllocator.c +++ b/src/sljit/sljitProtExecAllocator.c @@ -107,6 +107,7 @@ static SLJIT_INLINE int create_tempfile(void) int fd; char tmp_name[256]; size_t tmp_name_len = 0; + size_t tmp_len; char *dir; struct stat st; #if defined(SLJIT_SINGLE_THREADED) && SLJIT_SINGLE_THREADED @@ -125,18 +126,22 @@ static SLJIT_INLINE int create_tempfile(void) dir = secure_getenv("TMPDIR"); if (dir) { - tmp_name_len = strlen(dir); - if (tmp_name_len > 0 && tmp_name_len < sizeof(tmp_name)) { - if ((stat(dir, &st) == 0) && S_ISDIR(st.st_mode)) + tmp_len = strlen(dir); + if (tmp_len > 0 && tmp_len < sizeof(tmp_name)) { + if ((stat(dir, &st) == 0) && S_ISDIR(st.st_mode)) { strcpy(tmp_name, dir); + tmp_name_len = tmp_len; + } } } #ifdef P_tmpdir if (!tmp_name_len) { - tmp_name_len = strlen(P_tmpdir); - if (tmp_name_len > 0 && tmp_name_len < sizeof(tmp_name)) + tmp_len = strlen(P_tmpdir); + if (tmp_len > 0 && tmp_len < sizeof(tmp_name)) { strcpy(tmp_name, P_tmpdir); + tmp_name_len = tmp_len; + } } #endif if (!tmp_name_len) { From 5e93d28a76920dd2bc607259ad856954e43d24c9 Mon Sep 17 00:00:00 2001 From: Mathias Krause Date: Tue, 8 Nov 2022 13:06:24 +0100 Subject: [PATCH 2/3] sljit/exec: provide function to test runtime availability of rwx maps SELinux or PaX/grsecurity based kernels may deny creating writable and executable mappings, leading to errors when trying to allocate JIT memory, even though JIT support is generally available. Provide a function to probe for the runtime availability of rwx maps to support users like libpcre2 which can use it to announce the lack of JIT and fall back to the interpreter instead. This function is only needed for Linux and only if we're using the default JIT memory allocator, as all others implement workarounds via double mappings. Signed-off-by: Mathias Krause --- src/sljit/sljitConfigInternal.h | 11 +++++++ src/sljit/sljitExecAllocator.c | 55 +++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/src/sljit/sljitConfigInternal.h b/src/sljit/sljitConfigInternal.h index cd3ce6973..0ad655827 100644 --- a/src/sljit/sljitConfigInternal.h +++ b/src/sljit/sljitConfigInternal.h @@ -633,6 +633,17 @@ SLJIT_API_FUNC_ATTRIBUTE sljit_sw sljit_exec_offset(void* ptr); #define SLJIT_EXEC_OFFSET(ptr) sljit_exec_offset(ptr) #else #define SLJIT_EXEC_OFFSET(ptr) 0 + +/* SELinux or grsecurity kernels may deny creating rwx mappings, so we need +to probe at runtime if JIT memory is supported. */ +#if defined __linux__ && \ + (!defined SLJIT_PROT_EXECUTABLE_ALLOCATOR || !SLJIT_PROT_EXECUTABLE_ALLOCATOR) && \ + (!defined SLJIT_WX_EXECUTABLE_ALLOCATOR || !SLJIT_WX_EXECUTABLE_ALLOCATOR) +SLJIT_API_FUNC_ATTRIBUTE int sljit_get_runtime_support(void); +#else +#define sljit_get_runtime_support() 1 +#endif + #endif #endif /* SLJIT_EXECUTABLE_ALLOCATOR */ diff --git a/src/sljit/sljitExecAllocator.c b/src/sljit/sljitExecAllocator.c index 92d940ddc..ed948259d 100644 --- a/src/sljit/sljitExecAllocator.c +++ b/src/sljit/sljitExecAllocator.c @@ -94,6 +94,61 @@ static SLJIT_INLINE void free_chunk(void *chunk, sljit_uw size) #else /* POSIX */ +#ifdef __linux__ +#include + +static SLJIT_INLINE int is_permission_error(int err) +{ + /* PaX uses EPERM, SELinux uses EACCES */ + return err == EPERM || err == EACCES; +} + +SLJIT_API_FUNC_ATTRIBUTE int sljit_get_runtime_support(void) +{ + int status = -1; + size_t size; + void *addr; + FILE *f; + + /* Try to get the status from /proc/self/status, looking for PaX flags. */ + f = fopen("/proc/self/status", "re"); + if (f) { + char *buf = NULL; + size_t len; + + while (getline(&buf, &len, f) != -1) { + if (strncmp(buf, "PaX:", 4)) + continue; + + /* Look for 'm', indicating PaX MPROTECT is disabled. */ + status = !!strchr(buf+4, 'm'); + break; + } + + fclose(f); + free(buf); + + if (status != -1) + return status; + } + + /* + * Try to create a temporary rwx mapping to probe for its support. If + * this fails, test 'errno' to ensure it failed because we were not + * allowed to create such a mapping and not because of some transient + * error. + */ + size = get_page_alignment() + 1; + addr = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0); + if (addr == MAP_FAILED) + return is_permission_error(errno) ? 0 : -1; + + munmap(addr, size); + + return 1; +} +#endif + #if defined(__APPLE__) && defined(MAP_JIT) /* On macOS systems, returns MAP_JIT if it is defined _and_ we're running on a From c2dfa1ee2109a0bb75ccd6a9f6b874e81415458d Mon Sep 17 00:00:00 2001 From: Mathias Krause Date: Tue, 8 Nov 2022 13:23:20 +0100 Subject: [PATCH 3/3] Probe JIT availability at runtime SELinux or PaX/grsecurity based kernels may deny creating writable and executable mappings, leading to errors when trying to allocate JIT memory, even though JIT support is generally available. Instead of failing hard in this case, allow to use the interpreter fallback by probing and announcing the availability of JIT mode at runtime through the PCRE2_CONFIG_JIT hook. This only happens for configurations using only the default JIT memory allocator, i.e. not the SELinux aware one. However, we still mark the latter as experimental and distributions like Debian don't enable it. The current behaviour leads to nasty user visible errors on such systems, e.g. when running 'git grep': $ git grep peach fatal: Couldn't JIT the PCRE2 pattern 'peach', got '-48' With this change in place, it'll fall back to the interpreter and "just work", providing a much more pleasant user experience. Signed-off-by: Mathias Krause --- src/pcre2_config.c | 2 +- src/pcre2_internal.h | 2 ++ src/pcre2_jit_misc.c | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/pcre2_config.c b/src/pcre2_config.c index 5ef103caf..a7184e538 100644 --- a/src/pcre2_config.c +++ b/src/pcre2_config.c @@ -145,7 +145,7 @@ switch (what) case PCRE2_CONFIG_JIT: #ifdef SUPPORT_JIT - *((uint32_t *)where) = 1; + *((uint32_t *)where) = !!PRIV(jit_supported)(); #else *((uint32_t *)where) = 0; #endif diff --git a/src/pcre2_internal.h b/src/pcre2_internal.h index 92dd3138d..b31c4d022 100644 --- a/src/pcre2_internal.h +++ b/src/pcre2_internal.h @@ -1992,6 +1992,7 @@ is available. */ #define _pcre2_jit_free PCRE2_SUFFIX(_pcre2_jit_free_) #define _pcre2_jit_get_size PCRE2_SUFFIX(_pcre2_jit_get_size_) #define _pcre2_jit_get_target PCRE2_SUFFIX(_pcre2_jit_get_target_) +#define _pcre2_jit_supported PCRE2_SUFFIX(_pcre2_jit_supported_) #define _pcre2_memctl_malloc PCRE2_SUFFIX(_pcre2_memctl_malloc_) #define _pcre2_ord2utf PCRE2_SUFFIX(_pcre2_ord2utf_) #define _pcre2_script_run PCRE2_SUFFIX(_pcre2_script_run_) @@ -2019,6 +2020,7 @@ extern void _pcre2_jit_free_rodata(void *, void *); extern void _pcre2_jit_free(void *, pcre2_memctl *); extern size_t _pcre2_jit_get_size(void *); const char * _pcre2_jit_get_target(void); +extern int _pcre2_jit_supported(void); extern void * _pcre2_memctl_malloc(size_t, pcre2_memctl *); extern unsigned int _pcre2_ord2utf(uint32_t, PCRE2_UCHAR *); extern BOOL _pcre2_script_run(PCRE2_SPTR, PCRE2_SPTR, BOOL); diff --git a/src/pcre2_jit_misc.c b/src/pcre2_jit_misc.c index bb6a5589c..780aa3c47 100644 --- a/src/pcre2_jit_misc.c +++ b/src/pcre2_jit_misc.c @@ -199,6 +199,21 @@ if (jit_stack != NULL) } +/************************************************* +* Test runtime JIT support * +*************************************************/ + +int +PRIV(jit_supported)(void) +{ +#ifndef SUPPORT_JIT +return 0; +#else /* SUPPORT_JIT */ +return sljit_get_runtime_support(); +#endif /* SUPPORT_JIT */ +} + + /************************************************* * Get target CPU type * *************************************************/