Skip to content

Commit ba678e1

Browse files
committed
Fix hierynomus#805: Prevent CHANNEL_CLOSE to be sent between Channel.isOpen and a Transport.write call
Otherwise, a disconnect with a "packet referred to nonexistent channel" message can occur. This particularly happens when the transport.Reader thread passes an eof from the server to the ChannelInputStream, the reading library-user thread returns, and closes the channel at the same time as the transport.Reader thread receives the subsequent CHANNEL_CLOSE from the server.
1 parent ec467a3 commit ba678e1

File tree

2 files changed

+45
-8
lines changed

2 files changed

+45
-8
lines changed

src/main/java/net/schmizz/sshj/connection/channel/AbstractChannel.java

+22
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,25 @@ public boolean isOpen() {
304304
}
305305
}
306306

307+
// Prevent CHANNEL_CLOSE to be sent between isOpen and a Transport.write call in the runnable, otherwise
308+
// a disconnect with a "packet referred to nonexistent channel" message can occur.
309+
//
310+
// This particularly happens when the transport.Reader thread passes an eof from the server to the
311+
// ChannelInputStream, the reading library-user thread returns, and closes the channel at the same time as the
312+
// transport.Reader thread receives the subsequent CHANNEL_CLOSE from the server.
313+
boolean whileOpen(TransportRunnable runnable) throws TransportException, ConnectionException {
314+
openCloseLock.lock();
315+
try {
316+
if (isOpen()) {
317+
runnable.run();
318+
return true;
319+
}
320+
} finally {
321+
openCloseLock.unlock();
322+
}
323+
return false;
324+
}
325+
307326
private void gotChannelRequest(SSHPacket buf)
308327
throws ConnectionException, TransportException {
309328
final String reqType;
@@ -427,5 +446,8 @@ public String toString() {
427446
+ rwin + " >";
428447
}
429448

449+
public interface TransportRunnable {
450+
void run() throws TransportException, ConnectionException;
451+
}
430452

431453
}

src/main/java/net/schmizz/sshj/connection/channel/ChannelOutputStream.java

+23-8
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
*/
3131
public final class ChannelOutputStream extends OutputStream implements ErrorNotifiable {
3232

33-
private final Channel chan;
33+
private final AbstractChannel chan;
3434
private final Transport trans;
3535
private final Window.Remote win;
3636

@@ -47,6 +47,12 @@ private final class DataBuffer {
4747

4848
private final SSHPacket packet = new SSHPacket(Message.CHANNEL_DATA);
4949
private final Buffer.PlainBuffer leftOvers = new Buffer.PlainBuffer();
50+
private final AbstractChannel.TransportRunnable packetWriteRunnable = new AbstractChannel.TransportRunnable() {
51+
@Override
52+
public void run() throws TransportException {
53+
trans.write(packet);
54+
}
55+
};
5056

5157
DataBuffer() {
5258
headerOffset = packet.rpos();
@@ -99,8 +105,9 @@ boolean flush(int bufferSize, boolean canAwaitExpansion) throws TransportExcepti
99105
if (leftOverBytes > 0) {
100106
leftOvers.putRawBytes(packet.array(), packet.wpos(), leftOverBytes);
101107
}
102-
103-
trans.write(packet);
108+
if (!chan.whileOpen(packetWriteRunnable)) {
109+
throwStreamClosed();
110+
}
104111
win.consume(writeNow);
105112

106113
packet.rpos(headerOffset);
@@ -119,7 +126,7 @@ boolean flush(int bufferSize, boolean canAwaitExpansion) throws TransportExcepti
119126

120127
}
121128

122-
public ChannelOutputStream(Channel chan, Transport trans, Window.Remote win) {
129+
public ChannelOutputStream(AbstractChannel chan, Transport trans, Window.Remote win) {
123130
this.chan = chan;
124131
this.trans = trans;
125132
this.win = win;
@@ -157,17 +164,22 @@ private void checkClose() throws SSHException {
157164
if (error != null) {
158165
throw error;
159166
} else {
160-
throw new ConnectionException("Stream closed");
167+
throwStreamClosed();
161168
}
162169
}
163170
}
164171

165172
@Override
166173
public synchronized void close() throws IOException {
167174
// Not closed yet, and underlying channel is open to flush the data to.
168-
if (!closed.getAndSet(true) && chan.isOpen()) {
169-
buffer.flush(false);
170-
trans.write(new SSHPacket(Message.CHANNEL_EOF).putUInt32(chan.getRecipient()));
175+
if (!closed.getAndSet(true)) {
176+
chan.whileOpen(new AbstractChannel.TransportRunnable() {
177+
@Override
178+
public void run() throws TransportException, ConnectionException {
179+
buffer.flush(false);
180+
trans.write(new SSHPacket(Message.CHANNEL_EOF).putUInt32(chan.getRecipient()));
181+
}
182+
});
171183
}
172184
}
173185

@@ -188,4 +200,7 @@ public String toString() {
188200
return "< ChannelOutputStream for Channel #" + chan.getID() + " >";
189201
}
190202

203+
private static void throwStreamClosed() throws ConnectionException {
204+
throw new ConnectionException("Stream closed");
205+
}
191206
}

0 commit comments

Comments
 (0)