99using System . Collections . Generic ;
1010using System . IO ;
1111using System . IO . Pipes ;
12+ using System . Reflection ;
1213using System . Runtime . InteropServices ;
1314using System . Security . AccessControl ;
1415using System . Security . Principal ;
@@ -18,6 +19,15 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel
1819{
1920 public class NamedPipeServerListener : ServerListenerBase < NamedPipeServerChannel >
2021 {
22+ // This int will be casted to a PipeOptions enum that only exists in .NET Core 2.1 and up which is why it's not available to us in .NET Standard.
23+ private const int CurrentUserOnly = 536870912 ;
24+
25+ // In .NET Framework, NamedPipeServerStream has a constructor that takes in a PipeSecurity object. We will use reflection to call the constructor,
26+ // since .NET Framework doesn't have the `CurrentUserOnly` PipeOption.
27+ // doc: https://docs.microsoft.com/en-us/dotnet/api/system.io.pipes.namedpipeserverstream.-ctor?view=netframework-4.7.2#System_IO_Pipes_NamedPipeServerStream__ctor_System_String_System_IO_Pipes_PipeDirection_System_Int32_System_IO_Pipes_PipeTransmissionMode_System_IO_Pipes_PipeOptions_System_Int32_System_Int32_System_IO_Pipes_PipeSecurity_
28+ private static ConstructorInfo _netFrameworkPipeServerConstructor =
29+ typeof ( NamedPipeServerStream ) . GetConstructor ( new [ ] { typeof ( string ) , typeof ( PipeDirection ) , typeof ( int ) , typeof ( PipeTransmissionMode ) , typeof ( PipeOptions ) , typeof ( int ) , typeof ( int ) , typeof ( PipeSecurity ) } ) ;
30+
2131 private ILogger logger ;
2232 private string inOutPipeName ;
2333 private readonly string outPipeName ;
@@ -50,7 +60,8 @@ public override void Start()
5060 {
5161 try
5262 {
53- if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
63+ // If we're running in Windows PowerShell, we use the constructor via Reflection
64+ if ( RuntimeInformation . FrameworkDescription . StartsWith ( ".NET Framework" ) )
5465 {
5566 PipeSecurity pipeSecurity = new PipeSecurity ( ) ;
5667
@@ -72,35 +83,49 @@ public override void Start()
7283 PipeAccessRights . ReadWrite , AccessControlType . Allow ) ) ;
7384 }
7485
75- // Unfortunately, .NET Core does not support passing in a PipeSecurity object into the constructor for
76- // NamedPipeServerStream so we are creating native Named Pipes and securing them using native APIs. The
77- // issue on .NET Core regarding Named Pipe security is here: https://github.com/dotnet/corefx/issues/30170
78- // 99% of this code was borrowed from PowerShell here:
79- // https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs#L124-L256
80- this . inOutPipeServer = NamedPipeNative . CreateNamedPipe ( inOutPipeName , pipeSecurity ) ;
86+ _netFrameworkPipeServerConstructor . Invoke ( new object [ ]
87+ {
88+ inOutPipeName ,
89+ PipeDirection . InOut ,
90+ 1 , // maxNumberOfServerInstances
91+ PipeTransmissionMode . Byte ,
92+ PipeOptions . Asynchronous ,
93+ 1024 , // inBufferSize
94+ 1024 , // outBufferSize
95+ pipeSecurity
96+ } ) ;
97+
8198 if ( this . outPipeName != null )
8299 {
83- this . outPipeServer = NamedPipeNative . CreateNamedPipe ( outPipeName , pipeSecurity ) ;
100+ _netFrameworkPipeServerConstructor . Invoke ( new object [ ]
101+ {
102+ outPipeName ,
103+ PipeDirection . InOut ,
104+ 1 , // maxNumberOfServerInstances
105+ PipeTransmissionMode . Byte ,
106+ PipeOptions . Asynchronous ,
107+ 1024 , // inBufferSize
108+ 1024 , // outBufferSize
109+ pipeSecurity
110+ } ) ;
84111 }
85112 }
86113 else
87114 {
88- // This handles the Unix case since PipeSecurity is not supported on Unix.
89- // Instead, we use chmod in Start-EditorServices.ps1
90115 this . inOutPipeServer = new NamedPipeServerStream (
91116 pipeName : inOutPipeName ,
92117 direction : PipeDirection . InOut ,
93118 maxNumberOfServerInstances : 1 ,
94119 transmissionMode : PipeTransmissionMode . Byte ,
95- options : PipeOptions . Asynchronous ) ;
120+ options : PipeOptions . Asynchronous | ( PipeOptions ) CurrentUserOnly ) ;
96121 if ( this . outPipeName != null )
97122 {
98123 this . outPipeServer = new NamedPipeServerStream (
99124 pipeName : outPipeName ,
100125 direction : PipeDirection . Out ,
101126 maxNumberOfServerInstances : 1 ,
102127 transmissionMode : PipeTransmissionMode . Byte ,
103- options : PipeOptions . None ) ;
128+ options : ( PipeOptions ) CurrentUserOnly ) ;
104129 }
105130 }
106131 ListenForConnection ( ) ;
@@ -171,143 +196,4 @@ private static async Task WaitForConnectionAsync(NamedPipeServerStream pipeServe
171196 await pipeServerStream . FlushAsync ( ) ;
172197 }
173198 }
174-
175- /// <summary>
176- /// Native API for Named Pipes
177- /// This code was borrowed from PowerShell here:
178- /// https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs#L124-L256
179- /// </summary>
180- internal static class NamedPipeNative
181- {
182- #region Pipe constants
183-
184- // Pipe open mode
185- internal const uint PIPE_ACCESS_DUPLEX = 0x00000003 ;
186-
187- // Pipe modes
188- internal const uint PIPE_TYPE_BYTE = 0x00000000 ;
189- internal const uint FILE_FLAG_OVERLAPPED = 0x40000000 ;
190- internal const uint FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000 ;
191- internal const uint PIPE_READMODE_BYTE = 0x00000000 ;
192-
193- #endregion
194-
195- #region Data structures
196-
197- [ StructLayout ( LayoutKind . Sequential ) ]
198- internal class SECURITY_ATTRIBUTES
199- {
200- /// <summary>
201- /// The size, in bytes, of this structure. Set this value to the size of the SECURITY_ATTRIBUTES structure.
202- /// </summary>
203- public int NLength ;
204-
205- /// <summary>
206- /// A pointer to a security descriptor for the object that controls the sharing of it.
207- /// </summary>
208- public IntPtr LPSecurityDescriptor = IntPtr . Zero ;
209-
210- /// <summary>
211- /// A Boolean value that specifies whether the returned handle is inherited when a new process is created.
212- /// </summary>
213- public bool InheritHandle ;
214-
215- /// <summary>
216- /// Initializes a new instance of the SECURITY_ATTRIBUTES class
217- /// </summary>
218- public SECURITY_ATTRIBUTES ( )
219- {
220- this . NLength = 12 ;
221- }
222- }
223-
224- #endregion
225-
226- #region Pipe methods
227-
228- [ DllImport ( "kernel32.dll" , SetLastError = true , CharSet = CharSet . Unicode ) ]
229- internal static extern SafePipeHandle CreateNamedPipe (
230- string lpName ,
231- uint dwOpenMode ,
232- uint dwPipeMode ,
233- uint nMaxInstances ,
234- uint nOutBufferSize ,
235- uint nInBufferSize ,
236- uint nDefaultTimeOut ,
237- SECURITY_ATTRIBUTES securityAttributes ) ;
238-
239- internal static SECURITY_ATTRIBUTES GetSecurityAttributes ( GCHandle securityDescriptorPinnedHandle , bool inheritHandle = false )
240- {
241- SECURITY_ATTRIBUTES securityAttributes = new NamedPipeNative . SECURITY_ATTRIBUTES ( ) ;
242- securityAttributes . InheritHandle = inheritHandle ;
243- securityAttributes . NLength = ( int ) Marshal . SizeOf ( securityAttributes ) ;
244- securityAttributes . LPSecurityDescriptor = securityDescriptorPinnedHandle . AddrOfPinnedObject ( ) ;
245- return securityAttributes ;
246- }
247-
248- /// <summary>
249- /// Helper method to create a PowerShell transport named pipe via native API, along
250- /// with a returned .Net NamedPipeServerStream object wrapping the named pipe.
251- /// </summary>
252- /// <param name="pipeName">Named pipe core name.</param>
253- /// <param name="securityDesc"></param>
254- /// <returns>NamedPipeServerStream</returns>
255- internal static NamedPipeServerStream CreateNamedPipe (
256- string pipeName ,
257- PipeSecurity pipeSecurity )
258-
259- {
260- string fullPipeName = @"\\.\pipe\" + pipeName ;
261- CommonSecurityDescriptor securityDesc = new CommonSecurityDescriptor ( false , false , pipeSecurity . GetSecurityDescriptorBinaryForm ( ) , 0 ) ;
262-
263- // Create optional security attributes based on provided PipeSecurity.
264- NamedPipeNative . SECURITY_ATTRIBUTES securityAttributes = null ;
265- GCHandle ? securityDescHandle = null ;
266- if ( securityDesc != null )
267- {
268- byte [ ] securityDescBuffer = new byte [ securityDesc . BinaryLength ] ;
269- securityDesc . GetBinaryForm ( securityDescBuffer , 0 ) ;
270-
271- securityDescHandle = GCHandle . Alloc ( securityDescBuffer , GCHandleType . Pinned ) ;
272- securityAttributes = NamedPipeNative . GetSecurityAttributes ( securityDescHandle . Value ) ;
273- }
274-
275- // Create named pipe.
276- SafePipeHandle pipeHandle = NamedPipeNative . CreateNamedPipe (
277- fullPipeName ,
278- NamedPipeNative . PIPE_ACCESS_DUPLEX | NamedPipeNative . FILE_FLAG_FIRST_PIPE_INSTANCE | NamedPipeNative . FILE_FLAG_OVERLAPPED ,
279- NamedPipeNative . PIPE_TYPE_BYTE | NamedPipeNative . PIPE_READMODE_BYTE ,
280- 1 ,
281- 1024 ,
282- 1024 ,
283- 0 ,
284- securityAttributes ) ;
285-
286- int lastError = Marshal . GetLastWin32Error ( ) ;
287- if ( securityDescHandle != null )
288- {
289- securityDescHandle . Value . Free ( ) ;
290- }
291-
292- if ( pipeHandle . IsInvalid )
293- {
294- throw new InvalidOperationException ( ) ;
295- }
296- // Create the .Net NamedPipeServerStream wrapper.
297- try
298- {
299- return new NamedPipeServerStream (
300- PipeDirection . InOut ,
301- true , // IsAsync
302- false , // IsConnected
303- pipeHandle ) ;
304- }
305- catch ( Exception )
306- {
307- pipeHandle . Dispose ( ) ;
308- throw ;
309- }
310- }
311- #endregion
312- }
313199}
0 commit comments