Not64BitExe, // Apphost is expected to be a 64-bit MachO executable
DuplicateLinkEdit, // Only one __LINKEDIT segment is expected in the apphost
DuplicateSymtab, // Only one SYMTAB is expected in the apphost
- SignNeedsLinkEdit, // CODE_SIGNATURE command must follow a Segment64 command named __LINKEDIT
- SignNeedsSymtab, // CODE_SIGNATURE command must follow the SYMTAB command
+ MissingLinkEdit, // CODE_SIGNATURE command must follow a Segment64 command named __LINKEDIT
+ MissingSymtab, // CODE_SIGNATURE command must follow the SYMTAB command
LinkEditNotLast, // __LINKEDIT must be the last segment in the binary layout
SymtabNotInLinkEdit, // SYMTAB must within the __LINKEDIT segment!
SignNotInLinkEdit, // Signature blob must be within the __LINKEDIT segment!
SignBlobNotLast, // Signature blob must be at the very end of the file
SignDoesntFollowSymtab, // Signature blob must immediately follow the Symtab
MemoryMapAccessFault, // Error reading the memory-mapped apphost
- InvalidUTF8 // UTF8 decoding failed
+ InvalidUTF8, // UTF8 decoding failed
+ SignNotRemoved, // Signature not removed from the host (while processing a single-file bundle)
}
/// <summary>
using (var accessor = mappedFile.CreateViewAccessor())
{
byte* file = null;
- RuntimeHelpers.PrepareConstrainedRegions();
try
{
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref file);
if (signature != null)
{
- Verify(linkEdit != null, MachOFormatError.SignNeedsLinkEdit);
- Verify(symtab != null, MachOFormatError.SignNeedsSymtab);
+ Verify(linkEdit != null, MachOFormatError.MissingLinkEdit);
+ Verify(symtab != null, MachOFormatError.MissingSymtab);
var symtabEnd = symtab->stroff + symtab->strsize;
var linkEditEnd = linkEdit->fileoff + linkEdit->filesize;
return false;
}
}
+
+ /// <summary>
+ /// This Method is a utility to adjust the apphost MachO-header
+ /// to include the bytes added by the single-file bundler at the end of the file.
+ ///
+ /// The tool assumes the following layout of the executable
+ ///
+ /// * MachoHeader (64-bit, executable, not swapped integers)
+ /// * LoadCommands
+ /// LC_SEGMENT_64 (__PAGEZERO)
+ /// LC_SEGMENT_64 (__TEXT)
+ /// LC_SEGMENT_64 (__DATA)
+ /// LC_SEGMENT_64 (__LINKEDIT)
+ /// ...
+ /// LC_SYMTAB
+ ///
+ /// * ... Different Segments
+ ///
+ /// * The __LINKEDIT Segment (last)
+ /// * ... Different sections ...
+ /// * SYMTAB (last)
+ ///
+ /// The MAC codesign tool places several restrictions on the layout
+ /// * The __LINKEDIT segment must be the last one
+ /// * The __LINKEDIT segment must cover the end of the file
+ /// * All bytes in the __LINKEDIT segment are used by other linkage commands
+ /// (ex: symbol/string table, dynamic load information etc)
+ ///
+ /// In order to circumvent these restrictions, we:
+ /// * Extend the __LINKEDIT segment to include the bundle-data
+ /// * Extend the string table to include all the bundle-data
+ /// (that is, the bundle-data appear as strings to the loader/codesign tool).
+ ///
+ /// This method has certain limitations:
+ /// * The bytes for the bundler may be unnecessarily loaded at startup
+ /// * Tools that process the string table may be confused (?)
+ /// * The string table size is limited to 4GB. Bundles larger than that size
+ /// cannot be accomodated by this utility.
+ ///
+ /// </summary>
+ /// <param name="filePath">Path to the AppHost</param>
+ /// <returns>
+ /// True if
+ /// - The input is a MachO binary, and
+ /// - The additional bytes were successfully accomodated within the MachO segments.
+ /// False otherwise
+ /// </returns>
+ /// <exception cref="AppHostMachOFormatException">
+ /// The input is a MachO file, but doesn't match the expect format of the AppHost.
+ /// </exception>
+ public static unsafe bool AdjustHeadersForBundle(string filePath)
+ {
+ ulong fileLength = (ulong)new FileInfo(filePath).Length;
+ using (var mappedFile = MemoryMappedFile.CreateFromFile(filePath))
+ {
+ using (var accessor = mappedFile.CreateViewAccessor())
+ {
+ byte* file = null;
+ try
+ {
+ accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref file);
+ Verify(file != null, MachOFormatError.MemoryMapAccessFault);
+
+ MachHeader* header = (MachHeader*)file;
+
+ if (!header->IsValid())
+ {
+ // Not a MachO file.
+ return false;
+ }
+
+ Verify(header->Is64BitExecutable(), MachOFormatError.Not64BitExe);
+
+ file += sizeof(MachHeader);
+ SegmentCommand64* linkEdit = null;
+ SymtabCommand* symtab = null;
+ LinkEditDataCommand* signature = null;
+
+ for (uint i = 0; i < header->ncmds; i++)
+ {
+ LoadCommand* command = (LoadCommand*)file;
+ if (command->cmd == Command.LC_SEGMENT_64)
+ {
+ SegmentCommand64* segment = (SegmentCommand64*)file;
+ if (segment->SegName.Equals("__LINKEDIT"))
+ {
+ Verify(linkEdit == null, MachOFormatError.DuplicateLinkEdit);
+ linkEdit = segment;
+ }
+ }
+ else if (command->cmd == Command.LC_SYMTAB)
+ {
+ Verify(symtab == null, MachOFormatError.DuplicateSymtab);
+ symtab = (SymtabCommand*)command;
+ }
+
+ file += command->cmdsize;
+ }
+
+ Verify(linkEdit != null, MachOFormatError.MissingLinkEdit);
+ Verify(symtab != null, MachOFormatError.MissingSymtab);
+
+ // Update the string table to include bundle-data
+ ulong newStringTableSize = fileLength - symtab->stroff;
+ if (newStringTableSize > uint.MaxValue)
+ {
+ // Too big, too bad;
+ return false;
+ }
+ symtab->strsize = (uint)newStringTableSize;
+
+ // Update the __LINKEDIT segment to include bundle-data
+ linkEdit->filesize = fileLength - linkEdit->fileoff;
+ linkEdit->vmsize = linkEdit->filesize;
+ }
+ finally
+ {
+ if (file != null)
+ {
+ accessor.SafeMemoryMappedViewHandle.ReleasePointer();
+ }
+ }
+ }
+ }
+
+ return true;
+ }
}
}
using Microsoft.DotNet.Cli.Build.Framework;
using Microsoft.DotNet.CoreSetup.Test;
using BundleTests.Helpers;
+using System.Runtime.InteropServices;
namespace Microsoft.NET.HostModel.Tests
{
.HaveStdOutContaining("Wow! We now say hello to the big world and you.");
}
+ private void CheckFileNotarizable(string path)
+ {
+ // attempt to remove signature data.
+ // no-op if the file is not signed (it should not be)
+ // fail if the file structure is malformed
+ // i: input, o: output, r: remove
+ Command.Create("codesign_allocate", $"-i {path} -o {path} -r")
+ .CaptureStdErr()
+ .CaptureStdOut()
+ .Execute()
+ .Should()
+ .Pass();
+ }
+
private void BundleRun(TestProjectFixture fixture, string publishPath)
{
var hostName = BundleHelper.GetHostName(fixture);
// Bundle to a single-file
string singleFile = BundleHelper.BundleApp(fixture);
+ // check that the file structure is understood by codesign
+ var targetOS = BundleHelper.GetTargetOS(fixture.CurrentRid);
+ if (targetOS == OSPlatform.OSX)
+ {
+ CheckFileNotarizable(singleFile);
+ }
+
// Run the extracted app
RunTheApp(singleFile);
}