File: Utilities\MachOSignature.cs
Web Access
Project: ..\..\..\test\Microsoft.NET.TestFramework\Microsoft.NET.TestFramework.csproj (Microsoft.NET.TestFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Runtime.Versioning;
using System.Buffers.Binary;
using System.Diagnostics;
 
namespace Microsoft.NET.TestFramework.Utilities
{
    public static class MachOSignature
    {
        /// <summary>
        /// Calls the 'codesign' utility to verify if the file has a valid Mach-O signature.
        /// </summary>
        /// <param name="file">The Mach Object file to check.</param>
        /// <param name="log">The output helper for logging.</param>
        /// <returns>True if the file has a valid Mach-O signature, otherwise false.</returns>
#if NET
        [SupportedOSPlatform("osx")]
#endif
        public static bool HasValidMachOSignature(FileInfo file, ITestOutputHelper log)
        {
            var codesignPath = @"/usr/bin/codesign";
            return new RunExeCommand(log, codesignPath, "-v", file.FullName)
                .Execute().ExitCode == 0;
        }
 
        /// <summary>
        ///  Reads the Mach-O load commands and returns true if an LC_CODE_SIGNATURE command is found, otherwise returns false. Does not validate the signature.
        /// </summary>
        /// <param name="file">The Mach Object file to check.</param>
        /// <returns>True if the file has a Mach-O signature load command, otherwise false.</returns>
        public static bool HasMachOSignatureLoadCommand(FileInfo file)
        {
            /* Mach-O files have the following structure:
             * 32 byte header beginning with a magic number and info about the file and load commands
             * A series of load commands with the following structure:
             * - 4-byte command type
             * - 4-byte command size
             * - variable length command-specific data
             */
            const uint LC_CODE_SIGNATURE = 0x0000001D;
            using (var stream = file.OpenRead())
            {
                // Read the MachO magic number to determine endianness
                Span<byte> eightByteBuffer = stackalloc byte[8];
                stream.ReadExactly(eightByteBuffer);
                // Determine if the magic number is in the same or opposite endianness as the runtime
                bool reverseEndinanness = BitConverter.ToUInt32(eightByteBuffer.Slice(0, 4)) switch
                {
                    0xFEEDFACF => false,
                    0xCFFAEDFE => true,
                    _ => throw new InvalidOperationException("Not a 64-bit Mach-O file")
                };
                // 4-byte value at offset 16 is the number of load commands
                // 4-byte value at offset 20 is the size of the load commands
                stream.Position = 16;
                ReadUInts(stream, eightByteBuffer, out uint loadCommandsCount, out uint loadCommandsSize);
                // Mach-0 64 byte headers are 32 bytes long, and the first load command will be right after
                stream.Position = 32;
                bool hasSignature = false;
                for (int commandIndex = 0; commandIndex < loadCommandsCount; commandIndex++)
                {
                    ReadUInts(stream, eightByteBuffer, out uint commandType, out uint commandSize);
                    if (commandType == LC_CODE_SIGNATURE)
                    {
                        hasSignature = true;
                    }
                    stream.Position += commandSize - eightByteBuffer.Length;
                }
                Debug.Assert(stream.Position == loadCommandsSize + 32);
                return hasSignature;
 
                void ReadUInts(Stream stream, Span<byte> buffer, out uint val1, out uint val2)
                {
                    stream.ReadExactly(buffer);
                    val1 = BitConverter.ToUInt32(buffer.Slice(0, 4));
                    val2 = BitConverter.ToUInt32(buffer.Slice(4, 4));
                    if (reverseEndinanness)
                    {
                        val1 = BinaryPrimitives.ReverseEndianness(val1);
                        val2 = BinaryPrimitives.ReverseEndianness(val2);
                    }
                }
            }
        }
    }
}