mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 19:06:15 +08:00
Prevent invalid SaveData writes from damaging guest memory (#444)
Add an optional write monitor so the team can find future memory damage on each supported desktop system.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Libs.Tests.HLE;
|
||||
|
||||
public sealed class GuestWriteWatchTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0x0000001000000001, "torn")]
|
||||
[InlineData(0x0000FFFF00000001, "torn")]
|
||||
[InlineData(0x0000000008000000, "shift")]
|
||||
[InlineData(0x0000000080015F00, "shift")]
|
||||
[InlineData(0x0000000007FFFFFF, null)]
|
||||
[InlineData(0x0000000009000000, null)]
|
||||
[InlineData(0x000000003F800000, null)]
|
||||
[InlineData(0x0000000080015F01, null)]
|
||||
[InlineData(0x0001000000000001, null)]
|
||||
public void ClassifyBulkValue_RecognizesCorruptionSignatures(ulong value, string? expected)
|
||||
{
|
||||
Assert.Equal(expected, GuestWriteWatch.ClassifyBulkValue(value));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0)]
|
||||
[InlineData(1, 7)]
|
||||
[InlineData(3, 5)]
|
||||
[InlineData(7, 1)]
|
||||
[InlineData(8, 0)]
|
||||
public void FirstAlignedOffset_ReturnsTheNextEightByteBoundary(ulong address, int expected)
|
||||
{
|
||||
Assert.Equal(expected, GuestWriteWatch.FirstAlignedOffset(address));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0x1000, 8, 0x1000, true)]
|
||||
[InlineData(0x0FFF, 1, 0x1000, false)]
|
||||
[InlineData(0x0FFF, 2, 0x1000, true)]
|
||||
[InlineData(0x1008, 1, 0x1000, false)]
|
||||
[InlineData(ulong.MaxValue - 3, 4, ulong.MaxValue - 1, true)]
|
||||
[InlineData(ulong.MaxValue, 1, ulong.MaxValue, true)]
|
||||
[InlineData(0x1000, 0, 0x1000, false)]
|
||||
public void Overlaps_HandlesBoundariesWithoutOverflow(
|
||||
ulong address,
|
||||
int length,
|
||||
ulong slot,
|
||||
bool expected)
|
||||
{
|
||||
Assert.Equal(expected, GuestWriteWatch.Overlaps(address, length, slot));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0x10000, 0xF2, true)]
|
||||
[InlineData(0x10001, 0xF2, false)]
|
||||
[InlineData(0x10000, 0xF1, false)]
|
||||
public void IsPoolMapping_RequiresTheExpectedSizeAndProtection(
|
||||
ulong length,
|
||||
int protection,
|
||||
bool expected)
|
||||
{
|
||||
Assert.Equal(expected, GuestWriteWatch.IsPoolMapping(length, protection));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, 0)]
|
||||
[InlineData("", 0)]
|
||||
[InlineData("not-hex", 0)]
|
||||
[InlineData("80", 0x80)]
|
||||
[InlineData("0x80", 0x80)]
|
||||
[InlineData(" 0X801DB3BBB ", 0x801DB3BBB)]
|
||||
public void Parse_HandlesHexadecimalWatchValues(string? text, ulong expected)
|
||||
{
|
||||
Assert.Equal(expected, GuestWriteWatch.Parse(text));
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,9 @@ public sealed class SaveDataExportsTests : IDisposable
|
||||
private const ulong SyncParam = Base + 0xC80;
|
||||
private const ulong SetupParam = Base + 0xD00;
|
||||
private const ulong SetupResult = Base + 0xD80;
|
||||
private const ulong TransactionOut = Base + 0xE00;
|
||||
private const ulong StaleR8 = Base + 0xE08;
|
||||
private const ulong StaleR9 = Base + 0xE10;
|
||||
|
||||
private const int NoEvent = unchecked((int)0x809F0008);
|
||||
private const int ParameterError = unchecked((int)0x809F0000);
|
||||
@@ -225,4 +228,64 @@ public sealed class SaveDataExportsTests : IDisposable
|
||||
unchecked((int)0x809F0008),
|
||||
SaveDataExports.SaveDataDelete(Reg(rdi: DeleteParam)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTransactionResource_WithoutOutPointer_DoesNotProbeStaleRegisters()
|
||||
{
|
||||
const uint sentinel = 0xA5A5A5A5;
|
||||
Assert.True(_ctx.TryWriteUInt32(TransactionOut, sentinel));
|
||||
Assert.True(_ctx.TryWriteUInt32(StaleR8, sentinel));
|
||||
Assert.True(_ctx.TryWriteUInt32(StaleR9, sentinel));
|
||||
|
||||
var ctx = Reg(rdi: UserId, rdx: 0, rcx: TransactionOut);
|
||||
ctx[CpuRegister.R8] = StaleR8;
|
||||
ctx[CpuRegister.R9] = StaleR9;
|
||||
|
||||
Assert.Equal(0, SaveDataExports.SaveDataCreateTransactionResource(ctx));
|
||||
Assert.True(_ctx.TryReadUInt32(TransactionOut, out var rcxValue));
|
||||
Assert.True(_ctx.TryReadUInt32(StaleR8, out var r8Value));
|
||||
Assert.True(_ctx.TryReadUInt32(StaleR9, out var r9Value));
|
||||
Assert.Equal(sentinel, rcxValue);
|
||||
Assert.Equal(sentinel, r8Value);
|
||||
Assert.Equal(sentinel, r9Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTransactionResource_WithOutPointerFlag_WritesOnlyRcx()
|
||||
{
|
||||
const uint sentinel = 0xA5A5A5A5;
|
||||
Assert.True(_ctx.TryWriteUInt32(TransactionOut, 0));
|
||||
Assert.True(_ctx.TryWriteUInt32(StaleR8, sentinel));
|
||||
Assert.True(_ctx.TryWriteUInt32(StaleR9, sentinel));
|
||||
|
||||
var ctx = Reg(rdi: UserId, rdx: 1, rcx: TransactionOut);
|
||||
ctx[CpuRegister.R8] = StaleR8;
|
||||
ctx[CpuRegister.R9] = StaleR9;
|
||||
|
||||
Assert.Equal(0, SaveDataExports.SaveDataCreateTransactionResource(ctx));
|
||||
Assert.True(_ctx.TryReadUInt32(TransactionOut, out var resource));
|
||||
Assert.True(_ctx.TryReadUInt32(StaleR8, out var r8Value));
|
||||
Assert.True(_ctx.TryReadUInt32(StaleR9, out var r9Value));
|
||||
Assert.NotEqual(0u, resource);
|
||||
Assert.Equal(sentinel, r8Value);
|
||||
Assert.Equal(sentinel, r9Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTransactionResource_WithLegacyOutPointer_WritesOnlyRdx()
|
||||
{
|
||||
const uint sentinel = 0xA5A5A5A5;
|
||||
Assert.True(_ctx.TryWriteUInt32(TransactionOut, 0));
|
||||
Assert.True(_ctx.TryWriteUInt32(StaleR8, sentinel));
|
||||
|
||||
Assert.Equal(
|
||||
0,
|
||||
SaveDataExports.SaveDataCreateTransactionResource(
|
||||
Reg(rdi: UserId, rdx: TransactionOut, rcx: StaleR8)));
|
||||
|
||||
Assert.True(_ctx.TryReadUInt32(TransactionOut, out var resource));
|
||||
Assert.True(_ctx.TryReadUInt32(StaleR8, out var rcxValue));
|
||||
Assert.NotEqual(0u, resource);
|
||||
Assert.Equal(sentinel, rcxValue);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user