From 75ff4a232085642f868cd1c908f9e16f3ab63322 Mon Sep 17 00:00:00 2001 From: Kevin Bataille Date: Tue, 21 Jul 2026 22:09:56 +0200 Subject: [PATCH] Guard diagnostic stub builder against argument-count overflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The x64 frame math (0x20 + 8*stackArgs) and x86 arg buffer size grow with the argument count. An absurdly large count could overflow int and produce a bogus or negative frame. Add a MaxArguments (256) bound checked at the public entry — far above any real calling convention — so the arithmetic stays in range. Add a test asserting the cap is inclusive and count+1 throws. Co-Authored-By: Claude Opus 4.8 (1M context) --- WhiteMagic/Assembly/StubAssembler.cs | 18 ++++++++++++++++-- WhiteMagicTest/StubAssemblerTests.cs | 14 ++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) 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]