Harden assembly timestamp handling

This commit is contained in:
Andy Howard
2026-09-16 12:20:14 +01:00
parent 0a5ec1259e
commit 8ef6e12788
4 changed files with 183 additions and 52 deletions
+69 -47
View File
@@ -1,62 +1,84 @@
using System;
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
namespace Sarand.Net.Standard.Base.Utils
{
//=======================================================================
public static class AssemblyUtil
{
//-------------------------------------------------------------------
#region Gets the build date and time (by reading the COFF header)
// http://msdn.microsoft.com/en-us/library/ms680313
// --- Definitions ---
struct _IMAGE_FILE_HEADER
{
#pragma warning disable CS0649
public ushort Machine;
public ushort NumberOfSections;
public uint TimeDateStamp;
public uint PointerToSymbolTable;
public uint NumberOfSymbols;
public ushort SizeOfOptionalHeader;
public ushort Characteristics;
#pragma warning restore CS0649
};
private const Int32 DosHeaderPeOffsetPosition = 0x3c;
private const UInt32 PeSignature = 0x00004550;
private const Int32 CoffTimeDateStampPosition = 8;
// --- Build Information ---
/// <summary>
/// Gets the UTC date and time represented by the assembly's PE/COFF timestamp.
/// </summary>
/// <remarks>
/// Deterministic builds may not store the physical build time in this field.
/// Use <see cref="TryGetBuildDateTime(Assembly, out DateTime)"/> when the caller
/// needs to distinguish an unavailable timestamp from <see cref="DateTime.MinValue"/>.
/// </remarks>
public static DateTime GetBuildDateTime(this Assembly assembly)
{
var path = assembly.Location;
if (File.Exists(path))
{
var buffer = new byte[Math.Max(Marshal.SizeOf(typeof(_IMAGE_FILE_HEADER)), 4)];
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
{
fileStream.Position = 0x3C;
fileStream.Read(buffer, 0, 4);
fileStream.Position = BitConverter.ToUInt32(buffer, 0); // COFF header offset
fileStream.Read(buffer, 0, 4); // "PE\0\0"
fileStream.Read(buffer, 0, buffer.Length);
}
var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try
{
var coffHeader = (_IMAGE_FILE_HEADER)Marshal.PtrToStructure(pinnedBuffer.AddrOfPinnedObject(), typeof(_IMAGE_FILE_HEADER));
DateTime buildDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
return buildDate.AddSeconds(coffHeader.TimeDateStamp);
//return TimeZoneInfo.Local.ToLocalTime(new DateTime(1970, 1, 1) + new TimeSpan(coffHeader.TimeDateStamp * TimeSpan.TicksPerSecond));
}
finally
{
pinnedBuffer.Free();
}
}
return new DateTime();
return TryGetBuildDateTime(assembly, out DateTime buildDateTime)
? buildDateTime
: DateTime.MinValue;
}
#endregion
//-------------------------------------------------------------------
/// <summary>
/// Tries to get the UTC date and time represented by the assembly's PE/COFF timestamp.
/// </summary>
public static Boolean TryGetBuildDateTime(this Assembly assembly, out DateTime buildDateTime)
{
ArgumentNullException.ThrowIfNull(assembly);
if (String.IsNullOrEmpty(assembly.Location))
{
buildDateTime = DateTime.MinValue;
return false;
}
return TryGetBuildDateTime(assembly.Location, out buildDateTime);
}
// --- PE Header Parsing ---
internal static Boolean TryGetBuildDateTime(String assemblyPath, out DateTime buildDateTime)
{
buildDateTime = DateTime.MinValue;
try
{
using FileStream fileStream = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using BinaryReader reader = new BinaryReader(fileStream);
fileStream.Position = DosHeaderPeOffsetPosition;
UInt32 coffHeaderOffset = reader.ReadUInt32();
fileStream.Position = coffHeaderOffset;
if (reader.ReadUInt32() != PeSignature)
{
return false;
}
fileStream.Position = coffHeaderOffset + CoffTimeDateStampPosition;
UInt32 timeDateStamp = reader.ReadUInt32();
buildDateTime = DateTime.UnixEpoch.AddSeconds(timeDateStamp);
return true;
}
catch (Exception exception) when (exception is IOException
or UnauthorizedAccessException
or ArgumentException
or NotSupportedException)
{
return false;
}
}
// --- The End ---
}
//=======================================================================
}