Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ private CodeDirectoryBlob(
HashType hashType,
ExecutableSegmentFlags execSegmentFlags,
byte[][] specialSlotHashes,
byte[][] codeHashes)
byte[][] codeHashes,
ulong execSegmentBase = 0,
ulong execSegmentLimit = 0)
{
// Always assume the executable length is the entire file size / signature start.
_cdHeader = new CodeDirectoryHeader(
Expand All @@ -80,8 +82,8 @@ private CodeDirectoryBlob(
hashType.GetHashSize(),
hashType,
signatureStart,
0,
signatureStart,
execSegmentBase,
execSegmentLimit,
execSegmentFlags);
_identifier = identifier;
_specialSlotHashes = specialSlotHashes;
Expand Down Expand Up @@ -120,9 +122,15 @@ public static CodeDirectoryBlob Create(
string identifier,
RequirementsBlob requirementsBlob,
HashType hashType = HashType.SHA256,
uint pageSize = MachObjectFile.DefaultPageSize)
uint pageSize = MachObjectFile.DefaultPageSize,
ulong execSegmentBase = 0,
ulong execSegmentLimit = 0,
ulong textSegmentFileEnd = 0)
{
uint codeSlotCount = GetCodeSlotCount((uint)signatureStart, pageSize);
// When textSegmentFileEnd is provided, we only hash the __TEXT segment (macOS 26+ behavior).
// Otherwise, we hash the entire file up to the signature start (legacy behavior).
long hashLimit = textSegmentFileEnd > 0 ? (long)textSegmentFileEnd : signatureStart;
uint codeSlotCount = GetCodeSlotCount((uint)hashLimit, pageSize);
uint specialCodeSlotCount = (uint)CodeDirectorySpecialSlot.Requirements;

var specialSlotHashes = new byte[specialCodeSlotCount][];
Expand Down Expand Up @@ -150,7 +158,8 @@ public static CodeDirectoryBlob Create(
Array.Reverse(specialSlotHashes);

// 0 - N are Code hashes
long remaining = signatureStart;
// Hash up to the hash limit (either __TEXT segment end or signature start)
long remaining = hashLimit;
long buffptr = 0;
int cdIndex = 0;
byte[] pageBuffer = new byte[pageSize];
Expand All @@ -171,7 +180,9 @@ public static CodeDirectoryBlob Create(
hashType,
ExecutableSegmentFlags.MainBinary,
specialSlotHashes,
codeHashes);
codeHashes,
execSegmentBase,
execSegmentLimit);
}

[StructLayout(LayoutKind.Sequential)]
Expand Down Expand Up @@ -251,18 +262,29 @@ public override bool Equals(object? obj)
return false;

if (_identifier != other._identifier)
{
Debug.WriteLine($"Identifiers differ: '{_identifier}' vs '{other._identifier}'");
Debug.WriteLine($"Expected (codesign):\n{other.ToCodesignString()}");
Debug.WriteLine($"Actual (managed):\n{ToCodesignString()}");
return false;
}

CodeDirectoryHeader thisHeader = _cdHeader;
CodeDirectoryHeader otherHeader = other._cdHeader;
if (!CodeDirectoryHeader.AreEqual(thisHeader, otherHeader))
{
Debug.WriteLine("CodeDirectory headers differ");
Debug.WriteLine($"Expected (codesign):\n{other.ToCodesignString()}");
Debug.WriteLine($"Actual (managed):\n{ToCodesignString()}");
return false;
}
for (int i = 0; i < _specialSlotHashes.Length; i++)
{
if (!_specialSlotHashes[i].SequenceEqual(other._specialSlotHashes[i]))
{
Debug.WriteLine($"Special slot hash {-(int)SpecialSlotCount + i} differs");
Debug.WriteLine($"Expected (codesign):\n{other.ToCodesignString()}");
Debug.WriteLine($"Actual (managed):\n{ToCodesignString()}");
return false;
}
}
Expand All @@ -271,6 +293,9 @@ public override bool Equals(object? obj)
{
if (!_codeHashes[i].SequenceEqual(other._codeHashes[i]))
{
Debug.WriteLine($"Code hash {i} differs");
Debug.WriteLine($"Expected (codesign):\n{other.ToCodesignString()}");
Debug.WriteLine($"Actual (managed):\n{ToCodesignString()}");
return false;
}
}
Expand Down Expand Up @@ -317,4 +342,43 @@ public int Write(IMachOFileWriter accessor, long offset)
}
return (int)Size;
}

/// <summary>
/// Formats the CodeDirectory information in a style similar to codesign's output
/// </summary>
internal string ToCodesignString()
{
var sb = new StringBuilder();
sb.AppendLine($"Identifier={_identifier}");
sb.AppendLine($"CodeDirectory v={(uint)_cdHeader.Version:X} size={Size} flags=0x{(uint)_cdHeader.Flags:X}({_cdHeader.Flags.ToString().ToLower(System.Globalization.CultureInfo.InvariantCulture)}) hashes={CodeSlotCount}+{SpecialSlotCount} location=embedded");
sb.AppendLine($"Hash type={_cdHeader.HashType.ToString().ToLower(System.Globalization.CultureInfo.InvariantCulture)} size={HashSize}");
sb.AppendLine($"Executable Segment base={_cdHeader.ExecSegmentBase}");
sb.AppendLine($"Executable Segment limit={_cdHeader.ExecSegmentLimit}");
sb.AppendLine($"Executable Segment flags=0x{(ulong)_cdHeader.ExecSegmentFlags:X}");
sb.AppendLine($"Page size={(1 << _cdHeader.Log2PageSize)}");

// Print special slot hashes (numbered from -SpecialSlotCount to -1)
for (int i = 0; i < SpecialSlotCount; i++)
{
int slotNumber = -(int)SpecialSlotCount + i;
sb.AppendLine($" {slotNumber,3}={ToHexStringLower(_specialSlotHashes[i])}");
}

// Print code hashes (numbered from 0 to CodeSlotCount-1)
for (int i = 0; i < CodeSlotCount; i++)
{
sb.AppendLine($" {i,3}={ToHexStringLower(_codeHashes[i])}");
}

return sb.ToString();
}

private static string ToHexStringLower(byte[] bytes)
{
#if NET
return Convert.ToHexStringLower(bytes);
#else
return BitConverter.ToString(bytes).Replace("-", "").ToLower(System.Globalization.CultureInfo.InvariantCulture);
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;

namespace Microsoft.NET.HostModel.MachO;
Expand Down Expand Up @@ -175,15 +176,29 @@ public static void AssertEquivalent(EmbeddedSignatureBlob? a, EmbeddedSignatureB
throw new ArgumentNullException("Both EmbeddedSignatureBlobs must be non-null for comparison.");

if (a.GetSpecialSlotHashCount() != b.GetSpecialSlotHashCount())
{
Debug.WriteLine($"Special slot hash counts differ: {a.GetSpecialSlotHashCount()} vs {b.GetSpecialSlotHashCount()}");
throw new ArgumentException("Special slot hash counts are not equivalent.");
}

if (!a.CodeDirectoryBlob.Equals(b.CodeDirectoryBlob))
{
Debug.WriteLine("CodeDirectory blobs are not equivalent:");
Debug.WriteLine($"Expected (codesign):\n{b.CodeDirectoryBlob.ToCodesignString()}");
Debug.WriteLine($"Actual (managed):\n{a.CodeDirectoryBlob.ToCodesignString()}");
throw new ArgumentException("CodeDirectory blobs are not equivalent");
}

if (a.RequirementsBlob?.Size != b.RequirementsBlob?.Size)
{
Debug.WriteLine($"Requirements blob sizes differ: {a.RequirementsBlob?.Size} vs {b.RequirementsBlob?.Size}");
throw new ArgumentException("Requirements blobs are not equivalent");
}

if (a.CmsWrapperBlob?.Size != b.CmsWrapperBlob?.Size)
{
Debug.WriteLine($"CMS Wrapper blob sizes differ: {a.CmsWrapperBlob?.Size} vs {b.CmsWrapperBlob?.Size}");
throw new ArgumentException("CMS Wrapper blobs are not equivalent");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,29 @@ private static EmbeddedSignatureBlob CreateSignature(MachObjectFile machObject,
uint signatureStart = machObject._codeSignatureLoadCommand.Command.GetDataOffset(machObject._header);
RequirementsBlob requirementsBlob = RequirementsBlob.Empty;
CmsWrapperBlob cmsWrapperBlob = CmsWrapperBlob.Empty;

// Get __TEXT segment boundaries
// The VM address range is used for the CodeDirectory header metadata
ulong textSegmentVMAddress = machObject._textSegment64.Command.GetVMAddress(machObject._header);
ulong textSegmentVMSize = machObject._textSegment64.Command.GetVMSize(machObject._header);
ulong execSegmentBase = textSegmentVMAddress;
ulong execSegmentLimit = textSegmentVMAddress + textSegmentVMSize;

// The file range is used for hashing - we only hash the __TEXT segment content
// The __TEXT segment typically starts at file offset 0 and contains the Mach header and load commands
ulong textSegmentFileOffset = machObject._textSegment64.Command.GetFileOffset(machObject._header);
ulong textSegmentFileSize = machObject._textSegment64.Command.GetFileSize(machObject._header);
Debug.Assert(textSegmentFileOffset == 0, "Expected __TEXT segment to start at file offset 0");
ulong textSegmentFileEnd = textSegmentFileOffset + textSegmentFileSize;

var codeDirectory = CodeDirectoryBlob.Create(
file,
signatureStart,
identifier,
requirementsBlob);
requirementsBlob,
execSegmentBase: execSegmentBase,
execSegmentLimit: execSegmentLimit,
textSegmentFileEnd: textSegmentFileEnd);

return new EmbeddedSignatureBlob(
codeDirectoryBlob: codeDirectory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public void CanUnsignAndResign(string filePath, TestArtifact _)
Assert.True(IsSigned(managedSignedPath + ".resigned"), $"Failed to resign {filePath}");
}

[Theory(Skip = "Temporarily disabled due to macOS 26 codesign behavior change - only hashing __TEXT segment")]
[Theory]
[MemberData(nameof(GetTestFilePaths), nameof(MatchesCodesignOutput))]
[PlatformSpecific(TestPlatforms.OSX)]
void MatchesCodesignOutput(string filePath, TestArtifact _)
Expand Down
Loading