Files
mxaccessgw/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerExecutableValidatorTests.cs
T
Joseph Doherty 615b487a77 docs+ui: backfill XML doc comments and finish dashboard layout pass
Adds missing <summary>/<param> XML docs across 99 server, worker, and test
files so CommentChecker reports zero issues (TreatWarningsAsErrors needs the
analyzer clean). Bundles in WIP dashboard work: NavSection extraction,
MainLayout/site.css/js styling alignment, and DashboardOptions/Auth tweaks.
2026-05-27 14:20:10 -04:00

150 lines
5.9 KiB
C#

using System.Buffers.Binary;
using ZB.MOM.WW.MxGateway.Server.Configuration;
using ZB.MOM.WW.MxGateway.Server.Workers;
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Workers;
/// <summary>
/// Coverage for <see cref="WorkerExecutableValidator"/> PE-header architecture parsing
/// (finding Server-013). The validator reads the DOS <c>MZ</c> stub, follows the PE
/// header offset at <c>0x3c</c>, checks the <c>PE\0\0</c> signature, and compares the
/// machine field against the required <see cref="WorkerArchitecture"/>.
/// </summary>
public sealed class WorkerExecutableValidatorTests : IDisposable
{
private const ushort ImageFileMachineI386 = 0x014c;
private const ushort ImageFileMachineAmd64 = 0x8664;
private readonly List<string> _tempFiles = [];
/// <summary>Verifies that x86 executable matching required architecture does not throw.</summary>
[Fact]
public void Validate_X86ExecutableMatchingRequiredArchitecture_DoesNotThrow()
{
string path = WritePeFile(ImageFileMachineI386);
WorkerExecutableValidator.Validate(path, WorkerArchitecture.X86);
}
/// <summary>Verifies that x64 executable matching required architecture does not throw.</summary>
[Fact]
public void Validate_X64ExecutableMatchingRequiredArchitecture_DoesNotThrow()
{
string path = WritePeFile(ImageFileMachineAmd64);
WorkerExecutableValidator.Validate(path, WorkerArchitecture.X64);
}
/// <summary>Verifies that x64 executable when x86 required throws invalid executable.</summary>
[Fact]
public void Validate_X64ExecutableWhenX86Required_ThrowsInvalidExecutable()
{
string path = WritePeFile(ImageFileMachineAmd64);
WorkerProcessLaunchException exception = Assert.Throws<WorkerProcessLaunchException>(
() => WorkerExecutableValidator.Validate(path, WorkerArchitecture.X86));
Assert.Equal(WorkerProcessLaunchErrorCode.InvalidExecutable, exception.ErrorCode);
Assert.Contains("architecture", exception.Message, StringComparison.OrdinalIgnoreCase);
}
/// <summary>Verifies that x86 executable when x64 required throws invalid executable.</summary>
[Fact]
public void Validate_X86ExecutableWhenX64Required_ThrowsInvalidExecutable()
{
string path = WritePeFile(ImageFileMachineI386);
WorkerProcessLaunchException exception = Assert.Throws<WorkerProcessLaunchException>(
() => WorkerExecutableValidator.Validate(path, WorkerArchitecture.X64));
Assert.Equal(WorkerProcessLaunchErrorCode.InvalidExecutable, exception.ErrorCode);
}
/// <summary>Verifies that file without MZ header throws invalid executable.</summary>
[Fact]
public void Validate_FileWithoutMzHeader_ThrowsInvalidExecutable()
{
byte[] bytes = new byte[0x80];
// Leave the first two bytes as zero so the MZ signature check fails.
string path = WriteTempFile(bytes);
WorkerProcessLaunchException exception = Assert.Throws<WorkerProcessLaunchException>(
() => WorkerExecutableValidator.Validate(path, WorkerArchitecture.X86));
Assert.Equal(WorkerProcessLaunchErrorCode.InvalidExecutable, exception.ErrorCode);
Assert.Contains("MZ", exception.Message, StringComparison.Ordinal);
}
/// <summary>Verifies that file too small for PE header throws invalid executable.</summary>
[Fact]
public void Validate_FileTooSmallForPeHeader_ThrowsInvalidExecutable()
{
string path = WriteTempFile([(byte)'M', (byte)'Z']);
WorkerProcessLaunchException exception = Assert.Throws<WorkerProcessLaunchException>(
() => WorkerExecutableValidator.Validate(path, WorkerArchitecture.X86));
Assert.Equal(WorkerProcessLaunchErrorCode.InvalidExecutable, exception.ErrorCode);
}
/// <summary>Verifies that file without PE signature throws invalid executable.</summary>
[Fact]
public void Validate_FileWithoutPeSignature_ThrowsInvalidExecutable()
{
// Build a valid MZ header pointing at a PE offset that holds a wrong signature.
byte[] bytes = new byte[0x100];
bytes[0] = (byte)'M';
bytes[1] = (byte)'Z';
BinaryPrimitives.WriteInt32LittleEndian(bytes.AsSpan(0x3c, sizeof(int)), 0x80);
// PE region left as zeros — the "PE\0\0" signature check fails.
string path = WriteTempFile(bytes);
WorkerProcessLaunchException exception = Assert.Throws<WorkerProcessLaunchException>(
() => WorkerExecutableValidator.Validate(path, WorkerArchitecture.X86));
Assert.Equal(WorkerProcessLaunchErrorCode.InvalidExecutable, exception.ErrorCode);
Assert.Contains("PE", exception.Message, StringComparison.Ordinal);
}
private string WritePeFile(ushort machine)
{
const int peHeaderOffset = 0x80;
byte[] bytes = new byte[peHeaderOffset + 6];
bytes[0] = (byte)'M';
bytes[1] = (byte)'Z';
BinaryPrimitives.WriteInt32LittleEndian(bytes.AsSpan(0x3c, sizeof(int)), peHeaderOffset);
bytes[peHeaderOffset] = (byte)'P';
bytes[peHeaderOffset + 1] = (byte)'E';
bytes[peHeaderOffset + 2] = 0;
bytes[peHeaderOffset + 3] = 0;
BinaryPrimitives.WriteUInt16LittleEndian(bytes.AsSpan(peHeaderOffset + 4, sizeof(ushort)), machine);
return WriteTempFile(bytes);
}
private string WriteTempFile(byte[] bytes)
{
string path = Path.Combine(Path.GetTempPath(), $"mxgw-pe-{Guid.NewGuid():N}.bin");
File.WriteAllBytes(path, bytes);
_tempFiles.Add(path);
return path;
}
/// <inheritdoc />
public void Dispose()
{
foreach (string path in _tempFiles)
{
try
{
File.Delete(path);
}
catch (IOException)
{
// Best-effort cleanup of the temp PE fixtures.
}
}
_tempFiles.Clear();
}
}