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]