FileStream over write-only overlapped named pipe handle throws UnauthorizedAccessException on .NET 11 (regression from .NET 10) #131503

Description

@AndyGerlicher

Description

On .NET 11, constructing a FileStream over the write side of a write-only, overlapped named pipe throws UnauthorizedAccessException. The same code works on .NET 10 and earlier.

SafeFileHandleh=CreateNamedPipeW(name,PIPE_ACCESS_OUTBOUND|FILE_FLAG_OVERLAPPED, ...);usingvarfs=newFileStream(h,FileAccess.Write,bufferSize:4096,isAsync:true);// throws on .NET 11

The OSFileStreamStrategy constructor evaluates handle.CanSeek, which goes SafeFileHandle.TypeGetFileTypeCore()GetPipeOrSocketType(). That probe requires read access on the handle, and a PIPE_ACCESS_OUTBOUND server handle is write-only, so it fails with ERROR_ACCESS_DENIED (5) and the constructor throws.

Note that nothing is ever read from this handle — the stream is opened FileAccess.Write only. The type/seek probe looks like unnecessary work for a write-only handle, and it is newly fatal.

Reproduction Steps

Minimal standalone repro (multi-targeted net10.0;net11.0, no external dependencies):

https://github.com/AndyGerlicher/net11-pipe-repro

git clone https://github.com/AndyGerlicher/net11-pipe-repro
cd net11-pipe-repro
dotnet run -f net10.0
dotnet run -f net11.0

Expected behavior

new FileStream(handle, FileAccess.Write, bufferSize, isAsync: true) succeeds, as it does on .NET 10:

[PASS] PIPE_ACCESS_OUTBOUND + FileStream (what BuildXL does today)
[PASS] PIPE_ACCESS_DUPLEX + FileStream (workaround candidate 1)
[PASS] PIPE_ACCESS_OUTBOUND + NamedPipeServerStream (workaround candidate 2)

Actual behavior

On .NET 11 the first case fails:

Runtime: .NET 11.0.0-preview.6.26359.118
[FAIL] PIPE_ACCESS_OUTBOUND + FileStream (what BuildXL does today)
System.UnauthorizedAccessException: Access to the path is denied.
at Microsoft.Win32.SafeHandles.SafeFileHandle.GetPipeOrSocketType()
at Microsoft.Win32.SafeHandles.SafeFileHandle.GetFileTypeCore()
at Microsoft.Win32.SafeHandles.SafeFileHandle.get_Type()
at Microsoft.Win32.SafeHandles.SafeFileHandle.get_CanSeek()
at System.IO.Strategies.OSFileStreamStrategy..ctor(SafeFileHandle handle, FileAccess access)
at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(SafeFileHandle handle, FileAccess access, Boolean isAsync)
at System.IO.FileStream..ctor(SafeFileHandle handle, FileAccess access, Int32 bufferSize, Boolean isAsync)
[PASS] PIPE_ACCESS_DUPLEX + FileStream (workaround candidate 1)
[PASS] PIPE_ACCESS_OUTBOUND + NamedPipeServerStream (workaround candidate 2)

Regression?

Yes — regression from .NET 10. Verified working on .NET 10.0.10, failing on .NET 11.0.0-preview.6.26359.118.

There does not appear to be an opt-out: setting System.IO.UseNet5CompatFileStream=true in runtimeconfig.json does not avoid the probe on .NET 11.

Known Workarounds

Both of these pass on .NET 10 and .NET 11 (both are exercised by the repro):

  1. Create the server handle with PIPE_ACCESS_DUPLEX instead of PIPE_ACCESS_OUTBOUND.
  2. Wrap the handle in NamedPipeServerStream instead of FileStream.

Configuration

  • .NET 11.0.0-preview.6.26359.118 (SDK 11.0.100-preview.6.26359.118) — fails
  • .NET 10.0.10 — works
  • Windows, x64
  • Not specific to a particular configuration as far as we can tell; the handle access mode is what matters.

Other information

This is not hypothetical — it breaks the BuildXL process sandbox, which CloudBuild/QuickBuild uses to execute every build target.

BuildXL.Processes.Internal.Pipes.CreateInheritablePipe creates the sandboxed child's stdin pipe with PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, and BuildXL.Processes.Internal.DetouredProcess.Start then wraps that handle:

if(writeHandle!=null){FileStreamstream=newFileStream(writeHandle,FileAccess.Write,m_bufferSize,isAsync:true);m_standardInputWriter=newStreamWriter(stream,m_standardInputEncoding,m_bufferSize){AutoFlush=false};}

That stdin pipe is created whenever the caller asks for stdout/stderr capture, which is the normal configuration.

Worth calling out how badly this presents in practice. SandboxedProcessFactory.ProcessStart does try { proc.Start(); } catch { proc?.Dispose(); throw; }, and DetouredProcess.Dispose() blocks indefinitely on this path — so the UnauthorizedAccessException never propagates and the already-created child process is never killed. The observable result in a distributed build is that every target launches its child process, emits zero bytes of stdout/stderr, records zero sandbox file-access events, and never completes; the build is eventually killed by a max-runtime watchdog with no error message anywhere. It took a full process dump to recover the exception above.

So while the deadlock is BuildXL's bug to fix, the trigger is this runtime behavior change, and the failure mode makes it very expensive to diagnose for anyone who hits it.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
     blocks
    (function() {
    function addCopyButtons() {
    document.querySelectorAll('pre code').forEach(function(codeBlock) {
    if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
    codeBlock.parentElement.setAttribute('data-copy-added', 'true');
    var btn = document.createElement('button');
    btn.textContent = 'Copy';
    btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
    btn.onmouseover = function() { this.style.opacity = '1'; };
    btn.onmouseout = function() { this.style.opacity = '0.7'; };
    btn.onclick = function() {
    navigator.clipboard.writeText(codeBlock.textContent).then(function() {
    btn.textContent = 'Copied!';
    setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
    });
    };
    codeBlock.parentElement.style.position = 'relative';
    codeBlock.parentElement.appendChild(btn);
    });
    }
    addCopyButtons();
    // Re-run on dynamic content
    var observer = new MutationObserver(addCopyButtons);
    observer.observe(document.body, { childList: true, subtree: true });
    })();
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    
    Skip to content

    FileStream over write-only overlapped named pipe handle throws UnauthorizedAccessException on .NET 11 (regression from .NET 10) #131503

    Description

    @AndyGerlicher

    Description

    On .NET 11, constructing a FileStream over the write side of a write-only, overlapped named pipe throws UnauthorizedAccessException. The same code works on .NET 10 and earlier.

    SafeFileHandleh=CreateNamedPipeW(name,PIPE_ACCESS_OUTBOUND|FILE_FLAG_OVERLAPPED, ...);usingvarfs=newFileStream(h,FileAccess.Write,bufferSize:4096,isAsync:true);// throws on .NET 11

    The OSFileStreamStrategy constructor evaluates handle.CanSeek, which goes SafeFileHandle.TypeGetFileTypeCore()GetPipeOrSocketType(). That probe requires read access on the handle, and a PIPE_ACCESS_OUTBOUND server handle is write-only, so it fails with ERROR_ACCESS_DENIED (5) and the constructor throws.

    Note that nothing is ever read from this handle — the stream is opened FileAccess.Write only. The type/seek probe looks like unnecessary work for a write-only handle, and it is newly fatal.

    Reproduction Steps

    Minimal standalone repro (multi-targeted net10.0;net11.0, no external dependencies):

    https://github.com/AndyGerlicher/net11-pipe-repro

    git clone https://github.com/AndyGerlicher/net11-pipe-repro
    cd net11-pipe-repro
    dotnet run -f net10.0
    dotnet run -f net11.0
    

    Expected behavior

    new FileStream(handle, FileAccess.Write, bufferSize, isAsync: true) succeeds, as it does on .NET 10:

    [PASS] PIPE_ACCESS_OUTBOUND + FileStream (what BuildXL does today)
    [PASS] PIPE_ACCESS_DUPLEX + FileStream (workaround candidate 1)
    [PASS] PIPE_ACCESS_OUTBOUND + NamedPipeServerStream (workaround candidate 2)
    

    Actual behavior

    On .NET 11 the first case fails:

    Runtime: .NET 11.0.0-preview.6.26359.118
    [FAIL] PIPE_ACCESS_OUTBOUND + FileStream (what BuildXL does today)
    System.UnauthorizedAccessException: Access to the path is denied.
    at Microsoft.Win32.SafeHandles.SafeFileHandle.GetPipeOrSocketType()
    at Microsoft.Win32.SafeHandles.SafeFileHandle.GetFileTypeCore()
    at Microsoft.Win32.SafeHandles.SafeFileHandle.get_Type()
    at Microsoft.Win32.SafeHandles.SafeFileHandle.get_CanSeek()
    at System.IO.Strategies.OSFileStreamStrategy..ctor(SafeFileHandle handle, FileAccess access)
    at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(SafeFileHandle handle, FileAccess access, Boolean isAsync)
    at System.IO.FileStream..ctor(SafeFileHandle handle, FileAccess access, Int32 bufferSize, Boolean isAsync)
    [PASS] PIPE_ACCESS_DUPLEX + FileStream (workaround candidate 1)
    [PASS] PIPE_ACCESS_OUTBOUND + NamedPipeServerStream (workaround candidate 2)
    

    Regression?

    Yes — regression from .NET 10. Verified working on .NET 10.0.10, failing on .NET 11.0.0-preview.6.26359.118.

    There does not appear to be an opt-out: setting System.IO.UseNet5CompatFileStream=true in runtimeconfig.json does not avoid the probe on .NET 11.

    Known Workarounds

    Both of these pass on .NET 10 and .NET 11 (both are exercised by the repro):

    1. Create the server handle with PIPE_ACCESS_DUPLEX instead of PIPE_ACCESS_OUTBOUND.
    2. Wrap the handle in NamedPipeServerStream instead of FileStream.

    Configuration

    • .NET 11.0.0-preview.6.26359.118 (SDK 11.0.100-preview.6.26359.118) — fails
    • .NET 10.0.10 — works
    • Windows, x64
    • Not specific to a particular configuration as far as we can tell; the handle access mode is what matters.

    Other information

    This is not hypothetical — it breaks the BuildXL process sandbox, which CloudBuild/QuickBuild uses to execute every build target.

    BuildXL.Processes.Internal.Pipes.CreateInheritablePipe creates the sandboxed child's stdin pipe with PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, and BuildXL.Processes.Internal.DetouredProcess.Start then wraps that handle:

    if(writeHandle!=null){FileStreamstream=newFileStream(writeHandle,FileAccess.Write,m_bufferSize,isAsync:true);m_standardInputWriter=newStreamWriter(stream,m_standardInputEncoding,m_bufferSize){AutoFlush=false};}

    That stdin pipe is created whenever the caller asks for stdout/stderr capture, which is the normal configuration.

    Worth calling out how badly this presents in practice. SandboxedProcessFactory.ProcessStart does try { proc.Start(); } catch { proc?.Dispose(); throw; }, and DetouredProcess.Dispose() blocks indefinitely on this path — so the UnauthorizedAccessException never propagates and the already-created child process is never killed. The observable result in a distributed build is that every target launches its child process, emits zero bytes of stdout/stderr, records zero sandbox file-access events, and never completes; the build is eventually killed by a max-runtime watchdog with no error message anywhere. It took a full process dump to recover the exception above.

    So while the deadlock is BuildXL's bug to fix, the trigger is this runtime behavior change, and the failure mode makes it very expensive to diagnose for anyone who hits it.

    Metadata

    Metadata

    Assignees

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      FileStream over write-only overlapped named pipe handle throws UnauthorizedAccessException on .NET 11 (regression from .NET 10) #131503

      Description

      @AndyGerlicher

      Description

      On .NET 11, constructing a FileStream over the write side of a write-only, overlapped named pipe throws UnauthorizedAccessException. The same code works on .NET 10 and earlier.

      SafeFileHandleh=CreateNamedPipeW(name,PIPE_ACCESS_OUTBOUND|FILE_FLAG_OVERLAPPED, ...);usingvarfs=newFileStream(h,FileAccess.Write,bufferSize:4096,isAsync:true);// throws on .NET 11

      The OSFileStreamStrategy constructor evaluates handle.CanSeek, which goes SafeFileHandle.TypeGetFileTypeCore()GetPipeOrSocketType(). That probe requires read access on the handle, and a PIPE_ACCESS_OUTBOUND server handle is write-only, so it fails with ERROR_ACCESS_DENIED (5) and the constructor throws.

      Note that nothing is ever read from this handle — the stream is opened FileAccess.Write only. The type/seek probe looks like unnecessary work for a write-only handle, and it is newly fatal.

      Reproduction Steps

      Minimal standalone repro (multi-targeted net10.0;net11.0, no external dependencies):

      https://github.com/AndyGerlicher/net11-pipe-repro

      git clone https://github.com/AndyGerlicher/net11-pipe-repro
      cd net11-pipe-repro
      dotnet run -f net10.0
      dotnet run -f net11.0
      

      Expected behavior

      new FileStream(handle, FileAccess.Write, bufferSize, isAsync: true) succeeds, as it does on .NET 10:

      [PASS] PIPE_ACCESS_OUTBOUND + FileStream (what BuildXL does today)
      [PASS] PIPE_ACCESS_DUPLEX + FileStream (workaround candidate 1)
      [PASS] PIPE_ACCESS_OUTBOUND + NamedPipeServerStream (workaround candidate 2)
      

      Actual behavior

      On .NET 11 the first case fails:

      Runtime: .NET 11.0.0-preview.6.26359.118
      [FAIL] PIPE_ACCESS_OUTBOUND + FileStream (what BuildXL does today)
      System.UnauthorizedAccessException: Access to the path is denied.
      at Microsoft.Win32.SafeHandles.SafeFileHandle.GetPipeOrSocketType()
      at Microsoft.Win32.SafeHandles.SafeFileHandle.GetFileTypeCore()
      at Microsoft.Win32.SafeHandles.SafeFileHandle.get_Type()
      at Microsoft.Win32.SafeHandles.SafeFileHandle.get_CanSeek()
      at System.IO.Strategies.OSFileStreamStrategy..ctor(SafeFileHandle handle, FileAccess access)
      at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(SafeFileHandle handle, FileAccess access, Boolean isAsync)
      at System.IO.FileStream..ctor(SafeFileHandle handle, FileAccess access, Int32 bufferSize, Boolean isAsync)
      [PASS] PIPE_ACCESS_DUPLEX + FileStream (workaround candidate 1)
      [PASS] PIPE_ACCESS_OUTBOUND + NamedPipeServerStream (workaround candidate 2)
      

      Regression?

      Yes — regression from .NET 10. Verified working on .NET 10.0.10, failing on .NET 11.0.0-preview.6.26359.118.

      There does not appear to be an opt-out: setting System.IO.UseNet5CompatFileStream=true in runtimeconfig.json does not avoid the probe on .NET 11.

      Known Workarounds

      Both of these pass on .NET 10 and .NET 11 (both are exercised by the repro):

      1. Create the server handle with PIPE_ACCESS_DUPLEX instead of PIPE_ACCESS_OUTBOUND.
      2. Wrap the handle in NamedPipeServerStream instead of FileStream.

      Configuration

      • .NET 11.0.0-preview.6.26359.118 (SDK 11.0.100-preview.6.26359.118) — fails
      • .NET 10.0.10 — works
      • Windows, x64
      • Not specific to a particular configuration as far as we can tell; the handle access mode is what matters.

      Other information

      This is not hypothetical — it breaks the BuildXL process sandbox, which CloudBuild/QuickBuild uses to execute every build target.

      BuildXL.Processes.Internal.Pipes.CreateInheritablePipe creates the sandboxed child's stdin pipe with PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, and BuildXL.Processes.Internal.DetouredProcess.Start then wraps that handle:

      if(writeHandle!=null){FileStreamstream=newFileStream(writeHandle,FileAccess.Write,m_bufferSize,isAsync:true);m_standardInputWriter=newStreamWriter(stream,m_standardInputEncoding,m_bufferSize){AutoFlush=false};}

      That stdin pipe is created whenever the caller asks for stdout/stderr capture, which is the normal configuration.

      Worth calling out how badly this presents in practice. SandboxedProcessFactory.ProcessStart does try { proc.Start(); } catch { proc?.Dispose(); throw; }, and DetouredProcess.Dispose() blocks indefinitely on this path — so the UnauthorizedAccessException never propagates and the already-created child process is never killed. The observable result in a distributed build is that every target launches its child process, emits zero bytes of stdout/stderr, records zero sandbox file-access events, and never completes; the build is eventually killed by a max-runtime watchdog with no error message anywhere. It took a full process dump to recover the exception above.

      So while the deadlock is BuildXL's bug to fix, the trigger is this runtime behavior change, and the failure mode makes it very expensive to diagnose for anyone who hits it.

      Metadata

      Metadata

      Assignees

      Type

      No type

      Projects

      No projects

        Milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        FileStream over write-only overlapped named pipe handle throws UnauthorizedAccessException on .NET 11 (regression from .NET 10) #131503

        Description

        @AndyGerlicher

        Description

        On .NET 11, constructing a FileStream over the write side of a write-only, overlapped named pipe throws UnauthorizedAccessException. The same code works on .NET 10 and earlier.

        SafeFileHandleh=CreateNamedPipeW(name,PIPE_ACCESS_OUTBOUND|FILE_FLAG_OVERLAPPED, ...);usingvarfs=newFileStream(h,FileAccess.Write,bufferSize:4096,isAsync:true);// throws on .NET 11

        The OSFileStreamStrategy constructor evaluates handle.CanSeek, which goes SafeFileHandle.TypeGetFileTypeCore()GetPipeOrSocketType(). That probe requires read access on the handle, and a PIPE_ACCESS_OUTBOUND server handle is write-only, so it fails with ERROR_ACCESS_DENIED (5) and the constructor throws.

        Note that nothing is ever read from this handle — the stream is opened FileAccess.Write only. The type/seek probe looks like unnecessary work for a write-only handle, and it is newly fatal.

        Reproduction Steps

        Minimal standalone repro (multi-targeted net10.0;net11.0, no external dependencies):

        https://github.com/AndyGerlicher/net11-pipe-repro

        git clone https://github.com/AndyGerlicher/net11-pipe-repro
        cd net11-pipe-repro
        dotnet run -f net10.0
        dotnet run -f net11.0
        

        Expected behavior

        new FileStream(handle, FileAccess.Write, bufferSize, isAsync: true) succeeds, as it does on .NET 10:

        [PASS] PIPE_ACCESS_OUTBOUND + FileStream (what BuildXL does today)
        [PASS] PIPE_ACCESS_DUPLEX + FileStream (workaround candidate 1)
        [PASS] PIPE_ACCESS_OUTBOUND + NamedPipeServerStream (workaround candidate 2)
        

        Actual behavior

        On .NET 11 the first case fails:

        Runtime: .NET 11.0.0-preview.6.26359.118
        [FAIL] PIPE_ACCESS_OUTBOUND + FileStream (what BuildXL does today)
        System.UnauthorizedAccessException: Access to the path is denied.
        at Microsoft.Win32.SafeHandles.SafeFileHandle.GetPipeOrSocketType()
        at Microsoft.Win32.SafeHandles.SafeFileHandle.GetFileTypeCore()
        at Microsoft.Win32.SafeHandles.SafeFileHandle.get_Type()
        at Microsoft.Win32.SafeHandles.SafeFileHandle.get_CanSeek()
        at System.IO.Strategies.OSFileStreamStrategy..ctor(SafeFileHandle handle, FileAccess access)
        at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(SafeFileHandle handle, FileAccess access, Boolean isAsync)
        at System.IO.FileStream..ctor(SafeFileHandle handle, FileAccess access, Int32 bufferSize, Boolean isAsync)
        [PASS] PIPE_ACCESS_DUPLEX + FileStream (workaround candidate 1)
        [PASS] PIPE_ACCESS_OUTBOUND + NamedPipeServerStream (workaround candidate 2)
        

        Regression?

        Yes — regression from .NET 10. Verified working on .NET 10.0.10, failing on .NET 11.0.0-preview.6.26359.118.

        There does not appear to be an opt-out: setting System.IO.UseNet5CompatFileStream=true in runtimeconfig.json does not avoid the probe on .NET 11.

        Known Workarounds

        Both of these pass on .NET 10 and .NET 11 (both are exercised by the repro):

        1. Create the server handle with PIPE_ACCESS_DUPLEX instead of PIPE_ACCESS_OUTBOUND.
        2. Wrap the handle in NamedPipeServerStream instead of FileStream.

        Configuration

        • .NET 11.0.0-preview.6.26359.118 (SDK 11.0.100-preview.6.26359.118) — fails
        • .NET 10.0.10 — works
        • Windows, x64
        • Not specific to a particular configuration as far as we can tell; the handle access mode is what matters.

        Other information

        This is not hypothetical — it breaks the BuildXL process sandbox, which CloudBuild/QuickBuild uses to execute every build target.

        BuildXL.Processes.Internal.Pipes.CreateInheritablePipe creates the sandboxed child's stdin pipe with PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, and BuildXL.Processes.Internal.DetouredProcess.Start then wraps that handle:

        if(writeHandle!=null){FileStreamstream=newFileStream(writeHandle,FileAccess.Write,m_bufferSize,isAsync:true);m_standardInputWriter=newStreamWriter(stream,m_standardInputEncoding,m_bufferSize){AutoFlush=false};}

        That stdin pipe is created whenever the caller asks for stdout/stderr capture, which is the normal configuration.

        Worth calling out how badly this presents in practice. SandboxedProcessFactory.ProcessStart does try { proc.Start(); } catch { proc?.Dispose(); throw; }, and DetouredProcess.Dispose() blocks indefinitely on this path — so the UnauthorizedAccessException never propagates and the already-created child process is never killed. The observable result in a distributed build is that every target launches its child process, emits zero bytes of stdout/stderr, records zero sandbox file-access events, and never completes; the build is eventually killed by a max-runtime watchdog with no error message anywhere. It took a full process dump to recover the exception above.

        So while the deadlock is BuildXL's bug to fix, the trigger is this runtime behavior change, and the failure mode makes it very expensive to diagnose for anyone who hits it.

        Metadata

        Metadata

        Assignees

        Type

        No type

        Projects

        No projects

          Milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          FileStream over write-only overlapped named pipe handle throws UnauthorizedAccessException on .NET 11 (regression from .NET 10) #131503

          Description

          @AndyGerlicher

          Description

          On .NET 11, constructing a FileStream over the write side of a write-only, overlapped named pipe throws UnauthorizedAccessException. The same code works on .NET 10 and earlier.

          SafeFileHandleh=CreateNamedPipeW(name,PIPE_ACCESS_OUTBOUND|FILE_FLAG_OVERLAPPED, ...);usingvarfs=newFileStream(h,FileAccess.Write,bufferSize:4096,isAsync:true);// throws on .NET 11

          The OSFileStreamStrategy constructor evaluates handle.CanSeek, which goes SafeFileHandle.TypeGetFileTypeCore()GetPipeOrSocketType(). That probe requires read access on the handle, and a PIPE_ACCESS_OUTBOUND server handle is write-only, so it fails with ERROR_ACCESS_DENIED (5) and the constructor throws.

          Note that nothing is ever read from this handle — the stream is opened FileAccess.Write only. The type/seek probe looks like unnecessary work for a write-only handle, and it is newly fatal.

          Reproduction Steps

          Minimal standalone repro (multi-targeted net10.0;net11.0, no external dependencies):

          https://github.com/AndyGerlicher/net11-pipe-repro

          git clone https://github.com/AndyGerlicher/net11-pipe-repro
          cd net11-pipe-repro
          dotnet run -f net10.0
          dotnet run -f net11.0
          

          Expected behavior

          new FileStream(handle, FileAccess.Write, bufferSize, isAsync: true) succeeds, as it does on .NET 10:

          [PASS] PIPE_ACCESS_OUTBOUND + FileStream (what BuildXL does today)
          [PASS] PIPE_ACCESS_DUPLEX + FileStream (workaround candidate 1)
          [PASS] PIPE_ACCESS_OUTBOUND + NamedPipeServerStream (workaround candidate 2)
          

          Actual behavior

          On .NET 11 the first case fails:

          Runtime: .NET 11.0.0-preview.6.26359.118
          [FAIL] PIPE_ACCESS_OUTBOUND + FileStream (what BuildXL does today)
          System.UnauthorizedAccessException: Access to the path is denied.
          at Microsoft.Win32.SafeHandles.SafeFileHandle.GetPipeOrSocketType()
          at Microsoft.Win32.SafeHandles.SafeFileHandle.GetFileTypeCore()
          at Microsoft.Win32.SafeHandles.SafeFileHandle.get_Type()
          at Microsoft.Win32.SafeHandles.SafeFileHandle.get_CanSeek()
          at System.IO.Strategies.OSFileStreamStrategy..ctor(SafeFileHandle handle, FileAccess access)
          at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(SafeFileHandle handle, FileAccess access, Boolean isAsync)
          at System.IO.FileStream..ctor(SafeFileHandle handle, FileAccess access, Int32 bufferSize, Boolean isAsync)
          [PASS] PIPE_ACCESS_DUPLEX + FileStream (workaround candidate 1)
          [PASS] PIPE_ACCESS_OUTBOUND + NamedPipeServerStream (workaround candidate 2)
          

          Regression?

          Yes — regression from .NET 10. Verified working on .NET 10.0.10, failing on .NET 11.0.0-preview.6.26359.118.

          There does not appear to be an opt-out: setting System.IO.UseNet5CompatFileStream=true in runtimeconfig.json does not avoid the probe on .NET 11.

          Known Workarounds

          Both of these pass on .NET 10 and .NET 11 (both are exercised by the repro):

          1. Create the server handle with PIPE_ACCESS_DUPLEX instead of PIPE_ACCESS_OUTBOUND.
          2. Wrap the handle in NamedPipeServerStream instead of FileStream.

          Configuration

          • .NET 11.0.0-preview.6.26359.118 (SDK 11.0.100-preview.6.26359.118) — fails
          • .NET 10.0.10 — works
          • Windows, x64
          • Not specific to a particular configuration as far as we can tell; the handle access mode is what matters.

          Other information

          This is not hypothetical — it breaks the BuildXL process sandbox, which CloudBuild/QuickBuild uses to execute every build target.

          BuildXL.Processes.Internal.Pipes.CreateInheritablePipe creates the sandboxed child's stdin pipe with PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, and BuildXL.Processes.Internal.DetouredProcess.Start then wraps that handle:

          if(writeHandle!=null){FileStreamstream=newFileStream(writeHandle,FileAccess.Write,m_bufferSize,isAsync:true);m_standardInputWriter=newStreamWriter(stream,m_standardInputEncoding,m_bufferSize){AutoFlush=false};}

          That stdin pipe is created whenever the caller asks for stdout/stderr capture, which is the normal configuration.

          Worth calling out how badly this presents in practice. SandboxedProcessFactory.ProcessStart does try { proc.Start(); } catch { proc?.Dispose(); throw; }, and DetouredProcess.Dispose() blocks indefinitely on this path — so the UnauthorizedAccessException never propagates and the already-created child process is never killed. The observable result in a distributed build is that every target launches its child process, emits zero bytes of stdout/stderr, records zero sandbox file-access events, and never completes; the build is eventually killed by a max-runtime watchdog with no error message anywhere. It took a full process dump to recover the exception above.

          So while the deadlock is BuildXL's bug to fix, the trigger is this runtime behavior change, and the failure mode makes it very expensive to diagnose for anyone who hits it.

          Metadata

          Metadata

          Assignees

          Type

          No type

          Projects

          No projects

            Milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            FileStream over write-only overlapped named pipe handle throws UnauthorizedAccessException on .NET 11 (regression from .NET 10) #131503

            Description

            @AndyGerlicher

            Description

            On .NET 11, constructing a FileStream over the write side of a write-only, overlapped named pipe throws UnauthorizedAccessException. The same code works on .NET 10 and earlier.

            SafeFileHandleh=CreateNamedPipeW(name,PIPE_ACCESS_OUTBOUND|FILE_FLAG_OVERLAPPED, ...);usingvarfs=newFileStream(h,FileAccess.Write,bufferSize:4096,isAsync:true);// throws on .NET 11

            The OSFileStreamStrategy constructor evaluates handle.CanSeek, which goes SafeFileHandle.TypeGetFileTypeCore()GetPipeOrSocketType(). That probe requires read access on the handle, and a PIPE_ACCESS_OUTBOUND server handle is write-only, so it fails with ERROR_ACCESS_DENIED (5) and the constructor throws.

            Note that nothing is ever read from this handle — the stream is opened FileAccess.Write only. The type/seek probe looks like unnecessary work for a write-only handle, and it is newly fatal.

            Reproduction Steps

            Minimal standalone repro (multi-targeted net10.0;net11.0, no external dependencies):

            https://github.com/AndyGerlicher/net11-pipe-repro

            git clone https://github.com/AndyGerlicher/net11-pipe-repro
            cd net11-pipe-repro
            dotnet run -f net10.0
            dotnet run -f net11.0
            

            Expected behavior

            new FileStream(handle, FileAccess.Write, bufferSize, isAsync: true) succeeds, as it does on .NET 10:

            [PASS] PIPE_ACCESS_OUTBOUND + FileStream (what BuildXL does today)
            [PASS] PIPE_ACCESS_DUPLEX + FileStream (workaround candidate 1)
            [PASS] PIPE_ACCESS_OUTBOUND + NamedPipeServerStream (workaround candidate 2)
            

            Actual behavior

            On .NET 11 the first case fails:

            Runtime: .NET 11.0.0-preview.6.26359.118
            [FAIL] PIPE_ACCESS_OUTBOUND + FileStream (what BuildXL does today)
            System.UnauthorizedAccessException: Access to the path is denied.
            at Microsoft.Win32.SafeHandles.SafeFileHandle.GetPipeOrSocketType()
            at Microsoft.Win32.SafeHandles.SafeFileHandle.GetFileTypeCore()
            at Microsoft.Win32.SafeHandles.SafeFileHandle.get_Type()
            at Microsoft.Win32.SafeHandles.SafeFileHandle.get_CanSeek()
            at System.IO.Strategies.OSFileStreamStrategy..ctor(SafeFileHandle handle, FileAccess access)
            at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(SafeFileHandle handle, FileAccess access, Boolean isAsync)
            at System.IO.FileStream..ctor(SafeFileHandle handle, FileAccess access, Int32 bufferSize, Boolean isAsync)
            [PASS] PIPE_ACCESS_DUPLEX + FileStream (workaround candidate 1)
            [PASS] PIPE_ACCESS_OUTBOUND + NamedPipeServerStream (workaround candidate 2)
            

            Regression?

            Yes — regression from .NET 10. Verified working on .NET 10.0.10, failing on .NET 11.0.0-preview.6.26359.118.

            There does not appear to be an opt-out: setting System.IO.UseNet5CompatFileStream=true in runtimeconfig.json does not avoid the probe on .NET 11.

            Known Workarounds

            Both of these pass on .NET 10 and .NET 11 (both are exercised by the repro):

            1. Create the server handle with PIPE_ACCESS_DUPLEX instead of PIPE_ACCESS_OUTBOUND.
            2. Wrap the handle in NamedPipeServerStream instead of FileStream.

            Configuration

            • .NET 11.0.0-preview.6.26359.118 (SDK 11.0.100-preview.6.26359.118) — fails
            • .NET 10.0.10 — works
            • Windows, x64
            • Not specific to a particular configuration as far as we can tell; the handle access mode is what matters.

            Other information

            This is not hypothetical — it breaks the BuildXL process sandbox, which CloudBuild/QuickBuild uses to execute every build target.

            BuildXL.Processes.Internal.Pipes.CreateInheritablePipe creates the sandboxed child's stdin pipe with PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, and BuildXL.Processes.Internal.DetouredProcess.Start then wraps that handle:

            if(writeHandle!=null){FileStreamstream=newFileStream(writeHandle,FileAccess.Write,m_bufferSize,isAsync:true);m_standardInputWriter=newStreamWriter(stream,m_standardInputEncoding,m_bufferSize){AutoFlush=false};}

            That stdin pipe is created whenever the caller asks for stdout/stderr capture, which is the normal configuration.

            Worth calling out how badly this presents in practice. SandboxedProcessFactory.ProcessStart does try { proc.Start(); } catch { proc?.Dispose(); throw; }, and DetouredProcess.Dispose() blocks indefinitely on this path — so the UnauthorizedAccessException never propagates and the already-created child process is never killed. The observable result in a distributed build is that every target launches its child process, emits zero bytes of stdout/stderr, records zero sandbox file-access events, and never completes; the build is eventually killed by a max-runtime watchdog with no error message anywhere. It took a full process dump to recover the exception above.

            So while the deadlock is BuildXL's bug to fix, the trigger is this runtime behavior change, and the failure mode makes it very expensive to diagnose for anyone who hits it.

            Metadata

            Metadata

            Assignees

            Type

            No type

            Projects

            No projects

              Milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              FileStream over write-only overlapped named pipe handle throws UnauthorizedAccessException on .NET 11 (regression from .NET 10) #131503

              Description

              @AndyGerlicher

              Description

              On .NET 11, constructing a FileStream over the write side of a write-only, overlapped named pipe throws UnauthorizedAccessException. The same code works on .NET 10 and earlier.

              SafeFileHandleh=CreateNamedPipeW(name,PIPE_ACCESS_OUTBOUND|FILE_FLAG_OVERLAPPED, ...);usingvarfs=newFileStream(h,FileAccess.Write,bufferSize:4096,isAsync:true);// throws on .NET 11

              The OSFileStreamStrategy constructor evaluates handle.CanSeek, which goes SafeFileHandle.TypeGetFileTypeCore()GetPipeOrSocketType(). That probe requires read access on the handle, and a PIPE_ACCESS_OUTBOUND server handle is write-only, so it fails with ERROR_ACCESS_DENIED (5) and the constructor throws.

              Note that nothing is ever read from this handle — the stream is opened FileAccess.Write only. The type/seek probe looks like unnecessary work for a write-only handle, and it is newly fatal.

              Reproduction Steps

              Minimal standalone repro (multi-targeted net10.0;net11.0, no external dependencies):

              https://github.com/AndyGerlicher/net11-pipe-repro

              git clone https://github.com/AndyGerlicher/net11-pipe-repro
              cd net11-pipe-repro
              dotnet run -f net10.0
              dotnet run -f net11.0
              

              Expected behavior

              new FileStream(handle, FileAccess.Write, bufferSize, isAsync: true) succeeds, as it does on .NET 10:

              [PASS] PIPE_ACCESS_OUTBOUND + FileStream (what BuildXL does today)
              [PASS] PIPE_ACCESS_DUPLEX + FileStream (workaround candidate 1)
              [PASS] PIPE_ACCESS_OUTBOUND + NamedPipeServerStream (workaround candidate 2)
              

              Actual behavior

              On .NET 11 the first case fails:

              Runtime: .NET 11.0.0-preview.6.26359.118
              [FAIL] PIPE_ACCESS_OUTBOUND + FileStream (what BuildXL does today)
              System.UnauthorizedAccessException: Access to the path is denied.
              at Microsoft.Win32.SafeHandles.SafeFileHandle.GetPipeOrSocketType()
              at Microsoft.Win32.SafeHandles.SafeFileHandle.GetFileTypeCore()
              at Microsoft.Win32.SafeHandles.SafeFileHandle.get_Type()
              at Microsoft.Win32.SafeHandles.SafeFileHandle.get_CanSeek()
              at System.IO.Strategies.OSFileStreamStrategy..ctor(SafeFileHandle handle, FileAccess access)
              at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(SafeFileHandle handle, FileAccess access, Boolean isAsync)
              at System.IO.FileStream..ctor(SafeFileHandle handle, FileAccess access, Int32 bufferSize, Boolean isAsync)
              [PASS] PIPE_ACCESS_DUPLEX + FileStream (workaround candidate 1)
              [PASS] PIPE_ACCESS_OUTBOUND + NamedPipeServerStream (workaround candidate 2)
              

              Regression?

              Yes — regression from .NET 10. Verified working on .NET 10.0.10, failing on .NET 11.0.0-preview.6.26359.118.

              There does not appear to be an opt-out: setting System.IO.UseNet5CompatFileStream=true in runtimeconfig.json does not avoid the probe on .NET 11.

              Known Workarounds

              Both of these pass on .NET 10 and .NET 11 (both are exercised by the repro):

              1. Create the server handle with PIPE_ACCESS_DUPLEX instead of PIPE_ACCESS_OUTBOUND.
              2. Wrap the handle in NamedPipeServerStream instead of FileStream.

              Configuration

              • .NET 11.0.0-preview.6.26359.118 (SDK 11.0.100-preview.6.26359.118) — fails
              • .NET 10.0.10 — works
              • Windows, x64
              • Not specific to a particular configuration as far as we can tell; the handle access mode is what matters.

              Other information

              This is not hypothetical — it breaks the BuildXL process sandbox, which CloudBuild/QuickBuild uses to execute every build target.

              BuildXL.Processes.Internal.Pipes.CreateInheritablePipe creates the sandboxed child's stdin pipe with PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, and BuildXL.Processes.Internal.DetouredProcess.Start then wraps that handle:

              if(writeHandle!=null){FileStreamstream=newFileStream(writeHandle,FileAccess.Write,m_bufferSize,isAsync:true);m_standardInputWriter=newStreamWriter(stream,m_standardInputEncoding,m_bufferSize){AutoFlush=false};}

              That stdin pipe is created whenever the caller asks for stdout/stderr capture, which is the normal configuration.

              Worth calling out how badly this presents in practice. SandboxedProcessFactory.ProcessStart does try { proc.Start(); } catch { proc?.Dispose(); throw; }, and DetouredProcess.Dispose() blocks indefinitely on this path — so the UnauthorizedAccessException never propagates and the already-created child process is never killed. The observable result in a distributed build is that every target launches its child process, emits zero bytes of stdout/stderr, records zero sandbox file-access events, and never completes; the build is eventually killed by a max-runtime watchdog with no error message anywhere. It took a full process dump to recover the exception above.

              So while the deadlock is BuildXL's bug to fix, the trigger is this runtime behavior change, and the failure mode makes it very expensive to diagnose for anyone who hits it.

              Metadata

              Metadata

              Assignees

              Type

              No type

              Projects

              No projects

                Milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                Skip to content

                FileStream over write-only overlapped named pipe handle throws UnauthorizedAccessException on .NET 11 (regression from .NET 10) #131503

                Description

                @AndyGerlicher

                Description

                On .NET 11, constructing a FileStream over the write side of a write-only, overlapped named pipe throws UnauthorizedAccessException. The same code works on .NET 10 and earlier.

                SafeFileHandleh=CreateNamedPipeW(name,PIPE_ACCESS_OUTBOUND|FILE_FLAG_OVERLAPPED, ...);usingvarfs=newFileStream(h,FileAccess.Write,bufferSize:4096,isAsync:true);// throws on .NET 11

                The OSFileStreamStrategy constructor evaluates handle.CanSeek, which goes SafeFileHandle.TypeGetFileTypeCore()GetPipeOrSocketType(). That probe requires read access on the handle, and a PIPE_ACCESS_OUTBOUND server handle is write-only, so it fails with ERROR_ACCESS_DENIED (5) and the constructor throws.

                Note that nothing is ever read from this handle — the stream is opened FileAccess.Write only. The type/seek probe looks like unnecessary work for a write-only handle, and it is newly fatal.

                Reproduction Steps

                Minimal standalone repro (multi-targeted net10.0;net11.0, no external dependencies):

                https://github.com/AndyGerlicher/net11-pipe-repro

                git clone https://github.com/AndyGerlicher/net11-pipe-repro
                cd net11-pipe-repro
                dotnet run -f net10.0
                dotnet run -f net11.0
                

                Expected behavior

                new FileStream(handle, FileAccess.Write, bufferSize, isAsync: true) succeeds, as it does on .NET 10:

                [PASS] PIPE_ACCESS_OUTBOUND + FileStream (what BuildXL does today)
                [PASS] PIPE_ACCESS_DUPLEX + FileStream (workaround candidate 1)
                [PASS] PIPE_ACCESS_OUTBOUND + NamedPipeServerStream (workaround candidate 2)
                

                Actual behavior

                On .NET 11 the first case fails:

                Runtime: .NET 11.0.0-preview.6.26359.118
                [FAIL] PIPE_ACCESS_OUTBOUND + FileStream (what BuildXL does today)
                System.UnauthorizedAccessException: Access to the path is denied.
                at Microsoft.Win32.SafeHandles.SafeFileHandle.GetPipeOrSocketType()
                at Microsoft.Win32.SafeHandles.SafeFileHandle.GetFileTypeCore()
                at Microsoft.Win32.SafeHandles.SafeFileHandle.get_Type()
                at Microsoft.Win32.SafeHandles.SafeFileHandle.get_CanSeek()
                at System.IO.Strategies.OSFileStreamStrategy..ctor(SafeFileHandle handle, FileAccess access)
                at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(SafeFileHandle handle, FileAccess access, Boolean isAsync)
                at System.IO.FileStream..ctor(SafeFileHandle handle, FileAccess access, Int32 bufferSize, Boolean isAsync)
                [PASS] PIPE_ACCESS_DUPLEX + FileStream (workaround candidate 1)
                [PASS] PIPE_ACCESS_OUTBOUND + NamedPipeServerStream (workaround candidate 2)
                

                Regression?

                Yes — regression from .NET 10. Verified working on .NET 10.0.10, failing on .NET 11.0.0-preview.6.26359.118.

                There does not appear to be an opt-out: setting System.IO.UseNet5CompatFileStream=true in runtimeconfig.json does not avoid the probe on .NET 11.

                Known Workarounds

                Both of these pass on .NET 10 and .NET 11 (both are exercised by the repro):

                1. Create the server handle with PIPE_ACCESS_DUPLEX instead of PIPE_ACCESS_OUTBOUND.
                2. Wrap the handle in NamedPipeServerStream instead of FileStream.

                Configuration

                • .NET 11.0.0-preview.6.26359.118 (SDK 11.0.100-preview.6.26359.118) — fails
                • .NET 10.0.10 — works
                • Windows, x64
                • Not specific to a particular configuration as far as we can tell; the handle access mode is what matters.

                Other information

                This is not hypothetical — it breaks the BuildXL process sandbox, which CloudBuild/QuickBuild uses to execute every build target.

                BuildXL.Processes.Internal.Pipes.CreateInheritablePipe creates the sandboxed child's stdin pipe with PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, and BuildXL.Processes.Internal.DetouredProcess.Start then wraps that handle:

                if(writeHandle!=null){FileStreamstream=newFileStream(writeHandle,FileAccess.Write,m_bufferSize,isAsync:true);m_standardInputWriter=newStreamWriter(stream,m_standardInputEncoding,m_bufferSize){AutoFlush=false};}

                That stdin pipe is created whenever the caller asks for stdout/stderr capture, which is the normal configuration.

                Worth calling out how badly this presents in practice. SandboxedProcessFactory.ProcessStart does try { proc.Start(); } catch { proc?.Dispose(); throw; }, and DetouredProcess.Dispose() blocks indefinitely on this path — so the UnauthorizedAccessException never propagates and the already-created child process is never killed. The observable result in a distributed build is that every target launches its child process, emits zero bytes of stdout/stderr, records zero sandbox file-access events, and never completes; the build is eventually killed by a max-runtime watchdog with no error message anywhere. It took a full process dump to recover the exception above.

                So while the deadlock is BuildXL's bug to fix, the trigger is this runtime behavior change, and the failure mode makes it very expensive to diagnose for anyone who hits it.

                Metadata

                Metadata

                Assignees

                Type

                No type

                Projects

                No projects

                  Milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions