Skip to content

Commit 5716ebc

Browse files
TylerLeonhardtRobert Holt
authored and
Robert Holt
committed
Use named pipe transport securely, remove TCP
* Use ACL'd named pipe as transport * Remove TCP as a transport option
1 parent db1026e commit 5716ebc

20 files changed

+352
-339
lines changed

PowerShellEditorServices.build.ps1

+7-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,10 @@ task TestPowerShellApi -If { !$script:IsUnix } {
143143
}
144144

145145
task Build {
146-
exec { & $script:dotnetExe build -c $Configuration .\src\PowerShellEditorServices.Host\PowerShellEditorServices.Host.csproj $script:TargetFrameworksParam }
146+
exec { & $script:dotnetExe publish -c $Configuration .\src\PowerShellEditorServices.Host\PowerShellEditorServices.Host.csproj -f netstandard1.6 }
147+
if (!$script:IsUnix) {
148+
exec { & $script:dotnetExe publish -c $Configuration .\src\PowerShellEditorServices.Host\PowerShellEditorServices.Host.csproj -f net451 }
149+
}
147150
exec { & $script:dotnetExe build -c $Configuration .\src\PowerShellEditorServices.VSCode\PowerShellEditorServices.VSCode.csproj $script:TargetFrameworksParam }
148151
exec { & $script:dotnetExe publish -c $Configuration .\src\PowerShellEditorServices\PowerShellEditorServices.csproj -f netstandard1.6 }
149152
Copy-Item $PSScriptRoot\src\PowerShellEditorServices\bin\$Configuration\netstandard1.6\publish\UnixConsoleEcho.dll -Destination $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\netstandard1.6
@@ -209,12 +212,15 @@ task LayoutModule -After Build {
209212
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\netstandard1.6\* -Filter Microsoft.PowerShell.EditorServices*.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Core\
210213
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\netstandard1.6\UnixConsoleEcho.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Core\
211214
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\netstandard1.6\libdisablekeyecho.* -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Core\
215+
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\netstandard1.6\publish\runtimes\win\lib\netstandard1.3\* -Filter System.IO.Pipes*.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Core\
216+
212217
if (!$script:IsUnix) {
213218
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices\bin\$Configuration\net451\Serilog*.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Desktop
214219

215220
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\net451\* -Filter Microsoft.PowerShell.EditorServices*.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Desktop\
216221
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\net451\Newtonsoft.Json.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Desktop\
217222
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\net451\UnixConsoleEcho.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Desktop\
223+
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\net451\publish\System.Runtime.InteropServices.RuntimeInformation.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Desktop\
218224
}
219225

220226
# Copy Third Party Notices.txt to module folder

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ of the following classes:
3030
- TemplateService
3131

3232
The intended usage model is now to host PowerShell Editor Services within powershell.exe
33-
and communicate with it over TCP sockets via the [Language Server Protocol](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md)
33+
and communicate with it over named pipes (or Unix domain sockets on macOS & Linux) via the [Language Server Protocol](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md)
3434
and [Debug Adapter Protocol](https://github.com/Microsoft/vscode-debugadapter-node/blob/master/protocol/src/debugProtocol.ts).
3535
Detailed usage documentation for this module is coming soon!
3636

module/PowerShellEditorServices/PowerShellEditorServices.psm1

-16
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,6 @@ function Start-EditorServicesHost {
3131
[string]
3232
$HostVersion,
3333

34-
[int]
35-
$LanguageServicePort,
36-
37-
[int]
38-
$DebugServicePort,
39-
4034
[switch]
4135
$Stdio,
4236

@@ -110,16 +104,6 @@ function Start-EditorServicesHost {
110104
$debugServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::Stdio
111105
}
112106

113-
if ($LanguageServicePort) {
114-
$languageServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::Tcp
115-
$languageServiceConfig.Endpoint = "$LanguageServicePort"
116-
}
117-
118-
if ($DebugServicePort) {
119-
$debugServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::Tcp
120-
$debugServiceConfig.Endpoint = "$DebugServicePort"
121-
}
122-
123107
if ($LanguageServiceNamedPipe) {
124108
$languageServiceConfig.TransportType = [Microsoft.PowerShell.EditorServices.Host.EditorServiceTransportType]::NamedPipe
125109
$languageServiceConfig.Endpoint = "$LanguageServiceNamedPipe"

module/PowerShellEditorServices/Start-EditorServices.ps1

+97-74
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# - Verifying the existence of dependencies like PowerShellGet
77
# - Verifying that the expected version of the PowerShellEditorServices module is installed
88
# - Installing the PowerShellEditorServices module if confirmed by the user
9-
# - Finding unused TCP port numbers for the language and debug services to use
9+
# - Creating named pipes for the language and debug services to use (if using named pipes)
1010
# - Starting the language and debug services from the PowerShellEditorServices module
1111
#
1212
# NOTE: If editor integration authors make modifications to this
@@ -39,7 +39,7 @@ param(
3939
[ValidateNotNullOrEmpty()]
4040
$LogPath,
4141

42-
[ValidateSet("Diagnostic", "Normal", "Verbose", "Error", "Diagnostic")]
42+
[ValidateSet("Diagnostic", "Normal", "Verbose", "Error")]
4343
$LogLevel,
4444

4545
[Parameter(Mandatory=$true)]
@@ -75,8 +75,7 @@ param(
7575
$DebugServicePipeName = $null
7676
)
7777

78-
$minPortNumber = 10000
79-
$maxPortNumber = 30000
78+
$DEFAULT_USER_MODE = "600"
8079

8180
if ($LogLevel -eq "Diagnostic") {
8281
$VerbosePreference = 'Continue'
@@ -168,67 +167,71 @@ function Test-ModuleAvailable($ModuleName, $ModuleVersion) {
168167
return $false;
169168
}
170169

171-
function Test-PortAvailability {
170+
function New-NamedPipeName {
171+
172+
# We try 10 times to find a valid pipe name
173+
for ($i = 0; $i -lt 10; $i++) {
174+
# add a guid to make the pipe unique
175+
$PipeName = "PSES_$((New-Guid).Guid)"
176+
177+
if ((Test-NamedPipeName -PipeName $PipeName)) {
178+
return $PipeName
179+
}
180+
}
181+
ExitWithError "Could not find valid a pipe name."
182+
}
183+
184+
function Get-NamedPipePath {
172185
param(
173186
[Parameter(Mandatory=$true)]
174-
[int]
175-
$PortNumber
187+
[ValidateNotNullOrEmpty()]
188+
[string]
189+
$PipeName
176190
)
177191

178-
$portAvailable = $true
179-
180-
try {
181-
# After some research, I don't believe we should run into problems using an IPv4 port
182-
# that happens to be in use via an IPv6 address. That is based on this info:
183-
# https://www.ibm.com/support/knowledgecenter/ssw_i5_54/rzai2/rzai2compipv4ipv6.htm#rzai2compipv4ipv6__compports
184-
$ipAddress = [System.Net.IPAddress]::Loopback
185-
Log "Testing availability of port ${PortNumber} at address ${ipAddress} / $($ipAddress.AddressFamily)"
186-
187-
$tcpListener = Microsoft.PowerShell.Utility\New-Object System.Net.Sockets.TcpListener @($ipAddress, $PortNumber)
188-
$tcpListener.Start()
189-
$tcpListener.Stop()
192+
if (-not $IsLinux -and -not $IsMacOS) {
193+
return "\\.\pipe\$PipeName";
190194
}
191-
catch [System.Net.Sockets.SocketException] {
192-
$portAvailable = $false
193-
194-
# Check the SocketErrorCode to see if it's the expected exception
195-
if ($_.Exception.SocketErrorCode -eq [System.Net.Sockets.SocketError]::AddressAlreadyInUse) {
196-
Log "Port $PortNumber is in use."
197-
}
198-
else {
199-
Log "SocketException on port ${PortNumber}: $($_.Exception)"
200-
}
195+
else {
196+
# Windows uses NamedPipes where non-Windows platforms use Unix Domain Sockets.
197+
# the Unix Domain Sockets live in the tmp directory and are prefixed with "CoreFxPipe_"
198+
return (Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "CoreFxPipe_$PipeName")
201199
}
202200

203-
$portAvailable
204201
}
205202

206-
$portsInUse = @{}
207-
$rand = Microsoft.PowerShell.Utility\New-Object System.Random
208-
function Get-AvailablePort() {
209-
$triesRemaining = 10;
210-
211-
while ($triesRemaining -gt 0) {
212-
do {
213-
$port = $rand.Next($minPortNumber, $maxPortNumber)
214-
}
215-
while ($portsInUse.ContainsKey($port))
216-
217-
# Whether we succeed or fail, don't try this port again
218-
$portsInUse[$port] = 1
203+
# Returns True if it's a valid pipe name
204+
# A valid pipe name is a file that does not exist either
205+
# in the temp directory (macOS & Linux) or in the pipe directory (Windows)
206+
function Test-NamedPipeName {
207+
param(
208+
[Parameter(Mandatory=$true)]
209+
[ValidateNotNullOrEmpty()]
210+
[string]
211+
$PipeName
212+
)
219213

220-
Log "Checking port: $port, attempts remaining $triesRemaining --------------------"
221-
if ((Test-PortAvailability -PortNumber $port) -eq $true) {
222-
Log "Port: $port is available"
223-
return $port
224-
}
214+
$path = Get-NamedPipePath -PipeName $PipeName
215+
return -not (Test-Path $path)
216+
}
225217

226-
Log "Port: $port is NOT available"
227-
$triesRemaining--;
218+
function Set-NamedPipeMode {
219+
param(
220+
[Parameter(Mandatory=$true)]
221+
[ValidateNotNullOrEmpty()]
222+
[string]
223+
$PipeFile
224+
)
225+
chmod $DEFAULT_USER_MODE $PipeFile
226+
if ($IsLinux) {
227+
$mode = stat -c "%A" $PipeFile
228+
}
229+
else {
230+
$mode = stat -f "%A" $PipeFile
231+
}
232+
if ($mode -ne $DEFAULT_USER_MODE) {
233+
ExitWithError "Permissions to the pipe file were not set properly. Expected: $DEFAULT_USER_MODE Actual: $mode for file: $PipeFile"
228234
}
229-
230-
Log "Did not find any available ports!!"
231-
return $null
232235
}
233236

234237
# Add BundledModulesPath to $env:PSModulePath
@@ -251,21 +254,36 @@ try {
251254

252255
Microsoft.PowerShell.Core\Import-Module PowerShellEditorServices -ErrorAction Stop
253256

254-
# Locate available port numbers for services
255-
# There could be only one service on Stdio channel
256-
257-
$languageServiceTransport = $null
258-
$debugServiceTransport = $null
257+
# Locate available port numbers for services
258+
# There could be only one service on Stdio channel
259259

260-
if ($Stdio.IsPresent -and -not $DebugServiceOnly.IsPresent) { $languageServiceTransport = "Stdio" }
261-
elseif ($LanguageServicePipeName) { $languageServiceTransport = "NamedPipe"; $languageServicePipeName = "$LanguageServicePipeName" }
262-
elseif ($languageServicePort = Get-AvailablePort) { $languageServiceTransport = "Tcp" }
263-
else { ExitWithError "Failed to find an open socket port for language service." }
260+
$languageServiceTransport = $null
261+
$debugServiceTransport = $null
264262

265-
if ($Stdio.IsPresent -and $DebugServiceOnly.IsPresent) { $debugServiceTransport = "Stdio" }
266-
elseif ($DebugServicePipeName) { $debugServiceTransport = "NamedPipe"; $debugServicePipeName = "$DebugServicePipeName" }
267-
elseif ($debugServicePort = Get-AvailablePort) { $debugServiceTransport = "Tcp" }
268-
else { ExitWithError "Failed to find an open socket port for debug service." }
263+
if ($Stdio.IsPresent) {
264+
$languageServiceTransport = "Stdio"
265+
$debugServiceTransport = "Stdio"
266+
}
267+
else {
268+
$languageServiceTransport = "NamedPipe"
269+
$debugServiceTransport = "NamedPipe"
270+
if (-not $LanguageServicePipeName) {
271+
$LanguageServicePipeName = New-NamedPipeName
272+
}
273+
else {
274+
if (-not (Test-NamedPipeName -PipeName $LanguageServicePipeName)) {
275+
ExitWithError "Pipe name supplied is already taken: $LanguageServicePipeName"
276+
}
277+
}
278+
if (-not $DebugServicePipeName) {
279+
$DebugServicePipeName = New-NamedPipeName
280+
}
281+
else {
282+
if (-not (Test-NamedPipeName -PipeName $DebugServicePipeName)) {
283+
ExitWithError "Pipe name supplied is already taken: $DebugServicePipeName"
284+
}
285+
}
286+
}
269287

270288
if ($EnableConsoleRepl) {
271289
Write-Host "PowerShell Integrated Console`n"
@@ -281,11 +299,9 @@ try {
281299
-LogPath $LogPath `
282300
-LogLevel $LogLevel `
283301
-AdditionalModules $AdditionalModules `
284-
-LanguageServicePort $languageServicePort `
285-
-DebugServicePort $debugServicePort `
286302
-LanguageServiceNamedPipe $LanguageServicePipeName `
287303
-DebugServiceNamedPipe $DebugServicePipeName `
288-
-Stdio:$Stdio.IsPresent`
304+
-Stdio:($TransportType -eq "Stdio")`
289305
-BundledModulesPath $BundledModulesPath `
290306
-EnableConsoleRepl:$EnableConsoleRepl.IsPresent `
291307
-DebugServiceOnly:$DebugServiceOnly.IsPresent `
@@ -300,11 +316,18 @@ try {
300316
"debugServiceTransport" = $debugServiceTransport;
301317
};
302318

303-
if ($languageServicePipeName) { $resultDetails["languageServicePipeName"] = "$languageServicePipeName" }
304-
if ($debugServicePipeName) { $resultDetails["debugServicePipeName"] = "$debugServicePipeName" }
305-
306-
if ($languageServicePort) { $resultDetails["languageServicePort"] = $languageServicePort }
307-
if ($debugServicePort) { $resultDetails["debugServicePort"] = $debugServicePort }
319+
if ($LanguageServicePipeName) {
320+
$resultDetails["languageServicePipeName"] = Get-NamedPipePath -PipeName $LanguageServicePipeName
321+
if ($IsLinux -or $IsMacOS) {
322+
Set-NamedPipeMode -PipeFile $resultDetails["languageServicePipeName"]
323+
}
324+
}
325+
if ($DebugServicePipeName) {
326+
$resultDetails["debugServicePipeName"] = Get-NamedPipePath -PipeName $DebugServicePipeName
327+
if ($IsLinux -or $IsMacOS) {
328+
Set-NamedPipeMode -PipeFile $resultDetails["debugServicePipeName"]
329+
}
330+
}
308331

309332
# Notify the client that the services have started
310333
WriteSessionFile $resultDetails

src/PowerShellEditorServices.Channel.WebSocket/PowerShellEditorServices.Channel.WebSocket.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
<ItemGroup>
1616
<PackageReference Include="Newtonsoft.Json">
17-
<Version>9.0.1</Version>
17+
<Version>10.0.3</Version>
1818
</PackageReference>
1919
</ItemGroup>
2020

src/PowerShellEditorServices.Host/EditorServicesHost.cs

+10-17
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ public enum EditorServicesHostStatus
3131

3232
public enum EditorServiceTransportType
3333
{
34-
Tcp,
3534
NamedPipe,
3635
Stdio
3736
}
@@ -41,7 +40,6 @@ public class EditorServiceTransportConfig
4140
public EditorServiceTransportType TransportType { get; set; }
4241
/// <summary>
4342
/// Configures the endpoint of the transport.
44-
/// For Tcp it's an integer specifying the port.
4543
/// For Stdio it's ignored.
4644
/// For NamedPipe it's the pipe name.
4745
/// </summary>
@@ -75,10 +73,6 @@ public class EditorServicesHost
7573

7674
#region Properties
7775

78-
public int DebugServicePort { get; private set; }
79-
80-
public int LanguageServicePort { get; private set; }
81-
8276
public EditorServicesHostStatus Status { get; private set; }
8377

8478
#endregion
@@ -182,11 +176,13 @@ public void StartLogging(string logFilePath, LogLevel logLevel)
182176
}
183177

184178
/// <summary>
185-
/// Starts the language service with the specified TCP socket port.
179+
/// Starts the language service with the specified config.
186180
/// </summary>
187-
/// <param name="languageServicePort">The port number for the language service.</param>
188-
/// <param name="profilePaths">The object containing the profile paths to load for this session.</param>
189-
public void StartLanguageService(EditorServiceTransportConfig config, ProfilePaths profilePaths)
181+
/// <param name="config">The config that contains information on the communication protocol that will be used.</param>
182+
/// <param name="profilePaths">The profiles that will be loaded in the session.</param>
183+
public void StartLanguageService(
184+
EditorServiceTransportConfig config,
185+
ProfilePaths profilePaths)
190186
{
191187
this.profilePaths = profilePaths;
192188

@@ -253,9 +249,11 @@ await this.editorSession.PowerShellContext.ImportCommandsModule(
253249
}
254250

255251
/// <summary>
256-
/// Starts the debug service with the specified TCP socket port.
252+
/// Starts the debug service with the specified config.
257253
/// </summary>
258-
/// <param name="debugServicePort">The port number for the debug service.</param>
254+
/// <param name="config">The config that contains information on the communication protocol that will be used.</param>
255+
/// <param name="profilePaths">The profiles that will be loaded in the session.</param>
256+
/// <param name="useExistingSession">Determines if we will reuse the session that we have.</param>
259257
public void StartDebugService(
260258
EditorServiceTransportConfig config,
261259
ProfilePaths profilePaths,
@@ -458,11 +456,6 @@ private IServerListener CreateServiceListener(MessageProtocolType protocol, Edit
458456
{
459457
switch (config.TransportType)
460458
{
461-
case EditorServiceTransportType.Tcp:
462-
{
463-
return new TcpSocketServerListener(protocol, int.Parse(config.Endpoint), this.logger);
464-
}
465-
466459
case EditorServiceTransportType.Stdio:
467460
{
468461
return new StdioServerListener(protocol, this.logger);

src/PowerShellEditorServices.Host/PowerShellEditorServices.Host.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
<ItemGroup>
1717
<PackageReference Include="Newtonsoft.Json">
18-
<Version>9.0.1</Version>
18+
<Version>10.0.3</Version>
1919
</PackageReference>
2020
<PackageReference Include="Microsoft.PowerShell.SDK">
2121
<Version>6.0.0-alpha13</Version>

0 commit comments

Comments
 (0)