Description
Description
PowerShell using PublishReadyToRun for build our standalone app. We create a NamedPipe by calling the Win32 API CreateNamedPipe
. For getting extra debugging information I added the following to the csproj:
<PublishReadyToRunCrossgen2ExtraArgs>--verify-type-and-field-layout</PublishReadyToRunCrossgen2ExtraArgs>
When the API is called with ReadyToRun enabled the fieldOffset is not matching. We get the error:
Fatal error. Verify_FieldOffset 'SECURITY_ATTRIBUTES.NLength' Field offset 8!=16(actual) || baseOffset 8!=8(actual)
There is no problem when we do not use ReadyToRun.
Reproduction Steps
Steps to build the project and run it:
dotnet publish -c release -f net7.0 -r win7-x64 --self-contained
.\bin\release\net7.0\win7-x64\publish\HelloWorld.exe
Please find below the contents of Program.cs and the csproj
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
using Dbg = System.Diagnostics.Debug;
class Program
{
public static void Main(string[] args)
{
if (!OperatingSystem.IsWindows()) {
return;
}
const string fullPipeName = @"\\.\pipe\PSHost.132894301173383087.33656.DefaultAppDomain.pwsh";
if (!OperatingSystem.IsWindows()) {
return;
}
const int _namedPipeBufferSizeForRemoting = 32768;
// Create optional security attributes based on provided PipeSecurity.
NamedPipeNative.SECURITY_ATTRIBUTES? securityAttributes = null;
var securityDesc = NamedPipeNative.GetServerPipeSecurity();
GCHandle? securityDescHandle = null;
if (securityDesc != null)
{
byte[] securityDescBuffer = new byte[securityDesc.BinaryLength];
securityDesc.GetBinaryForm(securityDescBuffer, 0);
securityDescHandle = GCHandle.Alloc(securityDescBuffer, GCHandleType.Pinned);
securityAttributes = NamedPipeNative.GetSecurityAttributes(securityDescHandle.Value);
}
SafePipeHandle pipeHandle = NamedPipeNative.CreateNamedPipe(
fullPipeName,
NamedPipeNative.PIPE_ACCESS_DUPLEX | NamedPipeNative.FILE_FLAG_FIRST_PIPE_INSTANCE | NamedPipeNative.FILE_FLAG_OVERLAPPED,
NamedPipeNative.PIPE_TYPE_MESSAGE | NamedPipeNative.PIPE_READMODE_MESSAGE,
1,
_namedPipeBufferSizeForRemoting,
_namedPipeBufferSizeForRemoting,
0,
securityAttributes);
int lastError = Marshal.GetLastWin32Error();
if (securityDescHandle != null)
{
securityDescHandle.Value.Free();
}
Console.WriteLine($"In CreateNamedPipe: pipeHandle.IsInvalid = {pipeHandle.IsInvalid}");
if (pipeHandle.IsInvalid)
{
throw new InvalidDataException(fullPipeName + " " + lastError);
}
try
{
var pipe = new NamedPipeServerStream(
PipeDirection.InOut,
true, // IsAsync
false, // IsConnected
pipeHandle);
if (pipe != null)
{
Console.WriteLine($"Pipe CanRead: {pipe.CanRead}");
pipe.Dispose();
Console.WriteLine("Pipe disposed");
}
}
catch (Exception e)
{
Console.WriteLine($"In CreateNamedPipe: {e.Message}");
pipeHandle.Dispose();
throw;
}
Console.WriteLine("Done!");
}
}
internal class NamedPipeNative
{
#region Pipe constants
// Pipe open modes
internal const uint PIPE_ACCESS_DUPLEX = 0x00000003;
internal const uint PIPE_ACCESS_OUTBOUND = 0x00000002;
internal const uint PIPE_ACCESS_INBOUND = 0x00000001;
// Pipe modes
internal const uint PIPE_TYPE_BYTE = 0x00000000;
internal const uint PIPE_TYPE_MESSAGE = 0x00000004;
internal const uint FILE_FLAG_OVERLAPPED = 0x40000000;
internal const uint FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000;
internal const uint PIPE_WAIT = 0x00000000;
internal const uint PIPE_NOWAIT = 0x00000001;
internal const uint PIPE_READMODE_BYTE = 0x00000000;
internal const uint PIPE_READMODE_MESSAGE = 0x00000002;
internal const uint PIPE_ACCEPT_REMOTE_CLIENTS = 0x00000000;
internal const uint PIPE_REJECT_REMOTE_CLIENTS = 0x00000008;
// Pipe errors
internal const uint ERROR_FILE_NOT_FOUND = 2;
internal const uint ERROR_BROKEN_PIPE = 109;
internal const uint ERROR_PIPE_BUSY = 231;
internal const uint ERROR_NO_DATA = 232;
internal const uint ERROR_MORE_DATA = 234;
internal const uint ERROR_PIPE_CONNECTED = 535;
internal const uint ERROR_IO_INCOMPLETE = 996;
internal const uint ERROR_IO_PENDING = 997;
// File function constants
internal const uint GENERIC_READ = 0x80000000;
internal const uint GENERIC_WRITE = 0x40000000;
internal const uint GENERIC_EXECUTE = 0x20000000;
internal const uint GENERIC_ALL = 0x10000000;
internal const uint CREATE_NEW = 1;
internal const uint CREATE_ALWAYS = 2;
internal const uint OPEN_EXISTING = 3;
internal const uint OPEN_ALWAYS = 4;
internal const uint TRUNCATE_EXISTING = 5;
internal const uint SECURITY_IMPERSONATIONLEVEL_ANONYMOUS = 0;
internal const uint SECURITY_IMPERSONATIONLEVEL_IDENTIFICATION = 1;
internal const uint SECURITY_IMPERSONATIONLEVEL_IMPERSONATION = 2;
internal const uint SECURITY_IMPERSONATIONLEVEL_DELEGATION = 3;
// Infinite timeout
internal const uint INFINITE = 0xFFFFFFFF;
#endregion
#region Data structures
[StructLayout(LayoutKind.Sequential)]
internal class SECURITY_ATTRIBUTES
{
/// <summary>
/// The size, in bytes, of this structure. Set this value to the size of the SECURITY_ATTRIBUTES structure.
/// </summary>
public int NLength;
/// <summary>
/// A pointer to a security descriptor for the object that controls the sharing of it.
/// </summary>
public IntPtr LPSecurityDescriptor = IntPtr.Zero;
/// <summary>
/// A Boolean value that specifies whether the returned handle is inherited when a new process is created.
/// </summary>
public bool InheritHandle;
/// <summary>
/// Initializes a new instance of the SECURITY_ATTRIBUTES class.
/// </summary>
public SECURITY_ATTRIBUTES()
{
this.NLength = 12;
}
}
#endregion
[DllImport("api-ms-win-core-namedpipe-l1-1-0.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern SafePipeHandle CreateNamedPipe(
string lpName,
uint dwOpenMode,
uint dwPipeMode,
uint nMaxInstances,
uint nOutBufferSize,
uint nInBufferSize,
uint nDefaultTimeOut,
SECURITY_ATTRIBUTES? securityAttributes);
internal static SECURITY_ATTRIBUTES GetSecurityAttributes(GCHandle securityDescriptorPinnedHandle, bool inheritHandle = false)
{
SECURITY_ATTRIBUTES securityAttributes = new NamedPipeNative.SECURITY_ATTRIBUTES();
securityAttributes.InheritHandle = inheritHandle;
securityAttributes.NLength = (int)Marshal.SizeOf(securityAttributes);
securityAttributes.LPSecurityDescriptor = securityDescriptorPinnedHandle.AddrOfPinnedObject();
return securityAttributes;
}
internal static CommonSecurityDescriptor? GetServerPipeSecurity()
{
if (!OperatingSystem.IsWindows()) {
return null;
}
const int _pipeAccessMaskFullControl = 0x1f019f;
// Built-in Admin SID
SecurityIdentifier adminSID = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
DiscretionaryAcl dacl = new DiscretionaryAcl(false, false, 1);
dacl.AddAccess(
AccessControlType.Allow,
adminSID,
_pipeAccessMaskFullControl,
InheritanceFlags.None,
PropagationFlags.None);
CommonSecurityDescriptor securityDesc = new CommonSecurityDescriptor(
false, false,
ControlFlags.DiscretionaryAclPresent | ControlFlags.OwnerDefaulted | ControlFlags.GroupDefaulted,
null, null, null, dacl);
if (securityDesc is null) {
throw new ArgumentNullException("securityDesc");
}
var sid = WindowsIdentity.GetCurrent().User;
if (sid is null)
{
throw new ArgumentNullException("sid");
}
// Conditionally add User SID
bool isAdminElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
if (!isAdminElevated)
{
securityDesc?.DiscretionaryAcl?.AddAccess(
AccessControlType.Allow,
sid,
_pipeAccessMaskFullControl,
InheritanceFlags.None,
PropagationFlags.None);
}
if (securityDesc is null) {
throw new ArgumentNullException("securityDesc after add access");
}
return securityDesc;
}
}
csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<UseAppHost>true</UseAppHost>
<PublishReadyToRun>true</PublishReadyToRun>
<PublishReadyToRunCrossgen2ExtraArgs>--verify-type-and-field-layout</PublishReadyToRunCrossgen2ExtraArgs>
</PropertyGroup>
</Project>
Expected behavior
No issues in fieldOffset when ready to run is enabled
Actual behavior
fieldOffset does not match
Regression?
Yes. Works fine in .NET 6.
Known Workarounds
Disable ready to run
Configuration
.NET SDK (reflecting any global.json):
Version: 7.0.100-preview.1.22110.4
Commit: 129d2465c8
Runtime Environment:
OS Name: Windows
OS Version: 10.0.22000
OS Platform: Windows
RID: win10-x64
Base Path: <...>\AppData\Local\Microsoft\dotnet\sdk\7.0.100-preview.1.22110.4\
Other information
No response