diff --git a/WhiteMagic/Assembly/StubAssembler.cs b/WhiteMagic/Assembly/StubAssembler.cs index 5f9d228..9c88494 100644 --- a/WhiteMagic/Assembly/StubAssembler.cs +++ b/WhiteMagic/Assembly/StubAssembler.cs @@ -13,6 +13,13 @@ namespace WhiteMagic.Assembly; /// public sealed class StubAssembler : IAssembler { + /// + /// The maximum number of call arguments a single stub may pass. Far above any real + /// calling convention; exists only to keep frame-size and stack-offset math from + /// overflowing . + /// + public const int MaxArguments = 256; + /// public byte[] Assemble(string assemblyText, ulong origin = 0) { @@ -51,11 +58,18 @@ public sealed class StubAssembler : IAssembler /// 4 (x86) or 8 (x64). /// Calling convention (ignored on x64; Windows has a single ABI). /// is not 4 or 8, - /// or is not known, or the distance between stub and target - /// exceeds the E8 rel32 range. + /// or is not known, or exceeds + /// , or the distance between stub and target exceeds the E8 rel32 + /// range. public byte[] BuildCallStub(IntPtr stubAddress, IntPtr targetAddress, nuint[] arguments, int pointerSize, CallConvention convention) { + ArgumentNullException.ThrowIfNull(arguments); + // Bound the argument count. No real calling convention passes anywhere near this + // many; the cap keeps the frame-size and stack-offset arithmetic (0x20 + 8*stackArgs) + // well inside int range, so it can never overflow into a bogus/negative frame. + ArgumentOutOfRangeException.ThrowIfGreaterThan(arguments.Length, MaxArguments, nameof(arguments)); + var buffer = new List(96); if (pointerSize == 4) diff --git a/WhiteMagicTest/StubAssemblerTests.cs b/WhiteMagicTest/StubAssemblerTests.cs index ec13fbf..0c72176 100644 --- a/WhiteMagicTest/StubAssemblerTests.cs +++ b/WhiteMagicTest/StubAssemblerTests.cs @@ -444,6 +444,20 @@ public class StubAssemblerTests Create().BuildCallStub((IntPtr)0x10000000, (IntPtr)0x12345678, [], 4, (CallConvention)99)); } + [Fact] + public void Too_many_arguments_throws_before_frame_math_overflows() + { + // Guards the 0x20 + 8*stackArgs frame arithmetic against int overflow. + // MaxArguments is honored (accepted) and MaxArguments+1 is rejected. + var atCap = new nuint[StubAssembler.MaxArguments]; + // At the cap the call still builds (cdecl x64), proving the bound is inclusive. + _ = Create().BuildCallStub((IntPtr)0x10000000, (IntPtr)0x10001000, atCap, 8, CallConvention.Cdecl); + + var overCap = new nuint[StubAssembler.MaxArguments + 1]; + Assert.Throws(() => + Create().BuildCallStub((IntPtr)0x10000000, (IntPtr)0x10001000, overCap, 8, CallConvention.Cdecl)); + } + // ── No-FASM ───────────────────────────────────────────────────────── [Fact]