diff --git a/changelog/fiber-configure.dd b/changelog/fiber-configure.dd new file mode 100644 index 0000000000..232673566b --- /dev/null +++ b/changelog/fiber-configure.dd @@ -0,0 +1,6 @@ +Make fiber stack protection-page size configurable + +It is now possible to change the guard page size by using +the new Fiber's constructor argument - guard_page_size. It defaults to +`PAGE_SIZE` (the same it used to be on Windows), and specifying 0 will +turn this feature off. diff --git a/changelog/fiber.dd b/changelog/fiber.dd new file mode 100644 index 0000000000..926dfa1f41 --- /dev/null +++ b/changelog/fiber.dd @@ -0,0 +1,9 @@ +Add Fiber's stack-protection page for Posix. + +The feature already existing for Windows' fiber implementation is now added to +the systems using mmap to allocate fibers' stacks: After (or before) the last +page used for the Fiber's stack, the page is allocate which is protected for +any kind of access. This will cause system to trap immediately on the fiber's +stack overflow. If in debugger session, one can inspect contents of the memory +before or after stack pointer and it can be seen if it contains END OF FIBER +string pattern. diff --git a/posix.mak b/posix.mak index 22d76dfc87..d15aa97511 100644 --- a/posix.mak +++ b/posix.mak @@ -197,7 +197,7 @@ $(DRUNTIME): $(OBJS) $(SRCS) UT_MODULES:=$(patsubst src/%.d,$(ROOT)/unittest/%,$(SRCS)) HAS_ADDITIONAL_TESTS:=$(shell test -d test && echo 1) ifeq ($(HAS_ADDITIONAL_TESTS),1) - ADDITIONAL_TESTS:=test/init_fini test/exceptions test/coverage test/profile test/cycles test/allocations test/typeinfo + ADDITIONAL_TESTS:=test/init_fini test/exceptions test/coverage test/profile test/cycles test/allocations test/typeinfo test/thread ADDITIONAL_TESTS+=$(if $(SHARED),test/shared,) endif diff --git a/src/core/thread.d b/src/core/thread.d index 0c9904ce15..24003b0c79 100644 --- a/src/core/thread.d +++ b/src/core/thread.d @@ -3976,18 +3976,21 @@ class Fiber * Params: * fn = The fiber function. * sz = The stack size for this fiber. + * guardPageSize = size of the guard page to trap fiber's stack + * overflows * * In: * fn must not be null. */ - this( void function() fn, size_t sz = PAGESIZE*4 ) nothrow + this( void function() fn, size_t sz = PAGESIZE*4, + size_t guardPageSize = PAGESIZE ) nothrow in { assert( fn ); } body { - allocStack( sz ); + allocStack( sz, guardPageSize ); reset( fn ); } @@ -3999,18 +4002,21 @@ class Fiber * Params: * dg = The fiber function. * sz = The stack size for this fiber. + * guardPageSize = size of the guard page to trap fiber's stack + * overflows * * In: * dg must not be null. */ - this( void delegate() dg, size_t sz = PAGESIZE*4 ) nothrow + this( void delegate() dg, size_t sz = PAGESIZE*4, + size_t guardPageSize = PAGESIZE ) nothrow in { assert( dg ); } body { - allocStack( sz ); + allocStack( sz, guardPageSize); reset( dg ); } @@ -4355,7 +4361,7 @@ private: // // Allocate a new stack for this fiber. // - final void allocStack( size_t sz ) nothrow + final void allocStack( size_t sz, size_t guardPageSize ) nothrow in { assert( !m_pmem && !m_ctxt ); @@ -4380,7 +4386,7 @@ private: { // reserve memory for stack m_pmem = VirtualAlloc( null, - sz + PAGESIZE, + sz + guardPageSize, MEM_RESERVE, PAGE_NOACCESS ); if( !m_pmem ) @@ -4388,7 +4394,7 @@ private: version( StackGrowsDown ) { - void* stack = m_pmem + PAGESIZE; + void* stack = m_pmem + guardPageSize; void* guard = m_pmem; void* pbase = stack + sz; } @@ -4407,13 +4413,16 @@ private: if( !stack ) onOutOfMemoryError(); - // allocate reserved guard page - guard = VirtualAlloc( guard, - PAGESIZE, - MEM_COMMIT, - PAGE_READWRITE | PAGE_GUARD ); - if( !guard ) - onOutOfMemoryError(); + if (guardPageSize) + { + // allocate reserved guard page + guard = VirtualAlloc( guard, + guardPageSize, + MEM_COMMIT, + PAGE_READWRITE | PAGE_GUARD ); + if( !guard ) + onOutOfMemoryError(); + } m_ctxt.bstack = pbase; m_ctxt.tstack = pbase; @@ -4429,6 +4438,9 @@ private: static if( __traits( compiles, mmap ) ) { + // Allocate more for the memory guard + sz += guardPageSize; + m_pmem = mmap( null, sz, PROT_READ | PROT_WRITE, @@ -4458,13 +4470,30 @@ private: { m_ctxt.bstack = m_pmem + sz; m_ctxt.tstack = m_pmem + sz; + void* guard = m_pmem; } else { m_ctxt.bstack = m_pmem; m_ctxt.tstack = m_pmem; + void* guard = m_pmem + sz - guardPageSize; } m_size = sz; + + static if( __traits( compiles, mmap ) ) + { + if (guardPageSize) + { + // protect end of stack + if ( mprotect(guard, guardPageSize, PROT_NONE) == -1 ) + abort(); + } + } + else + { + // Supported only for mmap allocated memory - results are + // undefined if applied to memory not obtained by mmap + } } Thread.add( m_ctxt ); diff --git a/test/thread/Makefile b/test/thread/Makefile new file mode 100644 index 0000000000..ca27d1fb86 --- /dev/null +++ b/test/thread/Makefile @@ -0,0 +1,18 @@ +include ../common.mak + +TESTS:=fiber_guard_page + +.PHONY: all clean +all: $(addprefix $(ROOT)/,$(addsuffix .done,$(TESTS))) + +# segfault || bus error (OSX) +$(ROOT)/fiber_guard_page.done: $(ROOT)/%.done : $(ROOT)/% + @echo Testing $* + $(QUIET)$(TIMELIMIT)$(ROOT)/$* $(RUN_ARGS); rc=$$?; [ $$rc -eq 139 ] || [ $$rc -eq 138 ] + @touch $@ + +$(ROOT)/%: $(SRC)/%.d + $(QUIET)$(DMD) $(DFLAGS) -of$@ $< + +clean: + rm -rf $(GENERATED) diff --git a/test/thread/src/fiber_guard_page.d b/test/thread/src/fiber_guard_page.d new file mode 100644 index 0000000000..212f4c857e --- /dev/null +++ b/test/thread/src/fiber_guard_page.d @@ -0,0 +1,46 @@ +import core.thread; +import core.sys.posix.sys.mman; + +// this should be true for most architectures +// (taken from core.thread) +version = StackGrowsDown; + +enum stackSize = 4096; + +// Simple method that causes a stack overflow +void stackMethod() +{ + // Over the stack size, so it overflows the stack + int[stackSize/int.sizeof+100] x; +} + +void main() +{ + auto test_fiber = new Fiber(&stackMethod, stackSize); + + // allocate a page below (above) the fiber's stack to make stack overflows possible (w/o segfaulting) + version (StackGrowsDown) + { + static assert(__traits(identifier, test_fiber.tupleof[8]) == "m_pmem"); + auto stackBottom = test_fiber.tupleof[8]; + auto p = mmap(stackBottom - 8 * stackSize, 8 * stackSize, + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + assert(p !is null, "failed to allocate page"); + } + else + { + auto m_sz = test_fiber.tupleof[7]; + auto m_pmem = test_fiber.tupleof[8]; + static assert(__traits(identifier, test_fiber.tupleof[7]) == "m_size"); + static assert(__traits(identifier, test_fiber.tupleof[8]) == "m_pmem"); + + auto stackTop = m_pmem + m_sz; + auto p = mmap(stackTop, 8 * stackSize, + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + assert(p !is null, "failed to allocate page"); + } + + // the guard page should prevent a mem corruption by stack + // overflow and cause a segfault instead (or generate SIGBUS on *BSD flavors) + test_fiber.call(); +}