未验证 提交 038d5ecb 编写于 作者: V Vladimir Sadov 提交者: GitHub

signable single file apps on mac (#46558)

* extend the __LINKEDIT. section to cover the single-exe metadata.

* codesign test
上级 662f306a
......@@ -13,8 +13,8 @@ public enum MachOFormatError
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!
......@@ -22,7 +22,8 @@ public enum MachOFormatError
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>
......
......@@ -172,6 +172,9 @@ void SetLastWriteTime()
BitConverter.GetBytes(bundleHeaderOffset),
pad0s: false));
RetryUtil.RetryOnIOError(() =>
MachOUtils.AdjustHeadersForBundle(appHostPath));
// Memory-mapped write does not updating last write time
RetryUtil.RetryOnIOError(() =>
File.SetLastWriteTimeUtc(appHostPath, DateTime.UtcNow));
......
......@@ -214,7 +214,6 @@ public static unsafe bool RemoveSignature(string filePath)
using (var accessor = mappedFile.CreateViewAccessor())
{
byte* file = null;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref file);
......@@ -264,8 +263,8 @@ public static unsafe bool RemoveSignature(string filePath)
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;
......@@ -319,5 +318,132 @@ public static unsafe bool RemoveSignature(string filePath)
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;
}
}
}
......@@ -7,6 +7,7 @@
using Microsoft.DotNet.Cli.Build.Framework;
using Microsoft.DotNet.CoreSetup.Test;
using BundleTests.Helpers;
using System.Runtime.InteropServices;
namespace Microsoft.NET.HostModel.Tests
{
......@@ -31,6 +32,20 @@ private void RunTheApp(string path)
.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);
......@@ -41,6 +56,13 @@ private void BundleRun(TestProjectFixture fixture, string publishPath)
// 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);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册