This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
HostWriter: Remove signature on MAC host (#8625)
In order to conform with Catalina notarization requirements, the template apphost will be shipped after codesigning. This commit implements a change to remove the code-signature when customizing the apphost for an app. Testing: - Tested the HostModel library using a locally signed version of apphost - Verified that unsigned host can be re-signed later - Verified that the original unsigned-apphost, and the signature-removed-apphost are binary equal.
- Loading branch information
1 parent
924d3fe
commit e77f4a4
Showing
3 changed files
with
376 additions
and
2 deletions.
There are no files selected for viewing
43 changes: 43 additions & 0 deletions
43
src/managed/Microsoft.NET.HostModel/AppHost/AppHostMachOFormatException.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System; | ||
|
||
namespace Microsoft.NET.HostModel.AppHost | ||
{ | ||
/// <summary> | ||
/// Additional details about the failure with caused an AppHostMachOFormatException | ||
/// </summary> | ||
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 | ||
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! | ||
SignCommandNotLast, // CODE_SIGNATURE command must be the last command | ||
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 | ||
} | ||
|
||
/// <summary> | ||
/// The MachO application host executable cannot be customized because | ||
/// it was not in the expected format | ||
/// </summary> | ||
public class AppHostMachOFormatException : AppHostUpdateException | ||
{ | ||
public readonly MachOFormatError Error; | ||
|
||
public AppHostMachOFormatException(MachOFormatError error) | ||
{ | ||
Error = error; | ||
} | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
319 changes: 319 additions & 0 deletions
319
src/managed/Microsoft.NET.HostModel/AppHost/MachOUtils.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,319 @@ | ||
// Copyright (c) .NET Foundation and contributors. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
using System.IO; | ||
using System.IO.MemoryMappedFiles; | ||
using System.Runtime.CompilerServices; | ||
using System.Text; | ||
|
||
namespace Microsoft.NET.HostModel.AppHost | ||
{ | ||
internal static class MachOUtils | ||
{ | ||
// The MachO Headers are copied from | ||
// https://opensource.apple.com/source/cctools/cctools-870/include/mach-o/loader.h | ||
// | ||
// The data fields and enumerations match the structure definitions in the above file, | ||
// and hence do not conform to C# CoreFx naming style. | ||
|
||
enum Magic : uint | ||
{ | ||
MH_MAGIC = 0xfeedface, | ||
MH_CIGAM = 0xcefaedfe, | ||
MH_MAGIC_64 = 0xfeedfacf, | ||
MH_CIGAM_64 = 0xcffaedfe | ||
} | ||
|
||
enum FileType : uint | ||
{ | ||
MH_EXECUTE = 0x2 | ||
} | ||
|
||
#pragma warning disable 0649 | ||
struct MachHeader | ||
{ | ||
public Magic magic; | ||
public int cputype; | ||
public int cpusubtype; | ||
public FileType filetype; | ||
public uint ncmds; | ||
public uint sizeofcmds; | ||
public uint flags; | ||
public uint reserved; | ||
|
||
public bool Is64BitExecutable() | ||
{ | ||
return magic == Magic.MH_MAGIC_64 && filetype == FileType.MH_EXECUTE; | ||
} | ||
|
||
public bool IsValid() | ||
{ | ||
switch (magic) | ||
{ | ||
case Magic.MH_CIGAM: | ||
case Magic.MH_CIGAM_64: | ||
case Magic.MH_MAGIC: | ||
case Magic.MH_MAGIC_64: | ||
return true; | ||
|
||
default: | ||
return false; | ||
} | ||
} | ||
} | ||
|
||
enum Command : uint | ||
{ | ||
LC_SYMTAB = 0x2, | ||
LC_SEGMENT_64 = 0x19, | ||
LC_CODE_SIGNATURE = 0x1d, | ||
} | ||
|
||
struct LoadCommand | ||
{ | ||
public Command cmd; | ||
public uint cmdsize; | ||
} | ||
|
||
// The linkedit_data_command contains the offsets and sizes of a blob | ||
// of data in the __LINKEDIT segment (including LC_CODE_SIGNATURE). | ||
struct LinkEditDataCommand | ||
{ | ||
public Command cmd; | ||
public uint cmdsize; | ||
public uint dataoff; | ||
public uint datasize; | ||
|
||
public void ZeroInit() | ||
{ | ||
cmd = 0; | ||
cmdsize = 0; | ||
dataoff = 0; | ||
datasize = 0; | ||
} | ||
} | ||
|
||
struct SymtabCommand | ||
{ | ||
public uint cmd; | ||
public uint cmdsize; | ||
public uint symoff; | ||
public uint nsyms; | ||
public uint stroff; | ||
public uint strsize; | ||
}; | ||
|
||
unsafe struct SegmentCommand64 | ||
{ | ||
public Command cmd; | ||
public uint cmdsize; | ||
public fixed byte segname[16]; | ||
public ulong vmaddr; | ||
public ulong vmsize; | ||
public ulong fileoff; | ||
public ulong filesize; | ||
public int maxprot; | ||
public int initprot; | ||
public uint nsects; | ||
public uint flags; | ||
|
||
public string SegName | ||
{ | ||
get | ||
{ | ||
fixed (byte* p = segname) | ||
{ | ||
int len = 0; | ||
while (*(p + len) != 0 && len++ < 16) ; | ||
|
||
try | ||
{ | ||
return Encoding.UTF8.GetString(p, len); | ||
} | ||
catch(ArgumentException) | ||
{ | ||
throw new AppHostMachOFormatException(MachOFormatError.InvalidUTF8); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
#pragma warning restore 0649 | ||
|
||
private static void Verify(bool condition, MachOFormatError error) | ||
{ | ||
if (!condition) | ||
{ | ||
throw new AppHostMachOFormatException(error); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// This Method is a utility to remove the code-signature (if any) | ||
/// from a MachO AppHost binary. | ||
/// | ||
/// 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 | ||
/// ... | ||
/// LC_CODE_SIGNATURE (last) | ||
/// | ||
/// * ... Different Segments ... | ||
/// | ||
/// * The __LINKEDIT Segment (last) | ||
/// * ... Different sections ... | ||
/// * SYMTAB | ||
/// * (Some alignment bytes) | ||
/// * The Code-signature | ||
/// | ||
/// In order to remove the signature, the method: | ||
/// - Removes (zeros out) the LC_CODE_SIGNATURE command | ||
/// - Adjusts the size and count of the load commands in the header | ||
/// - Truncates the size of the __LINKEDIT segment to the end of SYMTAB | ||
/// - Truncates the apphost file to the end of the __LINKEDIT segment | ||
/// | ||
/// </summary> | ||
/// <param name="filePath">Path to the AppHost</param> | ||
/// <returns> | ||
/// True if | ||
/// - The input is a MachO binary, and | ||
/// - It is a signed binary, and | ||
/// - The signature was successfully removed | ||
/// False otherwise | ||
/// </returns> | ||
/// <exception cref="AppHostMachOFormatException"> | ||
/// The input is a MachO file, but doesn't match the expect format of the AppHost. | ||
/// </exception> | ||
unsafe public static bool RemoveSignature(string filePath) | ||
{ | ||
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite)) | ||
{ | ||
uint signatureSize = 0; | ||
using (var mappedFile = MemoryMappedFile.CreateFromFile(stream, | ||
mapName: null, | ||
capacity: 0, | ||
MemoryMappedFileAccess.ReadWrite, | ||
HandleInheritability.None, | ||
leaveOpen: true)) | ||
{ | ||
using (var accessor = mappedFile.CreateViewAccessor()) | ||
{ | ||
byte* file = null; | ||
RuntimeHelpers.PrepareConstrainedRegions(); | ||
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; | ||
} | ||
else if (command->cmd == Command.LC_CODE_SIGNATURE) | ||
{ | ||
Verify(i == header->ncmds - 1, MachOFormatError.SignCommandNotLast); | ||
signature = (LinkEditDataCommand*)command; | ||
break; | ||
} | ||
|
||
file += command->cmdsize; | ||
} | ||
|
||
if (signature != null) | ||
{ | ||
Verify(linkEdit != null, MachOFormatError.SignNeedsLinkEdit); | ||
Verify(symtab != null, MachOFormatError.SignNeedsSymtab); | ||
|
||
var symtabEnd = symtab->stroff + symtab->strsize; | ||
var linkEditEnd = linkEdit->fileoff + linkEdit->filesize; | ||
var signatureEnd = signature->dataoff + signature->datasize; | ||
var fileEnd = (ulong)stream.Length; | ||
|
||
Verify(linkEditEnd == fileEnd, MachOFormatError.LinkEditNotLast); | ||
Verify(signatureEnd == fileEnd, MachOFormatError.SignBlobNotLast); | ||
|
||
Verify(symtab->symoff > linkEdit->fileoff, MachOFormatError.SymtabNotInLinkEdit); | ||
Verify(signature->dataoff > linkEdit->fileoff, MachOFormatError.SignNotInLinkEdit); | ||
|
||
// The signature blob immediately follows the symtab blob, | ||
// except for a few bytes of padding. | ||
Verify(signature->dataoff >= symtabEnd && signature->dataoff - symtabEnd < 32, MachOFormatError.SignBlobNotLast); | ||
|
||
// Remove the signature command | ||
header->ncmds--; | ||
header->sizeofcmds -= signature->cmdsize; | ||
|
||
signature->ZeroInit(); | ||
|
||
// Remove the signature blob (note for truncation) | ||
signatureSize = (uint)(fileEnd - symtabEnd); | ||
|
||
// Adjust the __LINKEDIT segment load command | ||
linkEdit->filesize -= signatureSize; | ||
|
||
// codesign --remove-signature doesn't reset the vmsize. | ||
// Setting the vmsize here makes the output bin-equal with the original | ||
// unsigned apphost (and not bin-equal with a signed-unsigned-apphost). | ||
linkEdit->vmsize = linkEdit->filesize; | ||
} | ||
} | ||
finally | ||
{ | ||
if(file != null) | ||
{ | ||
accessor.SafeMemoryMappedViewHandle.ReleasePointer(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
if (signatureSize != 0) | ||
{ | ||
// The signature was removed, update the file length | ||
stream.SetLength(stream.Length - signatureSize); | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
} | ||
} | ||
} | ||
|