Fix bounds, memory protection, and completion race in core helpers

- AllocatedMemory.Read<T>/Write<T>/ReadBytes/WriteBytes now validate that
  the requested byte range stays within the allocated block before calling
  into the memory accessor.
- Patch.Apply/Remove temporarily changes the target page to read-write and
  restores the original protection, mirroring the Detour behavior.
- MainThreadPump.WorkItem uses TrySetResult/TrySetException and swallows the
  InvalidOperationException raised when a completion source is already
  completed, preventing Dispose from failing during concurrent pump drainage.

Regression tests added for all three fixes.

Tests: 206 passing, 4 skipped.
This commit is contained in:
kbe
2026-07-22 02:16:23 +02:00
parent ffd72b37ed
commit 1911514120
5 changed files with 261 additions and 16 deletions
+52 -2
View File
@@ -1,5 +1,7 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
using WhiteMagic.Native;
namespace WhiteMagic.Hooking;
@@ -53,7 +55,31 @@ public sealed class Patch : IDisposable
return;
OriginalBytes = _memory.ReadBytes(Address, PatchBytes.Length);
_memory.WriteBytes(Address, PatchBytes);
if (!NativeMethods.VirtualProtectEx(
_memory.Handle,
Address,
PatchBytes.Length,
MemoryProtectionType.ExecuteReadWrite,
out MemoryProtectionType oldProtect))
{
int error = Marshal.GetLastPInvokeError();
throw new InvalidOperationException($"Failed to change target memory protection: error {error}");
}
try
{
_memory.WriteBytes(Address, PatchBytes);
}
finally
{
NativeMethods.VirtualProtectEx(
_memory.Handle,
Address,
PatchBytes.Length,
oldProtect,
out _);
}
}
/// <summary>Restores the original bytes if they were captured.</summary>
@@ -62,7 +88,31 @@ public sealed class Patch : IDisposable
if (OriginalBytes is null)
return;
_memory.WriteBytes(Address, OriginalBytes);
if (!NativeMethods.VirtualProtectEx(
_memory.Handle,
Address,
OriginalBytes.Length,
MemoryProtectionType.ExecuteReadWrite,
out MemoryProtectionType oldProtect))
{
int error = Marshal.GetLastPInvokeError();
throw new InvalidOperationException($"Failed to change target memory protection: error {error}");
}
try
{
_memory.WriteBytes(Address, OriginalBytes);
}
finally
{
NativeMethods.VirtualProtectEx(
_memory.Handle,
Address,
OriginalBytes.Length,
oldProtect,
out _);
}
OriginalBytes = null;
}