Subj : Terminal server (SSH): a single malformed inbound packet drops the nod To : GitLab issue in main/sbbs From : Rob Swindell Date : Mon Aug 10 2026 22:48:04 open https://gitlab.synchro.net/main/sbbs/-/issues/1225 ## Symptom A terminal-server node session over SSH was torn down mid-door with no user action, no idle timeout, and no crash of the external program. The client reconnected 11 seconds later on a fresh source port. ## Log Node 11, SyncTERM client (CTerm 1.332) over SSH, running a graphics-heavy door that streams sixel frames. Door name redacted as ``. ``` 22:33:43.29 Executing external: /sbbs/node11/DOOR32.SYS 22:33:45.52 : frames as sixel 22:33:46.18 SSH WARNING 'Invalid packet (236), expected SSH_MSG_CHANNEL_DATA (94)' (-32) popping data from input_thread 22:33:46.18 input thread terminated (received 123278 bytes in 13151 blocks) 22:33:46.18 output thread terminated (sent 179433865 bytes in 125287 blocks, 1432 average, 0 short) 22:33:46.18 passthru thread terminated 22:33:52.80 external Timeout waiting for output buffer to empty 22:33:52.94 logout initiated ``` Everything from 22:33:46.18 onward is fallout: the door kept rendering into a dead socket for 6.6 seconds, hit the output-buffer drain timeout, and then exited through postxtrn normally. No core file was written for it. ## What happens cryptlib decrypted an inbound SSH packet header carrying message type 236 where channel data was expected, and returned CRYPT_ERROR_BADDATA (-32). In `src/sbbs3/main.cpp`, `input_thread()` treats every `crypt_pop_channel_data()` error other than CRYPT_ERROR_COMPLETE and CRYPT_ERROR_TIMEOUT as fatal: it logs via GCES and breaks out of the loop, which ends the input thread and takes the node down with it. So a single bad packet is unconditionally fatal. That is defensible for SSH -- once the transport is out of frame there is nothing to resync to -- but it is worth confirming that is the intent rather than an accident of the error-handling structure, because the same path also catches errors that are not framing failures. ## Why the packet type is suspicious In `3rdp/src/cl/session/ssh2_rd.c`, `readPacketSSH2()` peeks the pad length and packet type out of the decrypted header and calls `checkPacketValid()` *before* the packet MAC is verified -- the "we've passed the crypto stage" comment sits after that check. So a bogus type here does not mean the peer authentically sent message 236; it is what a desynced or corrupted stream looks like at the first decrypted block. 236 is in SSH's private-use message range (192-255). Nothing in the protocol sends it, and cryptlib does not implement SSH rekeying, so a mishandled key re-exchange is not an explanation either. The preceding padding-length and packet-length checks both passed, which constrains the corruption only weakly -- most random bytes pass those. ## Frequency Exactly one occurrence of this message across three rotated log files (roughly 15 GB, Aug 2 through Aug 10). The same client had pushed 517 MB through the same code path in the preceding session without incident. This is not a recurring failure, and no reproduction is known. ## Notes on what it is not - Not a crash of the external program: no core, normal startup logging, clean exit through postxtrn afterwards. - Not an inactivity timeout. - Not a door writing to or reading from an inherited descriptor it should not touch: the door in question obtains its socket through the same shared DOOR32.SYS helper every other door here uses, and no other door has ever produced this. - TCP would have delivered the bytes in order, so the corruption is inside one of the two cryptlib endpoints rather than on the wire. ## Suggested next steps Filed to have a record rather than because a fix is obvious. To make progress if it recurs: - raise SSHErrorLevel on the terminal server for more cryptlib detail - capture the TCP stream for the affected client, which at least settles whether the byte stream itself was intact - consider logging the surrounding decrypted header bytes on BADDATA, so a recurrence shows whether the stream is offset by a fixed amount (a framing bug) or is garbage (a keystream or memory problem) Possibly related, both closed: #302 (cryptlib internal consistency check failures affecting SSH/TLS). -- *Authored by Claude (Claude Code), on behalf of @rswindell* --- SBBSecho 3.37-Linux * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705) .