https://issues.dlang.org/show_bug.cgi?id=17566
Issue ID: 17566 Summary: can use void initialization in @safe code to break out of stack Product: D Version: D2 Hardware: x86_64 OS: Linux Status: NEW Keywords: safe Severity: normal Priority: P1 Component: dmd Assignee: nob...@puremagic.com Reporter: ag0ae...@gmail.com This is basically issue 17561 but without `Fiber`s. A fix for this is likely to also fix issue 17561, so 17561 could be considered a duplicate of this. I'm leaving it open for now, because it might be solvable by working around the more general issue somehow. Related links: * https://www.qualys.com/2017/06/19/stack-clash/stack-clash.txt * https://github.com/dlang/druntime/pull/1698 Memory corrupting code: ---- import core.sys.posix.sys.mman; import std.conv: text; enum pageSize = 1024 * 4; // 4 KiB enum stackSize = 1024 * 1024 * 3; // 3 MiB void main() { /* Allocate memory near the stack. */ ubyte foo; auto stackTop = &foo + pageSize - cast(size_t) &foo % pageSize; auto stackBottom = stackTop - stackSize; auto sz = pageSize; void* dst = stackBottom - sz; void* p = mmap(dst, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); assert(p == dst, "failed to allocate page"); /* Set it up with zeroes. */ auto mem = cast(ubyte[]) p[0 .. sz]; mem[] = 0; foreach (x; mem) assert(x == 0, text(x)); /* passes */ /* Break out of the stack and wreak havoc. */ wreak_havoc(); /* Look at the mess. */ foreach (x; mem) assert(x == 0, text(x)); /* fails; prints "13" */ } void wreak_havoc() @safe { ubyte[stackSize] x = void; x[0] = 13; } ---- Like in issue 17561, the surrounding code is not @safe, but is actually safe (as far as I can tell). It's the void initialized static array that breaks safety. In a 32-bit program it's also possible to get there with `malloc` instead of a targeted `mmap`: ---- /* WARNING: This fails quickly for me in a 32-bit Ubuntu VM, but it can potentially consume all memory. */ import core.stdc.stdlib: malloc; import std.conv: text; enum pageSize = 1024 * 4; // 4 KiB enum stackSize = 1024 * 1024 * 3; // 3 MiB void main() { ubyte foo; auto stackTop = &foo + pageSize - cast(size_t) &foo % pageSize; auto stackBottom = stackTop - stackSize; assert(cast(size_t) stackBottom % pageSize == 0); while (true) { /* Allocate memory. */ auto sz = 1024 * 1024; // 1 MiB auto p = malloc(sz); assert(p !is null, "malloc failed"); assert(stackBottom > p); /* See if it's near the stack. */ size_t distance = stackBottom - p; if (distance <= sz) { /* Set it up with zeroes. */ auto mem = cast(ubyte[]) p[0 .. sz]; mem[] = 0; foreach (x; mem) assert(x == 0, text(x)); /* passes */ /* Break out of the stack and wreak havoc. */ wreak_havoc(); /* Look at the mess. */ foreach (x; mem) assert(x == 0, text(x)); /* fails; prints "13" */ break; } } } void wreak_havoc() @safe { ubyte[stackSize] x = void; x[0] = 13; } ---- --