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; /// /// Coverage for PE-header architecture parsing /// (finding Server-013). The validator reads the DOS MZ stub, follows the PE /// header offset at 0x3c, checks the PE\0\0 signature, and compares the /// machine field against the required . /// public sealed class WorkerExecutableValidatorTests : IDisposable { private const ushort ImageFileMachineI386 = 0x014c; private const ushort ImageFileMachineAmd64 = 0x8664; private readonly List _tempFiles = []; /// Verifies that x86 executable matching required architecture does not throw. [Fact] public void Validate_X86ExecutableMatchingRequiredArchitecture_DoesNotThrow() { string path = WritePeFile(ImageFileMachineI386); WorkerExecutableValidator.Validate(path, WorkerArchitecture.X86); } /// Verifies that x64 executable matching required architecture does not throw. [Fact] public void Validate_X64ExecutableMatchingRequiredArchitecture_DoesNotThrow() { string path = WritePeFile(ImageFileMachineAmd64); WorkerExecutableValidator.Validate(path, WorkerArchitecture.X64); } /// Verifies that x64 executable when x86 required throws invalid executable. [Fact] public void Validate_X64ExecutableWhenX86Required_ThrowsInvalidExecutable() { string path = WritePeFile(ImageFileMachineAmd64); WorkerProcessLaunchException exception = Assert.Throws( () => WorkerExecutableValidator.Validate(path, WorkerArchitecture.X86)); Assert.Equal(WorkerProcessLaunchErrorCode.InvalidExecutable, exception.ErrorCode); Assert.Contains("architecture", exception.Message, StringComparison.OrdinalIgnoreCase); } /// Verifies that x86 executable when x64 required throws invalid executable. [Fact] public void Validate_X86ExecutableWhenX64Required_ThrowsInvalidExecutable() { string path = WritePeFile(ImageFileMachineI386); WorkerProcessLaunchException exception = Assert.Throws( () => WorkerExecutableValidator.Validate(path, WorkerArchitecture.X64)); Assert.Equal(WorkerProcessLaunchErrorCode.InvalidExecutable, exception.ErrorCode); } /// Verifies that file without MZ header throws invalid executable. [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( () => WorkerExecutableValidator.Validate(path, WorkerArchitecture.X86)); Assert.Equal(WorkerProcessLaunchErrorCode.InvalidExecutable, exception.ErrorCode); Assert.Contains("MZ", exception.Message, StringComparison.Ordinal); } /// Verifies that file too small for PE header throws invalid executable. [Fact] public void Validate_FileTooSmallForPeHeader_ThrowsInvalidExecutable() { string path = WriteTempFile([(byte)'M', (byte)'Z']); WorkerProcessLaunchException exception = Assert.Throws( () => WorkerExecutableValidator.Validate(path, WorkerArchitecture.X86)); Assert.Equal(WorkerProcessLaunchErrorCode.InvalidExecutable, exception.ErrorCode); } /// Verifies that file without PE signature throws invalid executable. [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( () => 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; } /// public void Dispose() { foreach (string path in _tempFiles) { try { File.Delete(path); } catch (IOException) { // Best-effort cleanup of the temp PE fixtures. } } _tempFiles.Clear(); } }