From 6ac9c090c388646f82410f08d8a018809317ee32 Mon Sep 17 00:00:00 2001 From: Nemanja Boric Date: Thu, 24 Nov 2016 17:25:39 +0100 Subject: [PATCH 1/6] Add Fiber's guard page in Posix as well Windows Fiber implementation already uses the fiber stack guard page. This brings the similar protection for mmaped fiber stacks, using mprotect. --- changelog/fiber.dd | 9 +++++++++ src/core/thread.d | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 changelog/fiber.dd 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/src/core/thread.d b/src/core/thread.d index 0c9904ce15..810fcf18e2 100644 --- a/src/core/thread.d +++ b/src/core/thread.d @@ -4429,6 +4429,9 @@ private: static if( __traits( compiles, mmap ) ) { + // Allocate PAGESIZE more for the memory guard + sz += PAGESIZE; + m_pmem = mmap( null, sz, PROT_READ | PROT_WRITE, @@ -4458,13 +4461,31 @@ 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 - PAGESIZE; } m_size = sz; + + static if( __traits( compiles, mmap ) ) + { + // Mark end of stack + for ( ubyte* g = cast(ubyte*)guard; g < guard + PAGESIZE; g+= 32) + g[0 .. 32] = cast(ubyte[]) "END OF FIBER -- END OF FIBER -- "; + + // protect end of stack + if ( mprotect(guard, PAGESIZE, 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 ); From 3e964f0146edd125329b333f556c393f216bafa4 Mon Sep 17 00:00:00 2001 From: Nemanja Boric Date: Thu, 27 Apr 2017 14:53:15 +0200 Subject: [PATCH 2/6] Make Fiber's stack protection-page's size configurable This allows configuring the size or turning off completely the fiber's stack's guard page. It can be configured using new Fiber's constructor's parameter `guard_page_size`. --- changelog/fiber-configure.dd | 6 ++++ src/core/thread.d | 58 ++++++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 23 deletions(-) create mode 100644 changelog/fiber-configure.dd 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/src/core/thread.d b/src/core/thread.d index 810fcf18e2..307c8ab1c6 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. + * guard_page_size = 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 guard_page_size = PAGESIZE ) nothrow in { assert( fn ); } body { - allocStack( sz ); + allocStack( sz, guard_page_size ); reset( fn ); } @@ -3999,18 +4002,21 @@ class Fiber * Params: * dg = The fiber function. * sz = The stack size for this fiber. + * guard_page_size = 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 guard_page_size = PAGESIZE ) nothrow in { assert( dg ); } body { - allocStack( sz ); + allocStack( sz, guard_page_size); 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 guard_page_size ) nothrow in { assert( !m_pmem && !m_ctxt ); @@ -4380,7 +4386,7 @@ private: { // reserve memory for stack m_pmem = VirtualAlloc( null, - sz + PAGESIZE, + sz + guard_page_size, MEM_RESERVE, PAGE_NOACCESS ); if( !m_pmem ) @@ -4388,7 +4394,7 @@ private: version( StackGrowsDown ) { - void* stack = m_pmem + PAGESIZE; + void* stack = m_pmem + guard_page_size; 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 (guard_page_size) + { + // allocate reserved guard page + guard = VirtualAlloc( guard, + guard_page_size, + MEM_COMMIT, + PAGE_READWRITE | PAGE_GUARD ); + if( !guard ) + onOutOfMemoryError(); + } m_ctxt.bstack = pbase; m_ctxt.tstack = pbase; @@ -4429,8 +4438,8 @@ private: static if( __traits( compiles, mmap ) ) { - // Allocate PAGESIZE more for the memory guard - sz += PAGESIZE; + // Allocate more for the memory guard + sz += guard_page_size; m_pmem = mmap( null, sz, @@ -4467,19 +4476,22 @@ private: { m_ctxt.bstack = m_pmem; m_ctxt.tstack = m_pmem; - void* guard = m_pmem + sz - PAGESIZE; + void* guard = m_pmem + sz - guard_page_size; } m_size = sz; static if( __traits( compiles, mmap ) ) { - // Mark end of stack - for ( ubyte* g = cast(ubyte*)guard; g < guard + PAGESIZE; g+= 32) - g[0 .. 32] = cast(ubyte[]) "END OF FIBER -- END OF FIBER -- "; + if (guard_page_size) + { + // Mark end of stack + for ( ubyte* g = cast(ubyte*)guard; g < guard + guard_page_size; g+= 32) + g[0 .. 32] = cast(ubyte[]) "END OF FIBER -- END OF FIBER -- "; - // protect end of stack - if ( mprotect(guard, PAGESIZE, PROT_NONE) == -1 ) - abort(); + // protect end of stack + if ( mprotect(guard, guard_page_size, PROT_NONE) == -1 ) + abort(); + } } else { From bd719ea3bd787f9b06a11a54e53a709a41b20568 Mon Sep 17 00:00:00 2001 From: Martin Nowak Date: Fri, 5 May 2017 13:10:51 +0200 Subject: [PATCH 3/6] change guard_page_size to camelCase --- src/core/thread.d | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/core/thread.d b/src/core/thread.d index 307c8ab1c6..197727e499 100644 --- a/src/core/thread.d +++ b/src/core/thread.d @@ -3976,21 +3976,21 @@ class Fiber * Params: * fn = The fiber function. * sz = The stack size for this fiber. - * guard_page_size = size of the guard page to trap fiber's stack + * 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, - size_t guard_page_size = PAGESIZE ) nothrow + size_t guardPageSize = PAGESIZE ) nothrow in { assert( fn ); } body { - allocStack( sz, guard_page_size ); + allocStack( sz, guardPageSize ); reset( fn ); } @@ -4002,21 +4002,21 @@ class Fiber * Params: * dg = The fiber function. * sz = The stack size for this fiber. - * guard_page_size = size of the guard page to trap fiber's stack + * 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, - size_t guard_page_size = PAGESIZE ) nothrow + size_t guardPageSize = PAGESIZE ) nothrow in { assert( dg ); } body { - allocStack( sz, guard_page_size); + allocStack( sz, guardPageSize); reset( dg ); } @@ -4361,7 +4361,7 @@ private: // // Allocate a new stack for this fiber. // - final void allocStack( size_t sz, size_t guard_page_size ) nothrow + final void allocStack( size_t sz, size_t guardPageSize ) nothrow in { assert( !m_pmem && !m_ctxt ); @@ -4386,7 +4386,7 @@ private: { // reserve memory for stack m_pmem = VirtualAlloc( null, - sz + guard_page_size, + sz + guardPageSize, MEM_RESERVE, PAGE_NOACCESS ); if( !m_pmem ) @@ -4394,7 +4394,7 @@ private: version( StackGrowsDown ) { - void* stack = m_pmem + guard_page_size; + void* stack = m_pmem + guardPageSize; void* guard = m_pmem; void* pbase = stack + sz; } @@ -4413,11 +4413,11 @@ private: if( !stack ) onOutOfMemoryError(); - if (guard_page_size) + if (guardPageSize) { // allocate reserved guard page guard = VirtualAlloc( guard, - guard_page_size, + guardPageSize, MEM_COMMIT, PAGE_READWRITE | PAGE_GUARD ); if( !guard ) @@ -4439,7 +4439,7 @@ private: static if( __traits( compiles, mmap ) ) { // Allocate more for the memory guard - sz += guard_page_size; + sz += guardPageSize; m_pmem = mmap( null, sz, @@ -4476,20 +4476,20 @@ private: { m_ctxt.bstack = m_pmem; m_ctxt.tstack = m_pmem; - void* guard = m_pmem + sz - guard_page_size; + void* guard = m_pmem + sz - guardPageSize; } m_size = sz; static if( __traits( compiles, mmap ) ) { - if (guard_page_size) + if (guardPageSize) { // Mark end of stack - for ( ubyte* g = cast(ubyte*)guard; g < guard + guard_page_size; g+= 32) + for ( ubyte* g = cast(ubyte*)guard; g < guard + guardPageSize; g+= 32) g[0 .. 32] = cast(ubyte[]) "END OF FIBER -- END OF FIBER -- "; // protect end of stack - if ( mprotect(guard, guard_page_size, PROT_NONE) == -1 ) + if ( mprotect(guard, guardPageSize, PROT_NONE) == -1 ) abort(); } } From 3b7117bfa791a9530524524d03982f9ba3eeb2e0 Mon Sep 17 00:00:00 2001 From: Martin Nowak Date: Fri, 5 May 2017 15:24:27 +0200 Subject: [PATCH 4/6] add test for Fiber guard page --- posix.mak | 2 +- test/thread/Makefile | 18 ++++++++++++++++ test/thread/src/fiber_guard_page.d | 33 ++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 test/thread/Makefile create mode 100644 test/thread/src/fiber_guard_page.d 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/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..de9cc5b1dc --- /dev/null +++ b/test/thread/src/fiber_guard_page.d @@ -0,0 +1,33 @@ +import core.thread; +import core.sys.posix.sys.mman; + +// this should be true for most architectures +version = StackGrowsDown; + +int recurse(int i) +{ + return i == 0 ? 0 : recurse(i - 1) + i; +} + +void main() +{ + import core.stdc.stdio; + enum stackSize = 4096; + enum n = size_t.sizeof == 8 ? 128 : 512; + auto fib1 = new Fiber(function{ recurse(n); }, stackSize); + // allocate a page below (above) the fiber's stack to make stack overflows possible (w/o segfaulting) + version (StackGrowsDown) + { + auto stackBottom1 = fib1.tupleof[8]; // m_pmem + auto p = mmap(stackBottom1 - 8 * stackSize, 8 * stackSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + assert(p !is null, "failed to allocate page"); + } + else + { + auto stackTop1 = fib1.tupleof[8] + fib1.tupleof[7]; // m_pmem + m_sz + auto p = mmap(stackTop1, 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 + fib1.call(); +} From a5c0dc2d0c58ac408859b8c1b99da9b1036dacfc Mon Sep 17 00:00:00 2001 From: Martin Nowak Date: Fri, 5 May 2017 15:33:17 +0200 Subject: [PATCH 5/6] do not write to guard page - no point in creating a dirty write protected page --- src/core/thread.d | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/core/thread.d b/src/core/thread.d index 197727e499..24003b0c79 100644 --- a/src/core/thread.d +++ b/src/core/thread.d @@ -4484,10 +4484,6 @@ private: { if (guardPageSize) { - // Mark end of stack - for ( ubyte* g = cast(ubyte*)guard; g < guard + guardPageSize; g+= 32) - g[0 .. 32] = cast(ubyte[]) "END OF FIBER -- END OF FIBER -- "; - // protect end of stack if ( mprotect(guard, guardPageSize, PROT_NONE) == -1 ) abort(); From 6be1794e8700041e348efcf0b5a1731623c85fbb Mon Sep 17 00:00:00 2001 From: Nemanja Boric Date: Sat, 6 May 2017 12:16:47 +0200 Subject: [PATCH 6/6] Simplify fiber guard page test --- test/thread/src/fiber_guard_page.d | 37 ++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/test/thread/src/fiber_guard_page.d b/test/thread/src/fiber_guard_page.d index de9cc5b1dc..212f4c857e 100644 --- a/test/thread/src/fiber_guard_page.d +++ b/test/thread/src/fiber_guard_page.d @@ -2,32 +2,45 @@ import core.thread; import core.sys.posix.sys.mman; // this should be true for most architectures +// (taken from core.thread) version = StackGrowsDown; -int recurse(int i) +enum stackSize = 4096; + +// Simple method that causes a stack overflow +void stackMethod() { - return i == 0 ? 0 : recurse(i - 1) + i; + // Over the stack size, so it overflows the stack + int[stackSize/int.sizeof+100] x; } void main() { - import core.stdc.stdio; - enum stackSize = 4096; - enum n = size_t.sizeof == 8 ? 128 : 512; - auto fib1 = new Fiber(function{ recurse(n); }, stackSize); + 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) { - auto stackBottom1 = fib1.tupleof[8]; // m_pmem - auto p = mmap(stackBottom1 - 8 * stackSize, 8 * stackSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + 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 stackTop1 = fib1.tupleof[8] + fib1.tupleof[7]; // m_pmem + m_sz - auto p = mmap(stackTop1, 8 * stackSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + 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 - fib1.call(); + + // 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(); }