Guard diagnostic stub builder against argument-count overflow

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) <noreply@anthropic.com>
This commit is contained in:
kbe
2026-07-21 22:30:11 +02:00
co-authored by Claude Opus 4.8
parent e2654fff91
commit 75ff4a2320
2 changed files with 30 additions and 2 deletions
+16 -2
View File
@@ -13,6 +13,13 @@ namespace WhiteMagic.Assembly;
/// </remarks>
public sealed class StubAssembler : IAssembler
{
/// <summary>
/// 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 <see cref="int"/>.
/// </summary>
public const int MaxArguments = 256;
/// <inheritdoc />
public byte[] Assemble(string assemblyText, ulong origin = 0)
{
@@ -51,11 +58,18 @@ public sealed class StubAssembler : IAssembler
/// <param name="pointerSize">4 (x86) or 8 (x64).</param>
/// <param name="convention">Calling convention (ignored on x64; Windows has a single ABI).</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="pointerSize"/> is not 4 or 8,
/// or <paramref name="convention"/> is not known, or the distance between stub and target
/// exceeds the E8 rel32 range.</exception>
/// or <paramref name="convention"/> is not known, or <paramref name="arguments"/> exceeds
/// <see cref="MaxArguments"/>, or the distance between stub and target exceeds the E8 rel32
/// range.</exception>
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<byte>(96);
if (pointerSize == 4)
+14
View File
@@ -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<ArgumentOutOfRangeException>(() =>
Create().BuildCallStub((IntPtr)0x10000000, (IntPtr)0x10001000, overCap, 8, CallConvention.Cdecl));
}
// ── No-FASM ─────────────────────────────────────────────────────────
[Fact]