Skip to content

Client-initiated rekey breaks the session: byte-limit rekey kills in-flight SFTP, interval rekey hangs an idle session #231

Description

@GlassOnTin

While integrating sshlib 0.3.1 as an SSH engine in Haven (per the migration discussed in GlassHaven/Haven#58), a capability spike against Apache MINA sshd 2.19.0 found client-initiated rekeying broken in both trigger modes:

1. Byte-limit rekey during in-flight SFTP writes kills the channel

val client = SshClient(SshClientConfig {
    host = "127.0.0.1"; port = server.port
    hostKeyVerifier = trustAll
    rekeyBytesLimit = 256L * 1024
})
client.connect()                      // Success
client.authenticatePassword(...)      // Success
// SFTP write loop, 32 KiB chunks, 2 MiB total:
sftp.write(handle, offset, chunk)     // fails right past the limit:
// SftpResult.IoError(ChannelClosedException("SSH channel closed before complete SFTP packet"))
// at offset ~294912 (= just after 256 KiB)

2. Interval rekey wedges an idle session — a hang, not an error

val client = SshClient(SshClientConfig {
    ...
    rekeyIntervalMs = 1_000L
})
client.connect(); client.authenticatePassword(...)   // Success
delay(2_500)                                         // ≥2 interval rekeys while idle
sftp.opendir("/")                                    // suspends forever (no error, no timeout)

With the defaults (1 GiB / 1 h) this means any single transfer larger than 1 GiB dies mid-flight, and any session idle for more than an hour hangs on its next operation.

Both repros are plain JUnit tests against an in-process SshServer.setUpDefaultServer() (password auth, SftpSubsystemFactory, VirtualFileSystemFactory); the identical 2 MiB transfer passes with rekey thresholds left at defaults (no rekey triggered), so the rekey is the isolated variable. I haven't probed server-initiated rekey separately. Happy to contribute the fix if you can point me at where you'd want the in-flight-packet coordination to live — my guess is the outgoing packet queue isn't paused/drained around SSH_MSG_NEWKEYS, and the post-rekey wedge smells like a read loop stuck on stale cipher state.

The full probe suite (these two plus 16 other capability probes) lives in Haven at core/ssh/src/test/kotlin/sh/haven/core/ssh/sshlib/SshlibCapabilitySpikeTest.kt — the two rekey probes are written to fail when this is fixed, so we notice and drop our mitigation (we currently set both thresholds to Long.MAX_VALUE / 2).

Tested: sshlib 0.3.1 from Maven Central, JVM 21, MINA sshd 2.19.0, BC 1.84.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions