|
| 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