Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 80 additions & 31 deletions src/installer/managed/Microsoft.NET.HostModel/AppHost/HostWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,11 @@ void RewriteAppHost(MemoryMappedFile mappedFile, MemoryMappedViewAccessor access
RetryUtil.RetryOnIOError(() =>
{
bool isMachOImage;
using (FileStream appHostDestinationStream = new FileStream(appHostDestinationFilePath, FileMode.Create, FileAccess.ReadWrite))
// MacOS requires a new inode to be created when updating a signed file, so we'll delete the file and create a new one.
if (File.Exists(appHostDestinationFilePath))
File.Delete(appHostDestinationFilePath);

using (FileStream appHostDestinationStream = new FileStream(appHostDestinationFilePath, FileMode.CreateNew, FileAccess.ReadWrite))
{
using (FileStream appHostSourceStream = new(appHostSourceFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1))
{
Expand Down Expand Up @@ -149,9 +153,8 @@ void RewriteAppHost(MemoryMappedFile mappedFile, MemoryMappedViewAccessor access
{
if (enableMacOSCodeSign)
{
string fileName = Path.GetFileName(appHostDestinationFilePath);
MachObjectFile machObjectFile = MachObjectFile.Create(memoryMappedViewAccessor);
appHostLength = machObjectFile.CreateAdHocSignature(memoryMappedViewAccessor, fileName);
appHostLength = machObjectFile.CreateAdHocSignature(memoryMappedViewAccessor, destinationFileName);
}
else if (MachObjectFile.RemoveCodeSignatureIfPresent(memoryMappedViewAccessor, out long? length))
{
Expand All @@ -169,24 +172,7 @@ void RewriteAppHost(MemoryMappedFile mappedFile, MemoryMappedViewAccessor access
}
}
});

if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var filePermissionOctal = Convert.ToInt32("755", 8); // -rwxr-xr-x
const int EINTR = 4;
int chmodReturnCode = 0;

do
{
chmodReturnCode = chmod(appHostDestinationFilePath, filePermissionOctal);
}
while (chmodReturnCode == -1 && Marshal.GetLastWin32Error() == EINTR);

if (chmodReturnCode == -1)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not set file permission {Convert.ToString(filePermissionOctal, 8)} for {appHostDestinationFilePath}.");
}
}
Chmod755(appHostDestinationFilePath);
}
catch (Exception ex)
{
Expand All @@ -209,9 +195,11 @@ void RewriteAppHost(MemoryMappedFile mappedFile, MemoryMappedViewAccessor access
/// </summary>
/// <param name="appHostPath">The path of Apphost template, which has the place holder</param>
/// <param name="bundleHeaderOffset">The offset to the location of bundle header</param>
/// <param name="macosCodesign">Whether to ad-hoc sign the bundle as a Mach-O executable</param>
public static void SetAsBundle(
string appHostPath,
long bundleHeaderOffset)
long bundleHeaderOffset,
bool macosCodesign = false)
{
byte[] bundleHeaderPlaceholder = {
// 8 bytes represent the bundle header-offset
Expand All @@ -226,17 +214,58 @@ public static void SetAsBundle(

// Re-write the destination apphost with the proper contents.
RetryUtil.RetryOnIOError(() =>
BinaryUtils.SearchAndReplace(appHostPath,
bundleHeaderPlaceholder,
BitConverter.GetBytes(bundleHeaderOffset),
pad0s: false));
{
string tmpFile = null;
try
{
// MacOS keeps a cache of file signatures. To avoid using the cached value,
// we need to create a new inode with the contents of the old file, sign it,
// and copy it the original file path.
tmpFile = Path.GetTempFileName();
using (FileStream newBundleStream = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite))
{
using (FileStream oldBundleStream = new FileStream(appHostPath, FileMode.Open, FileAccess.Read))
{
oldBundleStream.CopyTo(newBundleStream);
}

RetryUtil.RetryOnIOError(() =>
MachOUtils.AdjustHeadersForBundle(appHostPath));
long bundleSize = newBundleStream.Length;
long mmapFileSize = macosCodesign
? bundleSize + MachObjectFile.GetSignatureSizeEstimate((uint)bundleSize, Path.GetFileName(appHostPath))
: bundleSize;
using (MemoryMappedFile memoryMappedFile = MemoryMappedFile.CreateFromFile(newBundleStream, null, mmapFileSize, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, leaveOpen: true))
using (MemoryMappedViewAccessor accessor = memoryMappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.ReadWrite))
{
BinaryUtils.SearchAndReplace(accessor,
bundleHeaderPlaceholder,
BitConverter.GetBytes(bundleHeaderOffset),
pad0s: false);

// Memory-mapped write does not updating last write time
RetryUtil.RetryOnIOError(() =>
File.SetLastWriteTimeUtc(appHostPath, DateTime.UtcNow));
if (MachObjectFile.IsMachOImage(accessor))
{
var machObjectFile = MachObjectFile.Create(accessor);
if (machObjectFile.HasSignature)
throw new AppHostMachOFormatException(MachOFormatError.SignNotRemoved);

bool wasBundled = machObjectFile.TryAdjustHeadersForBundle((ulong)bundleSize, accessor);
if (!wasBundled)
throw new InvalidOperationException("The single-file bundle was unable to be created. This is likely because the bundled content is too large.");

if (macosCodesign)
bundleSize = machObjectFile.CreateAdHocSignature(accessor, Path.GetFileName(appHostPath));
}
}
newBundleStream.SetLength(bundleSize);
}
File.Copy(tmpFile, appHostPath, overwrite: true);
Chmod755(appHostPath);
}
finally
{
if (tmpFile is not null)
File.Delete(tmpFile);
}
});
}

/// <summary>
Expand Down Expand Up @@ -302,6 +331,26 @@ private static byte[] GetSearchOptionBytes(DotNetSearchOptions searchOptions)
return searchOptionsBytes;
}

private static void Chmod755(string pathName)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return;
var filePermissionOctal = Convert.ToInt32("755", 8); // -rwxr-xr-x
const int EINTR = 4;
int chmodReturnCode;

do
{
chmodReturnCode = chmod(pathName, filePermissionOctal);
}
while (chmodReturnCode == -1 && Marshal.GetLastWin32Error() == EINTR);

if (chmodReturnCode == -1)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not set file permission {Convert.ToString(filePermissionOctal, 8)} for {pathName}.");
}
}

[LibraryImport("libc", SetLastError = true)]
private static partial int chmod([MarshalAs(UnmanagedType.LPStr)] string pathname, int mode);
}
Expand Down
168 changes: 0 additions & 168 deletions src/installer/managed/Microsoft.NET.HostModel/AppHost/MachOUtils.cs

This file was deleted.

14 changes: 1 addition & 13 deletions src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.DotNet.CoreSetup;
using Microsoft.NET.HostModel.AppHost;
using Microsoft.NET.HostModel.MachO;

Expand Down Expand Up @@ -345,18 +344,7 @@ public string GenerateBundle(IReadOnlyList<FileSpec> fileSpecs)
_tracer.Log($"Bundle: Path={bundlePath}, Size={bundle.Length}");
}

HostWriter.SetAsBundle(bundlePath, headerOffset);

// Sign the bundle if requested
// TODO: use managed code signing
if (_macosCodesign && RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && Codesign.IsAvailable)
{
var (exitCode, stdErr) = Codesign.Run("-s -", bundlePath);
if (exitCode != 0)
{
throw new InvalidOperationException($"Failed to codesign '{bundlePath}': {stdErr}");
}
}
HostWriter.SetAsBundle(bundlePath, headerOffset, _macosCodesign);

return bundlePath;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ internal struct Segment64LoadCommand
public ulong GetFileSize(MachHeader header) => header.ConvertValue(_fileSize);
public void SetFileSize(ulong value, MachHeader header) => _fileSize = header.ConvertValue(value);
public void SetVMSize(ulong value, MachHeader header) => _size = header.ConvertValue(value);
public ulong GetVMSize(MachHeader header) => header.ConvertValue(_size);
public uint GetSectionsCount(MachHeader header) => header.ConvertValue(_numberOfSections);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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.InteropServices;

namespace Microsoft.NET.HostModel.MachO;

/// <summary>
/// A load command with info about the location of the symbol table and string table.
/// See https://github.com/apple-oss-distributions/cctools/blob/7a5450708479bbff61527d5e0c32a3f7b7e4c1d0/include/mach-o/loader.h#L908 for reference.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct SymbolTableCommand
{
private readonly MachLoadCommandType _command;
private readonly uint _commandSize;
public uint _symbolTableOffset;
public uint _symbolsCount;
private uint _stringTableOffset;
private uint _stringTableSize; // in bytes

public bool IsDefault => this.Equals(default(SymbolTableCommand));

public uint GetSymbolTableOffset(MachHeader header) => header.ConvertValue(_symbolTableOffset);
public void SetSymbolTableOffset(uint value, MachHeader header) => _symbolTableOffset = header.ConvertValue(value);
public uint GetSymbolsCount(MachHeader header) => header.ConvertValue(_symbolsCount);
public void SetSymbolsCount(uint value, MachHeader header) => _symbolsCount = header.ConvertValue(value);
public uint GetStringTableOffset(MachHeader header) => header.ConvertValue(_stringTableOffset);
public void SetStringTableOffset(uint value, MachHeader header) => _stringTableOffset = header.ConvertValue(value);
public uint GetStringTableSize(MachHeader header) => header.ConvertValue(_stringTableSize);
public void SetStringTableSize(uint value, MachHeader header) => _stringTableSize = header.ConvertValue(value);
}
Loading