Skip to content

Commit 3295ed2

Browse files
author
William Li
authored
Merge pull request #3832 from wli3/port-catalina-signing
port remove signature on MAC host
2 parents 4322791 + 7b13136 commit 3295ed2

File tree

3 files changed

+364
-0
lines changed

3 files changed

+364
-0
lines changed

src/Tasks/Microsoft.NET.Build.Tasks/AppHost.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ public static void Create(
6161
SearchAndReplace(accessor, _bytesToSearch, bytesToWrite, appHostSourceFilePath);
6262
}
6363
}
64+
65+
MachOUtils.RemoveSignature(appHostDestinationFilePath);
6466
}
6567

6668
// See: https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
7+
namespace Microsoft.NET.Build.Tasks
8+
{
9+
/// <summary>
10+
/// Additional details about the failure with caused an AppHostMachOFormatException
11+
/// </summary>
12+
internal enum MachOFormatError
13+
{
14+
Not64BitExe, // Apphost is expected to be a 64-bit MachO executable
15+
DuplicateLinkEdit, // Only one __LINKEDIT segment is expected in the apphost
16+
DuplicateSymtab, // Only one SYMTAB is expected in the apphost
17+
SignNeedsLinkEdit, // CODE_SIGNATURE command must follow a Segment64 command named __LINKEDIT
18+
SignNeedsSymtab, // CODE_SIGNATURE command must follow the SYMTAB command
19+
LinkEditNotLast, // __LINKEDIT must be the last segment in the binary layout
20+
SymtabNotInLinkEdit, // SYMTAB must within the __LINKEDIT segment!
21+
SignNotInLinkEdit, // Signature blob must be within the __LINKEDIT segment!
22+
SignCommandNotLast, // CODE_SIGNATURE command must be the last command
23+
SignBlobNotLast, // Signature blob must be at the very end of the file
24+
SignDoesntFollowSymtab, // Signature blob must immediately follow the Symtab
25+
MemoryMapAccessFault, // Error reading the memory-mapped apphost
26+
InvalidUTF8 // UTF8 decoding failed
27+
}
28+
29+
/// <summary>
30+
/// The MachO application host executable cannot be customized because
31+
/// it was not in the expected format
32+
/// </summary>
33+
internal class AppHostMachOFormatException : BuildErrorException
34+
{
35+
public readonly MachOFormatError Error;
36+
37+
public AppHostMachOFormatException(MachOFormatError error)
38+
{
39+
Error = error;
40+
}
41+
}
42+
}
43+
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.IO;
6+
using System.IO.MemoryMappedFiles;
7+
using System.Runtime.CompilerServices;
8+
using System.Text;
9+
10+
namespace Microsoft.NET.Build.Tasks
11+
{
12+
internal static class MachOUtils
13+
{
14+
// The MachO Headers are copied from
15+
// https://opensource.apple.com/source/cctools/cctools-870/include/mach-o/loader.h
16+
//
17+
// The data fields and enumerations match the structure definitions in the above file,
18+
// and hence do not conform to C# CoreFx naming style.
19+
20+
enum Magic : uint
21+
{
22+
MH_MAGIC = 0xfeedface,
23+
MH_CIGAM = 0xcefaedfe,
24+
MH_MAGIC_64 = 0xfeedfacf,
25+
MH_CIGAM_64 = 0xcffaedfe
26+
}
27+
28+
enum FileType : uint
29+
{
30+
MH_EXECUTE = 0x2
31+
}
32+
33+
#pragma warning disable 0649
34+
struct MachHeader
35+
{
36+
public Magic magic;
37+
public int cputype;
38+
public int cpusubtype;
39+
public FileType filetype;
40+
public uint ncmds;
41+
public uint sizeofcmds;
42+
public uint flags;
43+
public uint reserved;
44+
45+
public bool Is64BitExecutable()
46+
{
47+
return magic == Magic.MH_MAGIC_64 && filetype == FileType.MH_EXECUTE;
48+
}
49+
50+
public bool IsValid()
51+
{
52+
switch (magic)
53+
{
54+
case Magic.MH_CIGAM:
55+
case Magic.MH_CIGAM_64:
56+
case Magic.MH_MAGIC:
57+
case Magic.MH_MAGIC_64:
58+
return true;
59+
60+
default:
61+
return false;
62+
}
63+
}
64+
}
65+
66+
enum Command : uint
67+
{
68+
LC_SYMTAB = 0x2,
69+
LC_SEGMENT_64 = 0x19,
70+
LC_CODE_SIGNATURE = 0x1d,
71+
}
72+
73+
struct LoadCommand
74+
{
75+
public Command cmd;
76+
public uint cmdsize;
77+
}
78+
79+
// The linkedit_data_command contains the offsets and sizes of a blob
80+
// of data in the __LINKEDIT segment (including LC_CODE_SIGNATURE).
81+
struct LinkEditDataCommand
82+
{
83+
public Command cmd;
84+
public uint cmdsize;
85+
public uint dataoff;
86+
public uint datasize;
87+
88+
public void ZeroInit()
89+
{
90+
cmd = 0;
91+
cmdsize = 0;
92+
dataoff = 0;
93+
datasize = 0;
94+
}
95+
}
96+
97+
struct SymtabCommand
98+
{
99+
public uint cmd;
100+
public uint cmdsize;
101+
public uint symoff;
102+
public uint nsyms;
103+
public uint stroff;
104+
public uint strsize;
105+
};
106+
107+
unsafe struct SegmentCommand64
108+
{
109+
public Command cmd;
110+
public uint cmdsize;
111+
public fixed byte segname[16];
112+
public ulong vmaddr;
113+
public ulong vmsize;
114+
public ulong fileoff;
115+
public ulong filesize;
116+
public int maxprot;
117+
public int initprot;
118+
public uint nsects;
119+
public uint flags;
120+
121+
public string SegName
122+
{
123+
get
124+
{
125+
fixed (byte* p = segname)
126+
{
127+
int len = 0;
128+
while (*(p + len) != 0 && len++ < 16) ;
129+
130+
try
131+
{
132+
return Encoding.UTF8.GetString(p, len);
133+
}
134+
catch(ArgumentException)
135+
{
136+
throw new AppHostMachOFormatException(MachOFormatError.InvalidUTF8);
137+
}
138+
}
139+
}
140+
}
141+
}
142+
143+
#pragma warning restore 0649
144+
145+
private static void Verify(bool condition, MachOFormatError error)
146+
{
147+
if (!condition)
148+
{
149+
throw new AppHostMachOFormatException(error);
150+
}
151+
}
152+
153+
/// <summary>
154+
/// This Method is a utility to remove the code-signature (if any)
155+
/// from a MachO AppHost binary.
156+
///
157+
/// The tool assumes the following layout of the executable:
158+
///
159+
/// * MachoHeader (64-bit, executable, not swapped integers)
160+
/// * LoadCommands
161+
/// LC_SEGMENT_64 (__PAGEZERO)
162+
/// LC_SEGMENT_64 (__TEXT)
163+
/// LC_SEGMENT_64 (__DATA)
164+
/// LC_SEGMENT_64 (__LINKEDIT)
165+
/// ...
166+
/// LC_SYMTAB
167+
/// ...
168+
/// LC_CODE_SIGNATURE (last)
169+
///
170+
/// * ... Different Segments ...
171+
///
172+
/// * The __LINKEDIT Segment (last)
173+
/// * ... Different sections ...
174+
/// * SYMTAB
175+
/// * (Some alignment bytes)
176+
/// * The Code-signature
177+
///
178+
/// In order to remove the signature, the method:
179+
/// - Removes (zeros out) the LC_CODE_SIGNATURE command
180+
/// - Adjusts the size and count of the load commands in the header
181+
/// - Truncates the size of the __LINKEDIT segment to the end of SYMTAB
182+
/// - Truncates the apphost file to the end of the __LINKEDIT segment
183+
///
184+
/// </summary>
185+
/// <param name="filePath">Path to the AppHost</param>
186+
/// <returns>
187+
/// True if
188+
/// - The input is a MachO binary, and
189+
/// - It is a signed binary, and
190+
/// - The signature was successfully removed
191+
/// False otherwise
192+
/// </returns>
193+
/// <exception cref="AppHostMachOFormatException">
194+
/// The input is a MachO file, but doesn't match the expect format of the AppHost.
195+
/// </exception>
196+
unsafe public static bool RemoveSignature(string filePath)
197+
{
198+
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite))
199+
{
200+
uint signatureSize = 0;
201+
using (var mappedFile = MemoryMappedFile.CreateFromFile(stream,
202+
mapName: null,
203+
capacity: 0,
204+
MemoryMappedFileAccess.ReadWrite,
205+
HandleInheritability.None,
206+
leaveOpen: true))
207+
{
208+
using (var accessor = mappedFile.CreateViewAccessor())
209+
{
210+
byte* file = null;
211+
RuntimeHelpers.PrepareConstrainedRegions();
212+
try
213+
{
214+
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref file);
215+
Verify(file != null, MachOFormatError.MemoryMapAccessFault);
216+
217+
MachHeader* header = (MachHeader*)file;
218+
219+
if (!header->IsValid())
220+
{
221+
// Not a MachO file.
222+
return false;
223+
}
224+
225+
Verify(header->Is64BitExecutable(), MachOFormatError.Not64BitExe);
226+
227+
file += sizeof(MachHeader);
228+
SegmentCommand64* linkEdit = null;
229+
SymtabCommand* symtab = null;
230+
LinkEditDataCommand* signature = null;
231+
232+
for (uint i = 0; i < header->ncmds; i++)
233+
{
234+
LoadCommand* command = (LoadCommand*)file;
235+
if (command->cmd == Command.LC_SEGMENT_64)
236+
{
237+
SegmentCommand64* segment = (SegmentCommand64*)file;
238+
if (segment->SegName.Equals("__LINKEDIT"))
239+
{
240+
Verify(linkEdit == null, MachOFormatError.DuplicateLinkEdit);
241+
linkEdit = segment;
242+
}
243+
}
244+
else if (command->cmd == Command.LC_SYMTAB)
245+
{
246+
Verify(symtab == null, MachOFormatError.DuplicateSymtab);
247+
symtab = (SymtabCommand*)command;
248+
}
249+
else if (command->cmd == Command.LC_CODE_SIGNATURE)
250+
{
251+
Verify(i == header->ncmds - 1, MachOFormatError.SignCommandNotLast);
252+
signature = (LinkEditDataCommand*)command;
253+
break;
254+
}
255+
256+
file += command->cmdsize;
257+
}
258+
259+
if (signature != null)
260+
{
261+
Verify(linkEdit != null, MachOFormatError.SignNeedsLinkEdit);
262+
Verify(symtab != null, MachOFormatError.SignNeedsSymtab);
263+
264+
var symtabEnd = symtab->stroff + symtab->strsize;
265+
var linkEditEnd = linkEdit->fileoff + linkEdit->filesize;
266+
var signatureEnd = signature->dataoff + signature->datasize;
267+
var fileEnd = (ulong)stream.Length;
268+
269+
Verify(linkEditEnd == fileEnd, MachOFormatError.LinkEditNotLast);
270+
Verify(signatureEnd == fileEnd, MachOFormatError.SignBlobNotLast);
271+
272+
Verify(symtab->symoff > linkEdit->fileoff, MachOFormatError.SymtabNotInLinkEdit);
273+
Verify(signature->dataoff > linkEdit->fileoff, MachOFormatError.SignNotInLinkEdit);
274+
275+
// The signature blob immediately follows the symtab blob,
276+
// except for a few bytes of padding.
277+
Verify(signature->dataoff >= symtabEnd && signature->dataoff - symtabEnd < 32, MachOFormatError.SignBlobNotLast);
278+
279+
// Remove the signature command
280+
header->ncmds--;
281+
header->sizeofcmds -= signature->cmdsize;
282+
283+
signature->ZeroInit();
284+
285+
// Remove the signature blob (note for truncation)
286+
signatureSize = (uint)(fileEnd - symtabEnd);
287+
288+
// Adjust the __LINKEDIT segment load command
289+
linkEdit->filesize -= signatureSize;
290+
291+
// codesign --remove-signature doesn't reset the vmsize.
292+
// Setting the vmsize here makes the output bin-equal with the original
293+
// unsigned apphost (and not bin-equal with a signed-unsigned-apphost).
294+
linkEdit->vmsize = linkEdit->filesize;
295+
}
296+
}
297+
finally
298+
{
299+
if(file != null)
300+
{
301+
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
302+
}
303+
}
304+
}
305+
}
306+
307+
if (signatureSize != 0)
308+
{
309+
// The signature was removed, update the file length
310+
stream.SetLength(stream.Length - signatureSize);
311+
return true;
312+
}
313+
314+
return false;
315+
}
316+
}
317+
}
318+
}
319+

0 commit comments

Comments
 (0)