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
+30 -9
View File
@@ -86,12 +86,18 @@ public sealed class AllocatedMemory : IDisposable
public IntPtr AddressOf(string name)
{
ObjectDisposedException.ThrowIf(_disposed, this);
int offset = GetRegionOffset(name);
return _baseAddress + offset;
}
private int GetRegionOffset(string name)
{
ArgumentNullException.ThrowIfNull(name);
if (!_regions.TryGetValue(name, out int offset))
throw new ArgumentException($"Region '{name}' does not exist.", nameof(name));
return _baseAddress + offset;
return offset;
}
/// <summary>
@@ -105,8 +111,12 @@ public sealed class AllocatedMemory : IDisposable
{
ObjectDisposedException.ThrowIf(_disposed, this);
IntPtr address = AddressOf(name);
return _memory.Read<T>(address);
int offset = GetRegionOffset(name);
int size = Marshal.SizeOf<T>();
if (offset > _size - size)
throw new ArgumentOutOfRangeException(nameof(name), $"Region '{name}' read of {size} bytes exceeds allocation size {_size}.");
return _memory.Read<T>(_baseAddress + offset);
}
/// <summary>
@@ -121,8 +131,12 @@ public sealed class AllocatedMemory : IDisposable
{
ObjectDisposedException.ThrowIf(_disposed, this);
IntPtr address = AddressOf(name);
return _memory.Write(address, value);
int offset = GetRegionOffset(name);
int size = Marshal.SizeOf<T>();
if (offset > _size - size)
throw new ArgumentOutOfRangeException(nameof(name), $"Region '{name}' write of {size} bytes exceeds allocation size {_size}.");
return _memory.Write(_baseAddress + offset, value);
}
/// <summary>
@@ -136,8 +150,12 @@ public sealed class AllocatedMemory : IDisposable
{
ObjectDisposedException.ThrowIf(_disposed, this);
IntPtr address = AddressOf(name);
return _memory.ReadBytes(address, count);
int offset = GetRegionOffset(name);
ArgumentOutOfRangeException.ThrowIfNegative(count);
if (offset > _size - count)
throw new ArgumentOutOfRangeException(nameof(count), $"Region '{name}' read of {count} bytes exceeds allocation size {_size}.");
return _memory.ReadBytes(_baseAddress + offset, count);
}
/// <summary>
@@ -151,8 +169,11 @@ public sealed class AllocatedMemory : IDisposable
{
ObjectDisposedException.ThrowIf(_disposed, this);
IntPtr address = AddressOf(name);
return _memory.WriteBytes(address, bytes);
int offset = GetRegionOffset(name);
if (offset > _size - bytes.Length)
throw new ArgumentOutOfRangeException(nameof(bytes), $"Region '{name}' write of {bytes.Length} bytes exceeds allocation size {_size}.");
return _memory.WriteBytes(_baseAddress + offset, bytes);
}
/// <summary>