Skip to content

Commit

Permalink
Dynamic stack alloc tests
Browse files Browse the repository at this point in the history
  • Loading branch information
SingleAccretion committed May 20, 2023
1 parent 6c093d2 commit 73f4d14
Show file tree
Hide file tree
Showing 2 changed files with 342 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -9319,7 +9319,11 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// copies of susceptible parameters to avoid buffer overrun attacks through locals/params
bool getNeedsGSSecurityCookie() const
{
#ifdef TARGET_WASM
return false; // Cookie checks NYI in the LLVM backend.
#else
return compNeedsGSSecurityCookie;
#endif
}
void setNeedsGSSecurityCookie()
{
Expand Down
338 changes: 338 additions & 0 deletions src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2151,6 +2151,8 @@ private static void TestTryCatch()

TestIntraFrameFilterOrderDeep();

TestDynamicStackAlloc();

TestCatchAndThrow();

TestRethrow();
Expand Down Expand Up @@ -2574,6 +2576,342 @@ static void InnerInnerFilterAndFinally(ref int counter)
EndTest(counter == 7);
}

private static void TestDynamicStackAlloc()
{
const int StkAllocSize = 999;
bool result = false;

[MethodImpl(MethodImplOptions.NoInlining)]
static void DoAlloc(out byte* addr, int size = 0)
{
if (size == 0)
{
size = StkAllocSize;
}
byte* stk = stackalloc byte[size];
addr = stk;

try
{
Volatile.Write(ref stk[size - 1], 1);
if (Volatile.Read(ref stk[size - 1]) == 2)
{
Volatile.Write(ref *(int*)null, 0);
}
}
catch (NullReferenceException)
{
Volatile.Read(ref stk[size - 1]);
}
}

StartTest("TestDynamicStackAlloc(release on return)");
{
DoAlloc(out byte* addrOne);
DoAlloc(out byte* addrTwo);
result = addrOne == addrTwo;
}
EndTest(result);

[MethodImpl(MethodImplOptions.NoInlining)]
static void DoDoubleAlloc(bool* pReturnWithEH)
{
byte* stkOne = stackalloc byte[StkAllocSize];
byte* stkTwo = stackalloc byte[StkAllocSize];

try
{
Volatile.Write(ref stkOne[StkAllocSize - 1], 1);
Volatile.Write(ref stkTwo[StkAllocSize - 1], 1);
if (Volatile.Read(ref *pReturnWithEH))
{
Volatile.Write(ref *(int*)null, 0);
}
}
catch when (!Volatile.Read(ref *pReturnWithEH))
{
Volatile.Read(ref stkOne[StkAllocSize - 1]);
Volatile.Read(ref stkTwo[StkAllocSize - 1]);
}
}

StartTest("TestDynamicStackAlloc(double release on return)");
{
bool doReturnWithEH = false;
DoAlloc(out byte* addrOne);
DoDoubleAlloc(&doReturnWithEH);
DoAlloc(out byte* addrTwo);
result = addrOne == addrTwo;
}
EndTest(result);

[MethodImpl(MethodImplOptions.NoInlining)]
static void DoAllocAndThrow(out byte* addr)
{
byte* stk = stackalloc byte[StkAllocSize];
addr = stk;

try
{
Volatile.Write(ref stk[StkAllocSize - 1], 1);
Volatile.Write(ref *(int*)null, 0);
}
catch (DivideByZeroException)
{
Volatile.Read(ref stk[StkAllocSize - 1]);
}
}

StartTest("TestDynamicStackAlloc(release on EH return)");
{
byte stkByte;
byte* addrOne = null;
byte* addrTwo = &stkByte;
try
{
DoAllocAndThrow(out addrOne);
}
catch (NullReferenceException)
{
}
try
{
DoAllocAndThrow(out addrTwo);
}
catch (NullReferenceException)
{
}

result = addrOne == addrTwo;
}
EndTest(result);

StartTest("TestDynamicStackAlloc(double release on EH return)");
{
DoAlloc(out byte* addrOne);
try
{
bool doReturnWithEH = true;
DoDoubleAlloc(&doReturnWithEH);
}
catch (NullReferenceException)
{
}
DoAlloc(out byte* addrTwo);

result = addrOne == addrTwo;
}
EndTest(result);

StartTest("TestDynamicStackAlloc(release on EH return does not corrupt live state)");
{
byte* stkOne = stackalloc byte[StkAllocSize];
Volatile.Write(ref stkOne[StkAllocSize - 1], 2);

byte* stkTwo = null;
byte* stkThree = null;
try
{
DoAllocAndThrow(out stkTwo);
}
catch (NullReferenceException)
{
Volatile.Read(ref stkOne[StkAllocSize - 1]);
}

try
{
DoAlloc(out stkThree);
Volatile.Write(ref stkThree[StkAllocSize - 1], 10);

result = stkTwo == stkThree && stkOne != stkThree && Volatile.Read(ref stkOne[StkAllocSize - 1]) == 2;
Volatile.Write(ref *(int*)null, 0);
}
catch (NullReferenceException)
{
Volatile.Read(ref stkThree[StkAllocSize - 1]);
}
}
EndTest(result);

StartTest("TestDynamicStackAlloc(release from an empty shadow frame does not release the parent's frame)");
{
[MethodImpl(MethodImplOptions.NoInlining)]
void OuterMethodWithEmptyShadowStack(bool* pResult)
{
[MethodImpl(MethodImplOptions.NoInlining)]
static void SideEffect(byte* pByte)
{
if (Volatile.Read(ref *pByte) != 0)
{
throw new Exception();
}
}

[MethodImpl(MethodImplOptions.NoInlining)]
static void InnerMethodWithEmptyShadowStack()
{
try
{
byte* stk = stackalloc byte[StkAllocSize];
SideEffect(stk);
Console.WriteLine((int)stk);
}
catch (Exception)
{
}
}

try
{
byte* stkOne = stackalloc byte[StkAllocSize];
Volatile.Write(ref stkOne[StkAllocSize - 1], 1);
Console.WriteLine((int)stkOne);

InnerMethodWithEmptyShadowStack();

byte* stkTwo = stackalloc byte[StkAllocSize];
Volatile.Write(ref stkTwo[StkAllocSize - 1], 2);
Console.WriteLine((int)stkTwo);

*pResult = stkOne != stkTwo;
}
catch (Exception)
{
}
}

OuterMethodWithEmptyShadowStack(&result);
}
EndTest(result);

StartTest("TestDynamicStackAlloc(EH-live state)");
{
static void InnerFinallyHandler(out bool result)
{
byte* stk = stackalloc byte[StkAllocSize];

Volatile.Write(ref stk[0], 1);
Volatile.Write(ref stk[StkAllocSize / 2], 2);
Volatile.Write(ref stk[StkAllocSize - 1], 3);

result = false;
try
{
throw new Exception();
}
finally // A second-pass handler.
{
result = stk[0] == 1 && stk[StkAllocSize / 2] == 2 && stk[StkAllocSize - 1] == 3;
}
}

static bool ClearNativeStack(byte* pFill)
{
byte* stk = stackalloc byte[StkAllocSize];

Unsafe.InitBlock(stk, Volatile.Read(ref *pFill), StkAllocSize);

return Volatile.Read(ref stk[0]) == Volatile.Read(ref *pFill) &&
Volatile.Read(ref stk[StkAllocSize / 2]) == Volatile.Read(ref *pFill) &&
Volatile.Read(ref stk[StkAllocSize - 1]) == Volatile.Read(ref *pFill);
}

byte fill = 0x17;
try
{
InnerFinallyHandler(out result);
}
catch when (ClearNativeStack(&fill))
{
}

}
EndTest(result);

StartTest("TestDynamicStackAlloc(alignment)");
{
DoAlloc(out byte* addr, 1);
result = ((nuint)addr % 8) == 0;

DoAlloc(out addr, 3);
result &= ((nuint)addr % 8) == 0;

DoAlloc(out addr, 17);
result &= ((nuint)addr % 8) == 0;
}
EndTest(result);

StartTest("TestDynamicStackAlloc(allocation patterns)");
{
static bool TestAllocs(ref byte* lastAddr, params int[] allocs)
{
bool TestAlloc(int index, out byte* stkOut)
{
int allocSize = allocs[index];
byte* stk = stackalloc byte[allocSize];
stkOut = stk;

Volatile.Write(ref stk[allocSize - 1], 1);
try
{
if (Volatile.Read(ref stk[allocSize - 1]) == 2)
{
throw new Exception();
}
}
catch (Exception)
{
Volatile.Read(ref stk[allocSize - 1]);
}

int nextIndex = index + 1;
if (nextIndex < allocs.Length)
{
if (!TestAlloc(nextIndex, out byte* stkOne))
{
return false;
}

DoAlloc(out byte* stkTwo, allocs[nextIndex]);
return stkOne == stkTwo;
}

return true;
}

if (!TestAlloc(0, out _))
{
return false;
}

DoAlloc(out byte* addr, 1);
if (lastAddr != null && addr != lastAddr)
{
return false;
}

lastAddr = addr;
return true;
}

const int PageSize = 64 * 1024;
const int LargeBlock = PageSize / 4;
const int AverageBlock = LargeBlock / 4;
const int SmallBlock = AverageBlock / 4;
const int AlmostPageSize = PageSize - SmallBlock;

int pageHeaderSize = 3 * sizeof(nint);
byte* lastAddr = null;
result = TestAllocs(ref lastAddr, SmallBlock / 2, AlmostPageSize, SmallBlock, PageSize);
result &= TestAllocs(ref lastAddr, SmallBlock, SmallBlock);
result &= TestAllocs(ref lastAddr, LargeBlock, LargeBlock, LargeBlock, LargeBlock - pageHeaderSize, SmallBlock);
result &= TestAllocs(ref lastAddr, PageSize, 2 * PageSize, 4 * PageSize, SmallBlock, LargeBlock - pageHeaderSize, 8 * PageSize);
result &= TestAllocs(ref lastAddr, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53);
}
EndTest(result);
}

private static void TestRethrow()
{
StartTest("Test rethrow");
Expand Down

0 comments on commit 73f4d14

Please sign in to comment.