ShellStream.Dispose deadlocks when the server closes the channel at the same time
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 69/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- csharp
- Ambito
- backend, networking
Direzione di ricerca
Start by reading src/Renci.SshNet/ShellStream.cs around Dispose(bool) and Channel_Closed, then trace the Close paths in src/Renci.SshNet/Channels/Channel.cs. Check how locks and event handlers interact in the reported race, and add a regression test that reproduces concurrent disposal and remote channel closure. Done means the test completes reliably without deadlocking.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Version
SSH.NET 2026.0.0 (tag 2026.0.0, 7b2fd3d). The same code is on develop today. .NET 10, Linux x64 and arm64.
Summary
Disposing a ShellStream while the server closes its channel can deadlock forever. The two threads take the same two locks in opposite order, and neither wait has a timeout:
| Thread | Holds | Waits for |
|---|---|---|
Caller: ShellStream.Dispose() |
ShellStream._sync |
Channel._messagingLock |
Session listener: SSH_MSG_CHANNEL_CLOSE received |
Channel._messagingLock |
ShellStream._sync |
The two paths
Caller thread, disposing the stream:
ShellStream.Dispose(bool)takeslock (_sync)(src/Renci.SshNet/ShellStream.cs:970).- Still inside that lock it calls
_channel.Dispose()(ShellStream.cs:986). Channel.Dispose(bool)callsClose()(src/Renci.SshNet/Channels/Channel.cs:844), which takeslock (_messagingLock)(Channel.cs:534).
Listener thread, handling the server's SSH_MSG_CHANNEL_CLOSE:
OnChannelClosecallsOnClose()(Channel.cs:698), which callsClose()(Channel.cs:426) and takeslock (_messagingLock)(Channel.cs:534).- Still inside that lock it raises
Closed(Channel.cs:578). ShellStream.Channel_Closedhandles it by callingDispose()(ShellStream.cs:1006), which waits forlock (_sync)(ShellStream.cs:970).
If the caller enters step 1 of its path before the listener reaches step 3 of its own, each thread holds the lock the other one needs. A stack dump of a hung process shows exactly these two frames.
Repro
The race is timing-dependent. A simplified loop against a real OpenSSH server (a sketch of our test, not run as written here):
for (var i = 0; i < 100; i++)
{
using var client = new SshClient(host, port, user, password);
client.Connect();
var stream = client.CreateShellStream("xterm", 80, 24, 0, 0, 4096);
stream.WriteLine("exit"); // the server closes the channel shortly after
stream.Dispose(); // races with the listener's Channel_Closed -> Dispose
}
In our test suite this hung 4 of 15 runs: a test class whose Dispose() disposed the ShellStream while the remote shell exited. The process never recovered; CI killed it after an hour.
Workaround we use
Call SshClient.Disconnect() before disposing the ShellStream. That stops the listener thread, so no Closed event can arrive while Dispose holds _sync. With it, 12 of 12 runs passed.
Possible fixes
- In
ShellStream.Dispose, detach the handlers and dispose_channeloutsidelock (_sync): take the lock only to set_disposed, then release it before calling_channel.Dispose(). - Or, in
Channel.Close, raiseClosedafter leavinglock (_messagingLock), so that no user code runs while that lock is held.
- Lingua principale
- C#
- Stelle
- 4.4k
- Fork
- 988
- Merge medio
- 4g 22h
- PR unite (30g)
- 5
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di sshnet/SSH.NET
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 55/100
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 45/100
-
ShellStream.Expect over-discards on undecodable bytes, driving ArrayBuffer.ActiveLength negativeAperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 52/100
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 68/100
-
Using SshClient on Linux under Wine throws System.Security.Cryptography.CryptographicExceptionAperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
Tutte le issue di sshnet/SSH.NET
Issue simili
-
area-multitenancy area-security p1-recommended priority-high technical-debt
Difficoltà 2/5 Meno di un'ora Idoneità per principianti 75/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
-
help wanted needs-unity ready for a contributor
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
eyalgolan/statecraft-israel#1542 ·
I maintainer di solito rispondono entro 1 giorno
-
kind/enhancement Provider Upgrade pulumi/pulumi-azure R1 Task
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
pulumi/pulumi-azure#3669 ·
-
[BUG] FromCString causes 1 byte OOB read in native codeForse già presa Una pull request collegata a questa issue è aperta o già unita. Apertatype/bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
I maintainer di solito rispondono entro 1 giorno