diff --git a/configure.ac b/configure.ac index 604a6ff5..7c4b3b1e 100644 --- a/configure.ac +++ b/configure.ac @@ -21,7 +21,7 @@ fi AC_LANG(C) -AC_CHECK_HEADERS([execinfo.h sys/prctl.h sys/time.h sys/wait.h windows.h]) +AC_CHECK_HEADERS([execinfo.h sys/mman.h sys/prctl.h sys/time.h sys/wait.h windows.h]) AC_CHECK_FUNCS([fork kill sigprocmask sigaltstack backtrace]) have_pari=no diff --git a/src/cysignals/implementation_cygwin.c b/src/cysignals/implementation_cygwin.c index 9597a884..3b181143 100644 --- a/src/cysignals/implementation_cygwin.c +++ b/src/cysignals/implementation_cygwin.c @@ -5,6 +5,20 @@ LONG WINAPI win32_altstack_handler(EXCEPTION_POINTERS *exc) { int sig = 0; + /* If we're not handling a signal there is no reason to execute the + * following code; otherwise it can be run in inappropriate contexts + * such as when a STATUS_ACCESS_VIOLATION is raised when accessing + * uncommitted memory in an mmap created with MAP_NORESERVE. See + * discussion at https://trac.sagemath.org/ticket/27214#comment:11 + * + * Unfortunately, when handling an exception that occurred while + * handling another signal, there is currently no way (through Cygwin) + * to distinguish this case from a legitimate segfault. + */ + if (!cysigs.inside_signal_handler) { + return ExceptionContinueExecution; + } + /* Logic cribbed from Cygwin for mapping common Windows exception * codes to the relevant signal numbers: * https://cygwin.com/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=winsup/cygwin/exceptions.cc;h=77eff05707f95f7277974fadbccf0e74223d8d1c;hb=HEAD#l650 diff --git a/src/cysignals/tests.pyx b/src/cysignals/tests.pyx index dbe14d58..e3bf993c 100644 --- a/src/cysignals/tests.pyx +++ b/src/cysignals/tests.pyx @@ -42,6 +42,7 @@ from __future__ import absolute_import from libc.signal cimport (SIGHUP, SIGINT, SIGABRT, SIGILL, SIGSEGV, SIGFPE, SIGBUS, SIGQUIT) from libc.stdlib cimport abort +from libc.errno cimport errno from posix.signal cimport sigaltstack, stack_t, SS_ONSTACK from cpython cimport PyErr_SetString @@ -54,6 +55,8 @@ cdef extern from "tests_helper.c" nogil: void ms_sleep(long ms) void signal_after_delay(int signum, long ms) void signals_after_delay(int signum, long ms, long interval, int n) + void* map_noreserve() + int unmap_noreserve(void* addr) cdef extern from *: ctypedef int volatile_int "volatile int" @@ -616,6 +619,28 @@ def unguarded_dereference_null_pointer(): with nogil: dereference_null_pointer() +def test_access_mmap_noreserve(): + """ + TESTS: + + Regression test for https://github.com/sagemath/cysignals/pull/108; if + the issue is fixed then ``test_access_mmap_noreserve()`` should have no + output. Otherwise the subprocess will exit and report an error occurred + during signal handling:: + + >>> from cysignals.tests import test_access_mmap_noreserve + >>> subpython_err('from cysignals.tests import *; test_access_mmap_noreserve()') + + """ + + cdef int* ptr = map_noreserve() + if ptr == NULL: + raise RuntimeError(f"map_noreserve() failed; errno: {errno}") + + ptr[0] += 1 # Should just work + + if unmap_noreserve(ptr) != 0: + raise RuntimeError(f"uname_noreserve() failed; errno: {errno}") def test_abort(): """ diff --git a/src/cysignals/tests_helper.c b/src/cysignals/tests_helper.c index 156479a5..68801108 100644 --- a/src/cysignals/tests_helper.c +++ b/src/cysignals/tests_helper.c @@ -28,6 +28,9 @@ #if HAVE_UNISTD_H #include #endif +#if HAVE_SYS_MMAN_H +#include +#endif #if HAVE_SYS_TYPES_H #include #endif @@ -65,6 +68,39 @@ static void ms_sleep(long ms) } +/* Calls mmap if available with the MAP_NORESERVE flag; if neither is + * available just mmap without MAP_NORESERVE or malloc--this is used currently + * just to test a regression on Cygwin (see test_read_mmap_noreserve), + * so if the required functionality is not available then the test should pass + * trivially. + */ +#define MAP_NORESERVE_LEN 4096 +#if HAVE_SYS_MMAN_H +#ifndef MAP_NORESERVE +#define MAP_NORESERVE 0 +#endif +static void* map_noreserve(void) +{ + return mmap(NULL, MAP_NORESERVE_LEN, PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE|MAP_NORESERVE, -1, 0); +} + +static int unmap_noreserve(void* addr) { + return munmap(addr, MAP_NORESERVE_LEN); +} +#else +static void* map_noreserve(void) +{ + return malloc(MAP_NORESERVE_LEN); +} + +static int unmap_noreserve(void* addr) { + free(addr); + return 0; +} +#endif + + /* Signal the running process with signal ``signum`` after ``ms`` * milliseconds. Wait ``interval`` milliseconds, then signal again. * Repeat this until ``n`` signals have been sent.