Files
kbe 44a368de9c Fix dispatcher crash/deadlock, instruction analyzer, cache equality, task leak, and redirection protection
- MainThreadDispatcher: guard DispatchHook with try/catch so exceptions never escape to native caller; drain and fault pending work on Dispose; synchronize Execute/ExecuteAsync/Dispose against race/dispose.
- InstructionAnalyzer: require ModRM 0xEC for 0x83/0x81 sub-esp/rsp forms, rejecting unsafe RIP-relative or memory forms.
- PatternScannerCache: implement value equality on CacheKey so repeated scans actually hit cache.
- BackgroundTaskExecutor: add remote allocations to the free list immediately after VirtualAllocEx, before any write that could fail and leak.
- redirect: capture and restore original page protection in Apply/Remove instead of leaving target RWX.
- Regression tests for all six fixes.

Tests: 198 passing, 4 integration/interactive skipped.
2026-07-22 00:32:19 +02:00

43 lines
2.0 KiB
C#

using WhiteMagic.Hooking;
using Xunit;
namespace WhiteMagicTest.Hooking;
/// <summary>
/// Tests for <see cref="PrologueDecoder"/> covering the accepted x86/x64 prologue
/// shapes and rejection of opcodes outside the covered set.
/// </summary>
public class PrologueDecoderTests
{
[Theory]
[InlineData(false, new byte[] { 0x55 }, 1)] // push rbp
[InlineData(false, new byte[] { 0x53 }, 1)] // push rbx
[InlineData(false, new byte[] { 0x8B, 0xFF }, 2)] // mov edi, edi
[InlineData(false, new byte[] { 0x8B, 0xEC }, 2)] // mov ebp, esp
[InlineData(false, new byte[] { 0x83, 0xEC, 0x20 }, 3)] // sub esp, 0x20
[InlineData(false, new byte[] { 0x81, 0xEC, 0x00, 0x01, 0x00, 0x00 }, 6)] // sub esp, 0x100
[InlineData(true, new byte[] { 0x48, 0x8B, 0xEC }, 3)] // mov rbp, rsp
[InlineData(true, new byte[] { 0x48, 0x83, 0xEC, 0x28 }, 4)] // sub rsp, 0x28
[InlineData(true, new byte[] { 0x48, 0x81, 0xEC, 0x78, 0x56, 0x34, 0x12 }, 7)] // sub rsp, 0x12345678
public void Decodes_covered_prologue_shapes(bool is64Bit, byte[] bytes, int expectedLength)
{
int length = PrologueDecoder.GetInstructionLength(bytes, is64Bit);
Assert.Equal(expectedLength, length);
}
[Theory]
[InlineData(false, new byte[] { 0x83, 0x05, 0x39, 0x00, 0x00, 0x00, 0x01 })] // add [rip+0x39], 1 — wrong modrm
[InlineData(false, new byte[] { 0x83, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x01 })] // cmp [rip], 1 — wrong modrm
[InlineData(false, new byte[] { 0x81, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 })] // add [rip], 1 — wrong modrm
[InlineData(false, new byte[] { 0x0F, 0x05 })] // syscall
[InlineData(false, new byte[] { 0x90, 0x90 })] // nop
public void Rejects_unsafe_or_uncovered_shapes(bool is64Bit, byte[] bytes)
{
int length = PrologueDecoder.GetInstructionLength(bytes, is64Bit);
Assert.Equal(-1, length);
Assert.Throws<InvalidOperationException>(
() => PrologueDecoder.GetWholeInstructionLength(bytes, requiredBytes: 2, is64Bit));
}
}