Skip to content

Calling Win32 API CreateNamedPipe fails with ERROR_NOACCESS with PublishReadyToRun #65412

Closed
@adityapatwardhan

Description

@adityapatwardhan

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions