Summary
sshj/Java-based SFTP clients (Cyberduck, and likely others) cannot list any directory against bssh-server. Right after the SFTP handshake, on the client's initial SSH_FXP_REALPATH (type 16) + SSH_FXP_STAT (type 17) of the home directory, the server drops the connection with Session error error=SshEncoding: length invalid. The OpenSSH sftp CLI and FileZilla work against the exact same server, config, user, and directory. Reproduces with bssh-server v2.2.3.
Root Cause (corrects the original hypothesis)
The original report supposed the server mis-encodes the outgoing REALPATH/STAT response (SSH_FXP_NAME / SSH_FXP_ATTRS). A code audit shows that is not the cause:
SshEncoding: length invalid is not produced anywhere in the SFTP crate. It is russh::Error::SshEncoding(ssh_encoding::Error::Length) (crates/bssh-russh/src/lib_inner.rs:232-233, rendered as "length invalid"), surfaced by bssh at src/server/mod.rs:369-374. It is raised only through the map_err!(...)? calls in russh's incoming packet parser (crates/bssh-russh/src/server/encrypted.rs).
- The SFTP NAME/ATTRS encoders (
crates/bssh-russh-sftp/src/protocol/{name,attrs,file,file_attrs}.rs, ser.rs) write byte-correct u32 length prefixes, and the outgoing russh channel framing (crates/bssh-russh/src/session.rs:441-500) is correct. Crucially, the realpath/stat handler output (src/server/sftp.rs:983-1128) is client-independent: an outgoing-encode bug would break OpenSSH and FileZilla too, which contradicts the observation that they work.
So the error is an incoming russh decode: ssh_encoding::Error::Length fires when a length-prefixed field (String::decode / Bytes::decode / u32::decode) declares more bytes than remain in the packet reader, i.e. russh's (now strict) parser reads a field at an offset where sshj's byte layout does not match what russh expects. The most likely site is the CHANNEL_DATA handler's Bytes::decode at crates/bssh-russh/src/server/encrypted.rs:842, but any strict handler in that file qualifies (CHANNEL_REQUEST field reads :944-1183, or an sshj-initiated rekey KEXINIT hitting the name-list limits in crates/bssh-russh/src/helpers.rs:38-40,96-97).
Likely regression window: commit 3bcd56f ("update: sync russh/russh-sftp forks to upstream and unify ssh-key (#207)") introduced strict fixed-layout parsing plus ensure_end (crates/bssh-russh/src/parsing.rs:13-22) and the LimitedString/NameList limits, and touched encrypted.rs, helpers.rs, the whole SFTP crate, and src/server/sftp.rs. (Note: ensure_end returns TrailingData, not Length, so the specific length invalid points at a *::decode overrun, not trailing bytes.)
Proposed Solution
- Identify the exact overrun site (runtime evidence, static analysis cannot see sshj's bytes). Temporarily log the message id byte and raw payload length just before the
match msg in server_read_authenticated (crates/bssh-russh/src/server/encrypted.rs, near :803), or instrument each map_err!(…decode…)? site, then reproduce with Cyberduck/sshj to capture which decode returns Err(Length) and on which SSH message.
- Fix the parser to accept the sshj-legal message. Depending on the finding: relax an over-strict fixed-layout assumption, correctly consume an optional/variable field sshj includes, or raise/adjust a limit if a rekey name-list is the trigger. Preserve the hardening intent of
#207 for genuinely malformed packets while accepting well-formed sshj traffic.
- Add an interop regression test exercising the REALPATH+STAT-then-OPENDIR sequence an sshj client sends, so this class of client stays covered.
Implementation Notes
- Error definition and surfacing:
crates/bssh-russh/src/lib_inner.rs:232-233; src/server/mod.rs:369-374; bssh's SshHandler::Error = anyhow::Error (src/server/handler.rs:250-251) preserves the russh Display.
- Incoming parser (fault domain):
crates/bssh-russh/src/server/encrypted.rs:797-1365; explicit Error::Length sites at crates/bssh-russh/src/helpers.rs:38-40,96-97; ensure_end at crates/bssh-russh/src/parsing.rs:13-22.
- Outgoing path (ruled out):
crates/bssh-russh/src/session.rs:441-500; SFTP encoders under crates/bssh-russh-sftp/src/protocol/ and ser.rs.
- The SFTP request parser is tolerant (each SFTP packet is independently length-framed in
crates/bssh-russh-sftp/src/utils.rs:12-25, and the SFTP stream is decoupled from SSH-packet decoding), so trailing bytes in an SFTP-level REALPATH request cannot cause this; the fault is at the SSH transport layer.
- Reproduction harness: a Java sshj snippet (or Cyberduck) doing connect → REALPATH(".") → STAT is enough to trigger it deterministically; pair it with the instrumentation in step 1.
Acceptance Criteria
Related
Original Suggestion
Title: SFTP: Cyberduck (sshj) connection dropped on initial REALPATH/STAT — server errors SshEncoding: length invalid
Summary
Cyberduck (and likely other sshj/Java-based SFTP clients) cannot list any directory against bssh-server: right after the SFTP handshake, on the client's initial REALPATH + STAT of the home directory, the server drops the connection with:
Session error error=SshEncoding: length invalid
The OpenSSH sftp CLI and FileZilla work fine against the exact same server, config, user, and directory — so this is specific to the request/response pattern that sshj-based clients use.
Client-side symptom (Cyberduck)
디렉터리 sion의 목록을 얻어오는데 실패하였습니다. (127.0.0.1 – SFTP)
Broken transport; encountered EOF. The connection attempt was rejected.
The server may be down, or your network may not be properly configured.
Server-side log (bssh-server run -vv)
INFO bssh::server::handler: Starting SFTP session user=sion peer=127.0.0.1:54766 home=/Users/sion
DEBUG bssh::server::sftp: Creating SFTP handler user=sion chroot=None cwd=/Users/sion
INFO bssh::server::sftp: SFTP session initialized user=sion version=3
DEBUG bssh_russh_sftp::protocol: packet type 16 # SSH_FXP_REALPATH
DEBUG bssh_russh_sftp::protocol: packet type 17 # SSH_FXP_STAT
DEBUG russh::server: Connection closed with error
DEBUG bssh_russh_sftp::server: sftp stream ended
ERROR bssh::server: Session error error=SshEncoding: length invalid
The connection dies immediately after the REALPATH (16) / STAT (17) exchange — before any OPENDIR/READDIR. So the server appears to mis-encode the REALPATH/STAT response (an SSH_FXP_NAME / SSH_FXP_ATTRS packet) such that the outgoing SSH channel-data length is invalid, and russh's transport encoder aborts the connection.
Repro
bssh-server run -c config.yaml -D (no chroot: sftp.root unset; auth publickey or password; SFTP protocol v3 negotiated).
- Connect with Cyberduck (SFTP,
127.0.0.1, the configured user) → fails as above.
- Connect with OpenSSH
sftp or FileZilla to the same server → works (list, get, put, rename, etc. all fine).
Impact
Many GUI/mobile SFTP clients are sshj/JSch-based (Cyberduck, and others). If they all hit this, bssh-server cannot be a drop-in SFTP endpoint for those clients.
Environment
bssh-server 2.2.3 (bssh-server-macos-aarch64)
- SFTP protocol version 3
- Failing client: Cyberduck (sshj); working clients: OpenSSH
sftp, FileZilla
Summary
sshj/Java-based SFTP clients (Cyberduck, and likely others) cannot list any directory against
bssh-server. Right after the SFTP handshake, on the client's initialSSH_FXP_REALPATH(type 16) +SSH_FXP_STAT(type 17) of the home directory, the server drops the connection withSession error error=SshEncoding: length invalid. The OpenSSHsftpCLI and FileZilla work against the exact same server, config, user, and directory. Reproduces withbssh-serverv2.2.3.Root Cause (corrects the original hypothesis)
The original report supposed the server mis-encodes the outgoing REALPATH/STAT response (
SSH_FXP_NAME/SSH_FXP_ATTRS). A code audit shows that is not the cause:SshEncoding: length invalidis not produced anywhere in the SFTP crate. It isrussh::Error::SshEncoding(ssh_encoding::Error::Length)(crates/bssh-russh/src/lib_inner.rs:232-233, rendered as "length invalid"), surfaced bybsshatsrc/server/mod.rs:369-374. It is raised only through themap_err!(...)?calls in russh's incoming packet parser (crates/bssh-russh/src/server/encrypted.rs).crates/bssh-russh-sftp/src/protocol/{name,attrs,file,file_attrs}.rs,ser.rs) write byte-correct u32 length prefixes, and the outgoing russh channel framing (crates/bssh-russh/src/session.rs:441-500) is correct. Crucially, therealpath/stathandler output (src/server/sftp.rs:983-1128) is client-independent: an outgoing-encode bug would break OpenSSH and FileZilla too, which contradicts the observation that they work.So the error is an incoming russh decode:
ssh_encoding::Error::Lengthfires when a length-prefixed field (String::decode/Bytes::decode/u32::decode) declares more bytes than remain in the packet reader, i.e. russh's (now strict) parser reads a field at an offset where sshj's byte layout does not match what russh expects. The most likely site is theCHANNEL_DATAhandler'sBytes::decodeatcrates/bssh-russh/src/server/encrypted.rs:842, but any strict handler in that file qualifies (CHANNEL_REQUESTfield reads:944-1183, or an sshj-initiated rekeyKEXINIThitting the name-list limits incrates/bssh-russh/src/helpers.rs:38-40,96-97).Likely regression window: commit
3bcd56f("update: sync russh/russh-sftp forks to upstream and unify ssh-key (#207)") introduced strict fixed-layout parsing plusensure_end(crates/bssh-russh/src/parsing.rs:13-22) and theLimitedString/NameListlimits, and touchedencrypted.rs,helpers.rs, the whole SFTP crate, andsrc/server/sftp.rs. (Note:ensure_endreturnsTrailingData, notLength, so the specificlength invalidpoints at a*::decodeoverrun, not trailing bytes.)Proposed Solution
match msginserver_read_authenticated(crates/bssh-russh/src/server/encrypted.rs, near:803), or instrument eachmap_err!(…decode…)?site, then reproduce with Cyberduck/sshj to capture whichdecodereturnsErr(Length)and on which SSH message.#207for genuinely malformed packets while accepting well-formed sshj traffic.Implementation Notes
crates/bssh-russh/src/lib_inner.rs:232-233;src/server/mod.rs:369-374;bssh'sSshHandler::Error = anyhow::Error(src/server/handler.rs:250-251) preserves the russhDisplay.crates/bssh-russh/src/server/encrypted.rs:797-1365; explicitError::Lengthsites atcrates/bssh-russh/src/helpers.rs:38-40,96-97;ensure_endatcrates/bssh-russh/src/parsing.rs:13-22.crates/bssh-russh/src/session.rs:441-500; SFTP encoders undercrates/bssh-russh-sftp/src/protocol/andser.rs.crates/bssh-russh-sftp/src/utils.rs:12-25, and the SFTP stream is decoupled from SSH-packet decoding), so trailing bytes in an SFTP-level REALPATH request cannot cause this; the fault is at the SSH transport layer.Acceptance Criteria
map_err!(…decode…)?site and the offending SSH message from sshj are identified and documented.bssh-serverwithout the connection being dropped.sftpand FileZilla continue to work (no interop regression), and the#207hardening remains effective against genuinely malformed packets.Related
#207.Original Suggestion
Title: SFTP: Cyberduck (sshj) connection dropped on initial REALPATH/STAT — server errors
SshEncoding: length invalidSummary
Cyberduck (and likely other sshj/Java-based SFTP clients) cannot list any directory against
bssh-server: right after the SFTP handshake, on the client's initialREALPATH+STATof the home directory, the server drops the connection with:The OpenSSH
sftpCLI and FileZilla work fine against the exact same server, config, user, and directory — so this is specific to the request/response pattern that sshj-based clients use.Client-side symptom (Cyberduck)
Server-side log (
bssh-server run -vv)The connection dies immediately after the
REALPATH(16) /STAT(17) exchange — before anyOPENDIR/READDIR. So the server appears to mis-encode theREALPATH/STATresponse (anSSH_FXP_NAME/SSH_FXP_ATTRSpacket) such that the outgoing SSH channel-data length is invalid, and russh's transport encoder aborts the connection.Repro
bssh-server run -c config.yaml -D(no chroot:sftp.rootunset; auth publickey or password; SFTP protocol v3 negotiated).127.0.0.1, the configured user) → fails as above.sftpor FileZilla to the same server → works (list, get, put, rename, etc. all fine).Impact
Many GUI/mobile SFTP clients are sshj/JSch-based (Cyberduck, and others). If they all hit this,
bssh-servercannot be a drop-in SFTP endpoint for those clients.Environment
bssh-server2.2.3 (bssh-server-macos-aarch64)sftp, FileZilla