diff --git a/ssh/channel.go b/ssh/channel.go index cc0bb7a..00e4ceb 100644 --- a/ssh/channel.go +++ b/ssh/channel.go @@ -530,7 +530,17 @@ func (ch *channel) Reject(reason RejectionReason, message string) error { Language: "en", } ch.decided = true - return ch.sendMessage(reject) + err := ch.sendMessage(reject) + + // Remove the channel from the mux to prevent memory leaks. + // Do not call ch.close() here: no goroutine holds a reference to a + // rejected channel's internal channels (msg, incomingRequests), so + // removing it from chanList is sufficient for GC. Calling close() + // would race with the mux loop goroutine (handlePacket or dropAll), + // causing a panic from closing an already-closed channel. + ch.mux.chanList.remove(ch.localId) + + return err } func (ch *channel) Read(data []byte) (int, error) { diff --git a/ssh/mux_test.go b/ssh/mux_test.go index 21f0ac3..95874fa 100644 --- a/ssh/mux_test.go +++ b/ssh/mux_test.go @@ -825,6 +825,50 @@ func TestMuxChannelWindowDeferredUpdates(t *testing.T) { } } +func TestMuxChannelRejectRemovesFromMux(t *testing.T) { + serverMux, clientMux := muxPair() + defer serverMux.Close() + defer clientMux.Close() + + var wg sync.WaitGroup + t.Cleanup(wg.Wait) + wg.Add(1) + + go func() { + defer wg.Done() + + // The server waits for the channel creation request + newCh, ok := <-serverMux.incomingChannels + if !ok { + t.Error("failed to accept channel") + return + } + ch := newCh.(*channel) + + if serverMux.chanList.getChan(ch.localId) == nil { + t.Errorf("channel %d is not in the chanList before Reject", ch.localId) + } + + if err := ch.Reject(Prohibited, "rejecting this channel"); err != nil { + t.Errorf("Reject failed: %v", err) + } + + if serverMux.chanList.getChan(ch.localId) != nil { + t.Errorf("channel %d is still in the chanList after Reject", ch.localId) + } + }() + + _, _, err := clientMux.OpenChannel("test_leak", nil) + + if err == nil { + t.Fatal("expected an error (channel rejected), but got nil") + } + + if _, ok := err.(*OpenChannelError); !ok { + t.Errorf("expected *OpenChannelError, got: %T", err) + } +} + // Don't ship code with debug=true. func TestDebug(t *testing.T) { if debugMux {