From 73f4d145db28d34a90d03e5b89781509d2d6af7f Mon Sep 17 00:00:00 2001 From: SingleAccretion Date: Thu, 18 May 2023 22:36:42 +0300 Subject: [PATCH] Dynamic stack alloc tests --- src/coreclr/jit/compiler.h | 4 + .../SmokeTests/HelloWasm/HelloWasm.cs | 338 ++++++++++++++++++ 2 files changed, 342 insertions(+) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 9b88445951db..fc6b1a2ffa60 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -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() { diff --git a/src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.cs b/src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.cs index eca8b4362dd2..3bb6a26566e0 100644 --- a/src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.cs +++ b/src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.cs @@ -2151,6 +2151,8 @@ private static void TestTryCatch() TestIntraFrameFilterOrderDeep(); + TestDynamicStackAlloc(); + TestCatchAndThrow(); TestRethrow(); @@ -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");