From adb22bfb3ec7bf8b0f60f9c37f066f067476d152 Mon Sep 17 00:00:00 2001 From: Roger Sanders Date: Wed, 15 Nov 2023 12:55:02 +1100 Subject: [PATCH] Fixed setting ReadMode to Message for NamedPipeClientStream on Windows Fixes a longstanding issue inherited from the .NET framework which prevents ReadMode being set to Message on the client end of read only named pipes on Windows platforms. Also added in new tests for Message mode on Windows to verify correct functionality. Fix #83072 --- .../IO/Pipes/NamedPipeClientStream.Windows.cs | 4 + .../NamedPipeTests/NamedPipeTest.Specific.cs | 86 ++++++++++++++++++- 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.IO.Pipes/src/System/IO/Pipes/NamedPipeClientStream.Windows.cs b/src/libraries/System.IO.Pipes/src/System/IO/Pipes/NamedPipeClientStream.Windows.cs index 83d4f66d0cf4..7a9f6860d45f 100644 --- a/src/libraries/System.IO.Pipes/src/System/IO/Pipes/NamedPipeClientStream.Windows.cs +++ b/src/libraries/System.IO.Pipes/src/System/IO/Pipes/NamedPipeClientStream.Windows.cs @@ -43,6 +43,10 @@ private bool TryConnect(int timeout) { access |= Interop.Kernel32.GenericOperations.GENERIC_WRITE; } + else + { + access |= Interop.Kernel32.FileOperations.FILE_WRITE_ATTRIBUTES; + } SafePipeHandle handle = CreateNamedPipeClient(_normalizedPipePath, ref secAttrs, _pipeFlags, access); diff --git a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Specific.cs b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Specific.cs index 2b6421d1f09a..206dad113edc 100644 --- a/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Specific.cs +++ b/src/libraries/System.IO.Pipes/tests/NamedPipeTests/NamedPipeTest.Specific.cs @@ -472,6 +472,62 @@ public async Task PipeTransmissionMode_Returns_Byte() } } + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] // Unix doesn't currently support message mode + public void Windows_SetReadModeTo__PipeTransmissionModeMessage() + { + string pipeName = PipeStreamConformanceTests.GetUniquePipeName(); + using (var server = new NamedPipeServerStream(pipeName, PipeDirection.In, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous)) + using (var client = new NamedPipeClientStream(".", pipeName, PipeDirection.Out)) + { + Task clientConnect = client.ConnectAsync(); + server.WaitForConnection(); + clientConnect.Wait(); + + // Throws regardless of connection status for the pipe that is set to PipeDirection.In. This is because + // as per MSDN, "This mode gives the server the equivalent of GENERIC_READ access to the pipe", which + // isn't sufficient to call SetNamedPipeHandleState, as we need FILE_WRITE_ATTRIBUTES as well, or + // GENERIC_WRITE instead which includes this permission. Since the readmode can be set for the server + // side when creating the named pipe, this issue can be avoided by specifying the desired mode from the + // start. In the theoretical case of wanting to create a pipe in message mode, then downgrade the + // readmode to byte mode on the server however, this isn't currently possible in the Win32 API. + Assert.Throws(() => server.ReadMode = PipeTransmissionMode.Byte); + Assert.Throws(() => server.ReadMode = PipeTransmissionMode.Message); + + // Can select either message or byte mode on client end of the pipe + client.ReadMode = PipeTransmissionMode.Byte; + client.ReadMode = PipeTransmissionMode.Message; + } + + using (var server = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous)) + using (var client = new NamedPipeClientStream(".", pipeName, PipeDirection.In)) + { + Task clientConnect = client.ConnectAsync(); + server.WaitForConnection(); + clientConnect.Wait(); + + // Can select either message or byte mode on either end of the pipe + server.ReadMode = PipeTransmissionMode.Byte; + client.ReadMode = PipeTransmissionMode.Byte; + server.ReadMode = PipeTransmissionMode.Message; + client.ReadMode = PipeTransmissionMode.Message; + } + + using (var server = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous)) + using (var client = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous)) + { + Task clientConnect = client.ConnectAsync(); + server.WaitForConnection(); + clientConnect.Wait(); + + // Can select either message or byte mode on either end of the pipe + server.ReadMode = PipeTransmissionMode.Byte; + client.ReadMode = PipeTransmissionMode.Byte; + server.ReadMode = PipeTransmissionMode.Message; + client.ReadMode = PipeTransmissionMode.Message; + } + } + [Fact] [PlatformSpecific(TestPlatforms.Windows)] // Unix doesn't currently support message mode public void Windows_SetReadModeTo__PipeTransmissionModeByte() @@ -484,9 +540,20 @@ public void Windows_SetReadModeTo__PipeTransmissionModeByte() server.WaitForConnection(); clientConnect.Wait(); - // Throws regardless of connection status for the pipe that is set to PipeDirection.In - Assert.Throws(() => server.ReadMode = PipeTransmissionMode.Byte); client.ReadMode = PipeTransmissionMode.Byte; + + // Throws regardless of connection status for the pipe that is set to PipeDirection.In. This is because + // as per MSDN, "This mode gives the server the equivalent of GENERIC_READ access to the pipe", which + // isn't sufficient to call SetNamedPipeHandleState, as we need FILE_WRITE_ATTRIBUTES as well, or + // GENERIC_WRITE instead which includes this permission. Since the readmode can be set for the server + // side when creating the named pipe, this issue can be avoided by specifying the desired mode from the + // start. In the theoretical case of wanting to create a pipe in message mode, then downgrade the + // readmode to byte mode on the server however, this isn't currently possible in the Win32 API. + Assert.Throws(() => server.ReadMode = PipeTransmissionMode.Byte); + Assert.Throws(() => server.ReadMode = PipeTransmissionMode.Message); + + // Can't change to message mode on the client for a byte pipe + Assert.Throws(() => client.ReadMode = PipeTransmissionMode.Message); } using (var server = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) @@ -496,9 +563,14 @@ public void Windows_SetReadModeTo__PipeTransmissionModeByte() server.WaitForConnection(); clientConnect.Wait(); - // Throws regardless of connection status for the pipe that is set to PipeDirection.In - Assert.Throws(() => client.ReadMode = PipeTransmissionMode.Byte); + client.ReadMode = PipeTransmissionMode.Byte; server.ReadMode = PipeTransmissionMode.Byte; + + // Can't change to message mode on the server for a byte pipe + Assert.Throws(() => server.ReadMode = PipeTransmissionMode.Message); + + // Can't change to message mode on the client for a byte pipe + Assert.Throws(() => client.ReadMode = PipeTransmissionMode.Message); } using (var server = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) @@ -510,6 +582,12 @@ public void Windows_SetReadModeTo__PipeTransmissionModeByte() server.ReadMode = PipeTransmissionMode.Byte; client.ReadMode = PipeTransmissionMode.Byte; + + // Can't change to message mode on the server for a byte pipe + Assert.Throws(() => server.ReadMode = PipeTransmissionMode.Message); + + // Can't change to message mode on the client for a byte pipe + Assert.Throws(() => client.ReadMode = PipeTransmissionMode.Message); } }