Inspired by <https://www.qualys.com/2017/06/19/stack-clash/stack-clash.txt> and <https://github.com/dlang/druntime/pull/1698>, here is some code that skips over a Fiber's stack guard page to corrupt another Fiber's stack:
----
import core.thread: Fiber;
import std.conv: text;
enum pagesize = 4096; /* educated guess */
enum distance = 5 * pagesize; /* 4 pages of stack + 1 guard page */
void main()
{
auto f1 = new Fiber(() @safe {
ubyte[distance + 250] mess_up_f2 = void;
/* skipping over the guard page */
mess_up_f2[0] = 13;
});
auto f2 = new Fiber(() {
ubyte[500] data = 0;
foreach (d; data) assert(d == 0); /* passes */
Fiber.yield();
foreach (d; data) assert(d == 0, text(d)); /* fails; prints "13" */
});
assert(f1.tupleof[8] > f2.tupleof[8]);
immutable actualDistance = f1.tupleof[8] - f2.tupleof[8];
assert(distance == actualDistance, text(actualDistance));
/* If this fails, change `distance` to printed value. */
f2.call(); /* f2 sets up its data */
f1.call(); /* f1 messes with it */
f2.call(); /* f2 sees the corrupted data */
}
----
Tested in Ubuntu Linux with a git HEAD dmd. Might behave differently on other platforms.
As far as I see, this is an @safe issue, which can't be detected/prevented in the Fiber code. Fiber's operations are currently not @safe, but surely there is an expectation that a running an @safe function in a Fiber is safe.
I guess one possible fix would be to outlaw void initialization in @safe code. Maybe it can be allowed below a certain size, when it's also ensured that guard pages have at least that size.
Comment #1 by ag0aep6g — 2017-06-27T09:07:27Z
(In reply to ag0aep6g from comment #0)
> Tested [...] with a git HEAD dmd.
Which is: DMD64 D Compiler v2.075.0-b1-37-gec11b11
Comment #2 by ag0aep6g — 2017-06-28T13:09:36Z
Same issue applies to the main stack. Filed separately: issue 17566.
Comment #3 by robert.schadek — 2024-12-13T18:52:52Z