Skip to content

Commit 3a272e9

Browse files
committed
SSHD-1348: SFTP - gracefully handle zero-length SSH_FXP_READs
Fix inconsistency between checks for > 0 and >= 0, and ensure a zero- length read at or after EOF returns EOF, i.e., a SSH_FXP_STATUS reply with SSH_FX_EOF. Zero-length reads before EOF return a zero-length SSH_FXP_DATA reply.
1 parent 1ef64dc commit 3a272e9

3 files changed

Lines changed: 36 additions & 3 deletions

File tree

sshd-sftp/src/main/java/org/apache/sshd/sftp/server/FileHandle.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,21 @@ public int read(byte[] data, int doff, int length, long offset) throws IOExcepti
134134
@SuppressWarnings("resource")
135135
public int read(byte[] data, int doff, int length, long offset, AtomicReference<Boolean> eof) throws IOException {
136136
SeekableByteChannel channel = getFileChannel();
137+
if (length == 0 && offset >= channel.size()) {
138+
// Zero-length read at EOF. SFTP v3: draft RFC v2 says: "If [...] EOF is encountered before reading any
139+
// data, the server will respond with SSH_FXP_STATUS." (section 6.4). Later versions of the draft are
140+
// silent on this issue.
141+
//
142+
// See https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02#section-6.4
143+
if (eof != null) {
144+
eof.set(true);
145+
}
146+
return -1;
147+
}
137148
channel = channel.position(offset);
138149
int l = channel.read(ByteBuffer.wrap(data, doff, length));
139-
if (l > 0 && eof != null && l < length) {
140-
eof.set(channel.position() >= channel.size());
150+
if (eof != null) {
151+
eof.set(l < 0 || (l < length && channel.position() >= channel.size()));
141152
}
142153
return l;
143154
}

sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,7 @@ protected int doRead(
900900
session, id, Handle.safe(handle), h, offset, length);
901901
}
902902

903-
ValidateUtils.checkTrue(length > 0L, "Invalid read length: %d", length);
903+
ValidateUtils.checkTrue(length >= 0, "Invalid read length: %d", length);
904904
FileHandle fh = validateHandle(handle, h, FileHandle.class);
905905
SftpEventListener listener = getSftpEventListenerProxy();
906906
int readLen;

sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SimpleSftpClientTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.nio.file.Path;
2626
import java.util.Collections;
2727
import java.util.EnumSet;
28+
import java.util.concurrent.atomic.AtomicReference;
2829

2930
import org.apache.sshd.common.file.FileSystemFactory;
3031
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
@@ -115,6 +116,27 @@ void sftpProxyCalls() throws Exception {
115116
byte[] local = Files.readAllBytes(clientFile);
116117
assertArrayEquals(written, local, "Mismatched remote written data");
117118

119+
// Test zero reads
120+
try (SftpClient.CloseableHandle h = sftp.open(remoteFilePath, SftpClient.OpenMode.Read)) {
121+
AtomicReference<Boolean> eof = new AtomicReference<>();
122+
byte[] buf = {};
123+
int bytesRead = sftp.read(h, 0, buf, eof);
124+
assertEquals(0, bytesRead);
125+
assertFalse(eof.get() != null && eof.get().booleanValue());
126+
bytesRead = sftp.read(h, written.length, buf, eof);
127+
assertEquals(-1, bytesRead);
128+
assertTrue(eof.get() != null && eof.get().booleanValue());
129+
bytesRead = sftp.read(h, 1, buf, eof);
130+
assertEquals(0, bytesRead);
131+
assertFalse(eof.get() != null && eof.get().booleanValue());
132+
bytesRead = sftp.read(h, written.length + 10, buf, eof);
133+
assertEquals(-1, bytesRead);
134+
assertTrue(eof.get() != null && eof.get().booleanValue());
135+
bytesRead = sftp.read(h, written.length - 1, buf, eof);
136+
assertEquals(0, bytesRead);
137+
assertFalse(eof.get() != null && eof.get().booleanValue());
138+
}
139+
118140
try (SftpClient.CloseableHandle h = sftp.openDir(remoteFileDir)) {
119141
boolean matchFound = false;
120142
for (SftpClient.DirEntry entry : sftp.listDir(h)) {

0 commit comments

Comments
 (0)