Repository navigation
FileStream over write-only overlapped named pipe handle throws UnauthorizedAccessException on .NET 11 (regression from .NET 10) #131503
Description
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.
SafeFileHandle h = CreateNamedPipeW(name, PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, ...);
using var fs = new FileStream(h, FileAccess.Write, bufferSize: 4096, isAsync: true); // throws on .NET 11The OSFileStreamStrategy constructor evaluates handle.CanSeek, which goes SafeFileHandle.Type → GetFileTypeCore() → 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):
- Create the server handle with
PIPE_ACCESS_DUPLEXinstead ofPIPE_ACCESS_OUTBOUND. - Wrap the handle in
NamedPipeServerStreaminstead ofFileStream.
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)
{
FileStream stream = new FileStream(writeHandle, FileAccess.Write, m_bufferSize, isAsync: true);
m_standardInputWriter = new StreamWriter(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.
Activity
dotnet-policy-service commentedon Jul 28, 2026
Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.
Merged PR 942783: Use NamedPipeServerStream for the detoured process …
jhudsoncedaron commentedon Jul 30, 2026
I'm going to poke the bear. Try adding
FILE_READ_ATTRIBUTESto your open operation. Bet it works.(Generally speaking it's a good idea to provide FILE_READ_ATTRIBUTES when opening a handle to something and passing it to high level code that isn't yours. I learned that the hard way ages ago.)
adamsitnik commentedon Aug 10, 2026
Hi @AndyGerlicher !
Big thanks for a very detailed bug report. I was able to use your repro and confirm it.
I've tried to find a proper way to fix it. Here are my findings:
CanSeekcallsTypein order to figure out whether given file is seekable:runtime/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
Line 111 in 795badf
GetFileTypereturnsFILE_TYPE_PIPEfor both pipes and sockets, that is why we are callingGetNamedPipeInfoto figure out whether it's a pipe or socket:runtime/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
Lines 369 to 371 in 795badf
ERROR_ACCESS_DENIEDas pipe, because it's not reliable and foremost this info is exposed by new public API (SafeFileHandle.Type), which should simply keep throwing the exception in case of missing permissions.runtime/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
Lines 377 to 383 in 795badf
We could use
getsockoptto distinguish socket vs pipe, but it would add a dependency to additional library.I think it will be best if we mimic what we do on Unix, namely try to seek by
0from current position to figure out whether file is seekable:runtime/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
Line 556 in 795badf
This should unblock your scenario. I will just need to confirm that using different sys-call (
SetFilePointer) won't regress the performance.Thanks,
Adam
Ensure FileStream.CanSeek works for PIPE_ACCESS_OUTBOUND pipes (dotne…