9
9
using System . Collections . Generic ;
10
10
using System . IO ;
11
11
using System . IO . Pipes ;
12
+ using System . Reflection ;
12
13
using System . Runtime . InteropServices ;
13
14
using System . Security . AccessControl ;
14
15
using System . Security . Principal ;
@@ -18,6 +19,15 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel
18
19
{
19
20
public class NamedPipeServerListener : ServerListenerBase < NamedPipeServerChannel >
20
21
{
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
+
21
31
private ILogger logger ;
22
32
private string inOutPipeName ;
23
33
private readonly string outPipeName ;
@@ -50,7 +60,8 @@ public override void Start()
50
60
{
51
61
try
52
62
{
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" ) )
54
65
{
55
66
PipeSecurity pipeSecurity = new PipeSecurity ( ) ;
56
67
@@ -72,35 +83,49 @@ public override void Start()
72
83
PipeAccessRights . ReadWrite , AccessControlType . Allow ) ) ;
73
84
}
74
85
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
+
81
98
if ( this . outPipeName != null )
82
99
{
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
+ } ) ;
84
111
}
85
112
}
86
113
else
87
114
{
88
- // This handles the Unix case since PipeSecurity is not supported on Unix.
89
- // Instead, we use chmod in Start-EditorServices.ps1
90
115
this . inOutPipeServer = new NamedPipeServerStream (
91
116
pipeName : inOutPipeName ,
92
117
direction : PipeDirection . InOut ,
93
118
maxNumberOfServerInstances : 1 ,
94
119
transmissionMode : PipeTransmissionMode . Byte ,
95
- options : PipeOptions . Asynchronous ) ;
120
+ options : PipeOptions . Asynchronous | ( PipeOptions ) CurrentUserOnly ) ;
96
121
if ( this . outPipeName != null )
97
122
{
98
123
this . outPipeServer = new NamedPipeServerStream (
99
124
pipeName : outPipeName ,
100
125
direction : PipeDirection . Out ,
101
126
maxNumberOfServerInstances : 1 ,
102
127
transmissionMode : PipeTransmissionMode . Byte ,
103
- options : PipeOptions . None ) ;
128
+ options : ( PipeOptions ) CurrentUserOnly ) ;
104
129
}
105
130
}
106
131
ListenForConnection ( ) ;
@@ -171,143 +196,4 @@ private static async Task WaitForConnectionAsync(NamedPipeServerStream pipeServe
171
196
await pipeServerStream . FlushAsync ( ) ;
172
197
}
173
198
}
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
- }
313
199
}
0 commit comments