Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.ComponentModel;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.NET.HostModel.MachO;

Expand Down Expand Up @@ -162,7 +160,7 @@ void RewriteAppHost(MemoryMappedFile mappedFile, MemoryMappedViewAccessor access
}
}
}
using (FileStream appHostDestinationStream = new FileStream(appHostDestinationFilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None, bufferSize: 1))
using (FileStream appHostDestinationStream = HostModelUtils.CreateFileStreamForHost(appHostDestinationFilePath, FileAccess.ReadWrite, FileShare.None, bufferSize: 1))
using (MemoryMappedViewAccessor appHostAccessor = appHostDestinationMap.CreateViewAccessor(0, appHostDestinationLength, MemoryMappedFileAccess.Read))
{
// Write the final content to the destination file, only up to the total length of the host, not the entire mapped file.
Expand All @@ -179,14 +177,8 @@ void RewriteAppHost(MemoryMappedFile mappedFile, MemoryMappedViewAccessor access
}
}
});
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// chmod +755
File.SetUnixFileMode(appHostDestinationFilePath,
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
UnixFileMode.GroupRead | UnixFileMode.GroupExecute |
UnixFileMode.OtherRead | UnixFileMode.OtherExecute);
}

HostModelUtils.SetPermissionsForHost(appHostDestinationFilePath);
}
catch (Exception ex)
{
Expand Down
22 changes: 10 additions & 12 deletions src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -399,26 +399,24 @@ public string GenerateBundle(BundleContents bundleContents)
}
}

// MacOS keeps a cache of file signatures, so we must create a new inode to ensure the file signature is properly updated.
if (_macosCodesign && File.Exists(bundlePath))
// On non-Windows, delete any existing bundle so the output is written to a new inode.
// FileStreamOptions.UnixCreateMode only applies when a file is created, not when an
// existing file is truncated in place, so without this an existing non-executable
// bundle would keep its permissions and require a chmod (which can fail on some
// filesystems, e.g. bind-mounted volumes in rootless containers). On macOS this also
// ensures the kernel's code signature cache is not reused for the new contents.
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && File.Exists(bundlePath))
{
_tracer.Log($"Removing existing bundle file to clear signature cache: {bundlePath}");
_tracer.Log($"Removing existing bundle file: {bundlePath}");
File.Delete(bundlePath);
}
using (FileStream bundleOutputStream = File.Open(bundlePath, FileMode.Create, FileAccess.Write, FileShare.None))
using (FileStream bundleOutputStream = HostModelUtils.CreateFileStreamForHost(bundlePath, FileAccess.Write, FileShare.None))
Comment thread
elinor-fung marked this conversation as resolved.
{
BinaryUtils.WriteToStream(accessor, bundleOutputStream, (long)endOfBundle);
}
}
}
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// chmod +755
File.SetUnixFileMode(bundlePath,
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
UnixFileMode.GroupRead | UnixFileMode.GroupExecute |
UnixFileMode.OtherRead | UnixFileMode.OtherExecute);
}
HostModelUtils.SetPermissionsForHost(bundlePath);
Comment thread
elinor-fung marked this conversation as resolved.
return bundlePath;
}

Expand Down
67 changes: 63 additions & 4 deletions src/installer/managed/Microsoft.NET.HostModel/HostModelUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,77 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
#if NETFRAMEWORK
using Microsoft.IO;
#else
using System.IO;
#endif
using System.Runtime.InteropServices;
#if NETFRAMEWORK
using FileInfo = Microsoft.IO.FileInfo;
#endif


namespace Microsoft.NET.HostModel
{
internal static class HostModelUtils
{
#if NET
// -rwxr-xr-x: the permissions an apphost or single-file bundle should have.
private const UnixFileMode AppHostFileMode =
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
UnixFileMode.GroupRead | UnixFileMode.GroupExecute |
UnixFileMode.OtherRead | UnixFileMode.OtherExecute;
#endif

/// <summary>
/// Creates a <see cref="FileStream"/> for writing an apphost or bundle, requesting the desired
/// Unix permissions at creation time on platforms that support them.
/// When <paramref name="bufferSize"/> is <see langword="null"/>, the default buffer size is used.
/// </summary>
public static FileStream CreateFileStreamForHost(
string path,
FileAccess access,
FileShare share,
int? bufferSize = null)
{
#if NET
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
FileStreamOptions options = new()
{
Mode = FileMode.Create,
Access = access,
Share = share,
UnixCreateMode = AppHostFileMode,
};

if (bufferSize is int size)
{
options.BufferSize = size;
}

return new FileStream(path, options);
}
#endif
return bufferSize.HasValue
? new FileStream(path, FileMode.Create, access, share, bufferSize.Value)
: new FileStream(path, FileMode.Create, access, share);
}

/// <summary>
/// Ensures a host file has the required permissions.
/// </summary>
public static void SetPermissionsForHost(string filePath)
Comment thread
elinor-fung marked this conversation as resolved.
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return;

#if NET
// File already has required permissions
if ((File.GetUnixFileMode(filePath) & AppHostFileMode) == AppHostFileMode)
return;

File.SetUnixFileMode(filePath, AppHostFileMode);
#endif
}

private const string CodesignPath = @"/usr/bin/codesign";

public static bool IsCodesignAvailable() => File.Exists(CodesignPath);
Expand Down
69 changes: 0 additions & 69 deletions src/installer/managed/Microsoft.NET.HostModel/Utils/UnixUtils.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,38 @@ public void ExecutableImage()
.Be(expectedPermissions);
}

[Fact]
[PlatformSpecific(TestPlatforms.AnyUnix)]
public void ExecutableImage_ExistingNonExecutableFile()
{
using TestArtifact artifact = CreateTestDirectory();
string sourceAppHostMock = PrepareAppHostMockFile(artifact.Location);
string destinationFilePath = Path.Combine(artifact.Location, "DestinationAppHost.exe.mock");
string appBinaryFilePath = "Test/App/Binary/Path.dll";

// A non-executable file already exists at the destination (-rw-r--r--).
File.WriteAllText(destinationFilePath, "pre-existing content");
File.SetUnixFileMode(destinationFilePath,
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.OtherRead);

// -rwxr-xr-x
const UnixFileMode expectedPermissions = UnixFileMode.UserRead | UnixFileMode.UserExecute | UnixFileMode.UserWrite |
UnixFileMode.GroupRead | UnixFileMode.GroupExecute |
UnixFileMode.OtherRead | UnixFileMode.OtherExecute;

HostWriter.CreateAppHost(
sourceAppHostMock,
destinationFilePath,
appBinaryFilePath,
windowsGraphicalUserInterface: true);

// assert that the generated app has executable permissions
// despite the non-executable permissions on the pre-existing destination file.
File.GetUnixFileMode(destinationFilePath)
.Should()
.Be(expectedPermissions);
}

[Theory]
[PlatformSpecific(TestPlatforms.OSX)]
[InlineData("")]
Expand Down
Loading