Skip to content

Commit

Permalink
Add managed MachO signing (dotnet#108992)
Browse files Browse the repository at this point in the history
Creates a managed implementation of Mach-O object file signing for the apphost.

This signer is byte-for-byte identical to the output of codesign except for padding at the end of the file and the corresponding size fields in the headers.

The MachObjectFile and CodeSignature store all the relevant headers and signature information to add/remove a signature from a Mach Object. Most other files are details regarding header format and reading files with different endianness.

The new signer uses a MemoryMappedViewAccessor instead of a Stream. Since memory mapped files can't be resized, MachObjectFile.CreateAdHocSignature() will write out the headers and load commands to the memory mapped file, but not the signature blob. The signature blob will instead be written to the destination file stream after the rest of the file is copied from the memory mapped file.

The signature is composed of an EmbeddedSignature superblob with an ID and offset of 3 following blobs, followed by a CodeDirectory blob, an empty Requirements blob, and an empty CMS blob. The empty Requirements blob and an empty CMS blob are not strictly required but are added for compatibility with codesign. The CodeDirectory includes the identifier string, hashes of each page of the file and information required to verify the hashes.

The signing process for an unsigned executable extends the __LINKEDIT segment (which is always at the end of the file) to make room for the signature blob, writes the signature blob to that location, and adds a CodeSignature load command that points the the signature. If the executable was already signed, it simply clears the old signature and replaces it, extends or shrinks the existing __LINKEDIT size and updates the existing CodeSignature load command.

Co-authored-by: Adeel Mujahid <[email protected]>
  • Loading branch information
jtschuster and am11 authored Nov 12, 2024
1 parent bb5baa8 commit 5d69e2d
Show file tree
Hide file tree
Showing 41 changed files with 1,753 additions and 390 deletions.
4 changes: 4 additions & 0 deletions eng/Version.Details.xml
Original file line number Diff line number Diff line change
Expand Up @@ -474,5 +474,9 @@
<Uri>https://github.com/dotnet/node</Uri>
<Sha>78c56619da525bd37de4c2828092762fb4fa03c4</Sha>
</Dependency>
<Dependency Name="Microsoft.NET.HostModel.TestData" Version="10.0.0-beta.24522.1">
<Uri>https://github.com/dotnet/runtime-assets</Uri>
<Sha>24f902e6d5bfe3fec9f07d55efe44794aec614a1</Sha>
</Dependency>
</ToolsetDependencies>
</Dependencies>
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
<SystemTextRegularExpressionsTestDataVersion>10.0.0-beta.24530.1</SystemTextRegularExpressionsTestDataVersion>
<SystemWindowsExtensionsTestDataVersion>10.0.0-beta.24530.1</SystemWindowsExtensionsTestDataVersion>
<MicrosoftDotNetCilStripSourcesVersion>10.0.0-beta.24530.1</MicrosoftDotNetCilStripSourcesVersion>
<MicrosoftNETHostModelTestDataVersion>10.0.0-beta.24522.1</MicrosoftNETHostModelTestDataVersion>
<!-- dotnet-optimization dependencies -->
<optimizationwindows_ntx64MIBCRuntimeVersion>1.0.0-prerelease.24462.2</optimizationwindows_ntx64MIBCRuntimeVersion>
<optimizationwindows_ntx86MIBCRuntimeVersion>1.0.0-prerelease.24462.2</optimizationwindows_ntx86MIBCRuntimeVersion>
Expand Down
85 changes: 42 additions & 43 deletions src/installer/managed/Microsoft.NET.HostModel/AppHost/HostWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.NET.HostModel.MachO;

namespace Microsoft.NET.HostModel.AppHost
{
Expand Down Expand Up @@ -60,7 +61,7 @@ public enum SearchLocation : byte
/// <param name="appBinaryFilePath">Full path to app binary or relative path to the result apphost file</param>
/// <param name="windowsGraphicalUserInterface">Specify whether to set the subsystem to GUI. Only valid for PE apphosts.</param>
/// <param name="assemblyToCopyResourcesFrom">Path to the intermediate assembly, used for copying resources to PE apphosts.</param>
/// <param name="enableMacOSCodeSign">Sign the app binary using codesign with an anonymous certificate.</param>
/// <param name="enableMacOSCodeSign">Sign the app binary with an anonymous certificate. Only use when the AppHost is a Mach-O file built for MacOS.</param>
/// <param name="disableCetCompat">Remove CET Shadow Stack compatibility flag if set</param>
/// <param name="dotNetSearchOptions">Options for how the created apphost should look for the .NET install</param>
public static void CreateAppHost(
Expand Down Expand Up @@ -118,48 +119,55 @@ void RewriteAppHost(MemoryMappedFile mappedFile, MemoryMappedViewAccessor access
{
RetryUtil.RetryOnIOError(() =>
{
FileStream appHostSourceStream = null;
MemoryMappedFile memoryMappedFile = null;
MemoryMappedViewAccessor memoryMappedViewAccessor = null;
try
bool isMachOImage;
using (FileStream appHostDestinationStream = new FileStream(appHostDestinationFilePath, FileMode.Create, FileAccess.ReadWrite))
{
// Open the source host file.
appHostSourceStream = new FileStream(appHostSourceFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1);
memoryMappedFile = MemoryMappedFile.CreateFromFile(appHostSourceStream, null, 0, MemoryMappedFileAccess.Read, HandleInheritability.None, true);
memoryMappedViewAccessor = memoryMappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.CopyOnWrite);

using (FileStream appHostSourceStream = new(appHostSourceFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1))
{
isMachOImage = MachObjectFile.IsMachOImage(appHostSourceStream);
if (!isMachOImage && enableMacOSCodeSign)
{
throw new InvalidDataException("Cannot sign a non-Mach-O file.");
}
appHostSourceStream.CopyTo(appHostDestinationStream);
}
// Get the size of the source app host to ensure that we don't write extra data to the destination.
// On Windows, the size of the view accessor is rounded up to the next page boundary.
long sourceAppHostLength = appHostSourceStream.Length;

// Transform the host file in-memory.
RewriteAppHost(memoryMappedFile, memoryMappedViewAccessor);

// Save the transformed host.
using (FileStream fileStream = new FileStream(appHostDestinationFilePath, FileMode.Create))
long appHostLength = appHostDestinationStream.Length;
string destinationFileName = Path.GetFileName(appHostDestinationFilePath);
// On Mac, we need to extend the file size to accommodate the signature.
long appHostTmpCapacity = enableMacOSCodeSign ?
appHostLength + MachObjectFile.GetSignatureSizeEstimate((uint)appHostLength, destinationFileName)
: appHostLength;

using (MemoryMappedFile memoryMappedFile = MemoryMappedFile.CreateFromFile(appHostDestinationStream, null, appHostTmpCapacity, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, true))
using (MemoryMappedViewAccessor memoryMappedViewAccessor = memoryMappedFile.CreateViewAccessor(0, appHostTmpCapacity, MemoryMappedFileAccess.ReadWrite))
{
BinaryUtils.WriteToStream(memoryMappedViewAccessor, fileStream, sourceAppHostLength);

// Remove the signature from MachO hosts.
if (!appHostIsPEImage)
// Transform the host file in-memory.
RewriteAppHost(memoryMappedFile, memoryMappedViewAccessor);
if (isMachOImage)
{
MachOUtils.RemoveSignature(fileStream);
if (enableMacOSCodeSign)
{
string fileName = Path.GetFileName(appHostDestinationFilePath);
MachObjectFile machObjectFile = MachObjectFile.Create(memoryMappedViewAccessor);
appHostLength = machObjectFile.CreateAdHocSignature(memoryMappedViewAccessor, fileName);
}
else if (MachObjectFile.TryRemoveCodesign(memoryMappedViewAccessor, out long? length))
{
appHostLength = length.Value;
}
}
}
appHostDestinationStream.SetLength(appHostLength);

if (assemblyToCopyResourcesFrom != null && appHostIsPEImage)
{
using var updater = new ResourceUpdater(fileStream, true);
updater.AddResourcesFromPEImage(assemblyToCopyResourcesFrom);
updater.Update();
}
if (assemblyToCopyResourcesFrom != null && appHostIsPEImage)
{
using var updater = new ResourceUpdater(appHostDestinationStream, true);
updater.AddResourcesFromPEImage(assemblyToCopyResourcesFrom);
updater.Update();
}
}
finally
{
memoryMappedViewAccessor?.Dispose();
memoryMappedFile?.Dispose();
appHostSourceStream?.Dispose();
}
});

if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
Expand All @@ -178,15 +186,6 @@ void RewriteAppHost(MemoryMappedFile mappedFile, MemoryMappedViewAccessor access
{
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not set file permission {Convert.ToString(filePermissionOctal, 8)} for {appHostDestinationFilePath}.");
}

if (enableMacOSCodeSign && RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && HostModelUtils.IsCodesignAvailable())
{
(int exitCode, string stdErr) = HostModelUtils.RunCodesign("-s -", appHostDestinationFilePath);
if (exitCode != 0)
{
throw new AppHostSigningException(exitCode, stdErr);
}
}
}
}
catch (Exception ex)
Expand Down
Loading

0 comments on commit 5d69e2d

Please sign in to comment.