in sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java [1583:1751]
protected void decode() throws Exception {
// Decoding loop
for (;;) {
int authSize = inCipher != null ? inCipher.getAuthenticationTagSize() : 0;
boolean authMode = authSize > 0;
int macSize = inMac != null ? inMacSize : 0;
boolean etmMode = inMac != null && inMac.isEncryptThenMac();
// Wait for beginning of packet
if (decoderState == 0) {
// The read position should always be 0 at this point because we have compacted this buffer
assert decoderBuffer.rpos() == 0;
/*
* Note: according to RFC-4253 section 6:
*
* Implementations SHOULD decrypt the length after receiving the first 8 (or cipher block size whichever
* is larger) bytes
*
* However, we currently do not have ciphers with a block size of less than 8 we avoid un-necessary
* Math.max(minBufLen, 8) for each and every packet
*/
int minBufLen = etmMode || authMode ? Integer.BYTES : inCipherSize;
// If we have received enough bytes, start processing those
if (decoderBuffer.available() > minBufLen) {
if (authMode) {
// RFC 5647: packet length encoded in additional data
inCipher.updateAAD(decoderBuffer.array(), 0, Integer.BYTES);
} else if ((inCipher != null) && (!etmMode)) {
// Decrypt the first bytes so we can extract the packet length
inCipher.update(decoderBuffer.array(), 0, inCipherSize);
inBlocksCount.incrementAndGet();
}
// Read packet length
decoderLength = decoderBuffer.getInt();
/*
* Check packet length validity - we allow 8 times the minimum required packet length support in
* order to be aligned with some OpenSSH versions that allow up to 256k
*/
boolean lengthOK = true;
if ((decoderLength < SshConstants.SSH_PACKET_HEADER_LEN)
|| (decoderLength > (8 * SshConstants.SSH_REQUIRED_PAYLOAD_PACKET_LENGTH_SUPPORT))) {
log.warn("decode({}) Error decoding packet(invalid length): {}", this, decoderLength);
lengthOK = false;
} else if (inCipher != null
&& ((decoderLength + ((authMode || etmMode) ? 0 : Integer.BYTES)) % inCipherSize != 0)) {
log.warn("decode({}) Error decoding packet(padding; not multiple of {}): {}", this, inCipherSize,
decoderLength);
lengthOK = false;
}
if (!lengthOK) {
decoderBuffer.dumpHex(getSimplifiedLogger(), Level.FINEST,
"decode(" + this + ") invalid length packet", this);
// Mitigation against CVE-2008-5161 AKA CPNI-957037: make any disconnections due to decoding
// errors indistinguishable from failed MAC checks.
//
// If we disconnect here, a client may still deduce (since it sent only one block) that the
// length check failed. So we keep on requesting more data and fail later. OpenSSH actually
// discards the next 256kB of data, but in fact any number of bytes will do.
//
// Remember the exception, continue requiring an arbitrary number of bytes, and throw the
// exception later.
discarding = new SshException(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR,
"Invalid packet length: " + decoderLength);
decoderLength = decoderBuffer.available() + (2 + random.random(20)) * inCipherSize;
// Next larger multiple of the block size
decoderLength = ((decoderLength + (inCipherSize - 1)) / inCipherSize) * inCipherSize;
if (!authMode && !etmMode) {
decoderLength -= Integer.BYTES;
}
log.warn("decode({}) Invalid packet length; requesting {} bytes before disconnecting", this,
decoderLength - decoderBuffer.available());
}
// Ok, that's good, we can go to the next step
decoderState = 1;
} else {
// need more data
break;
}
// We have received the beginning of the packet
} else if (decoderState == 1) {
// The read position should always be after reading the packet length at this point
assert decoderBuffer.rpos() == Integer.BYTES;
// Check if the packet has been fully received
if (decoderBuffer.available() >= (decoderLength + macSize + authSize)) {
byte[] data = decoderBuffer.array();
if (authMode) {
inCipher.update(data, Integer.BYTES /* packet length is handled by AAD */, decoderLength);
int blocksCount = decoderLength / inCipherSize;
inBlocksCount.addAndGet(Math.max(1, blocksCount));
} else if (etmMode) {
validateIncomingMac(data, 0, decoderLength + Integer.BYTES);
if (inCipher != null) {
inCipher.update(data, Integer.BYTES /* packet length is unencrypted */, decoderLength);
int blocksCount = decoderLength / inCipherSize;
inBlocksCount.addAndGet(Math.max(1, blocksCount));
}
} else {
/*
* Decrypt the remaining of the packet - skip the block we already decoded in order to extract
* the packet length
*/
if (inCipher != null) {
int updateLen = decoderLength + Integer.BYTES - inCipherSize;
inCipher.update(data, inCipherSize, updateLen);
int blocksCount = updateLen / inCipherSize;
inBlocksCount.addAndGet(Math.max(1, blocksCount));
}
validateIncomingMac(data, 0, decoderLength + Integer.BYTES);
}
// Mitigation against CVE-2008-5161 AKA CPNI-957037. But is is highly unlikely that we pass the AAD or MAC checks above.
if (discarding != null) {
throw new SshException(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, discarding);
}
// Increment incoming packet sequence number
seqi = (seqi + 1L) & 0x0ffffffffL;
// Get padding
int pad = decoderBuffer.getUByte();
Buffer packet;
int wpos = decoderBuffer.wpos();
// Decompress if needed
if ((inCompression != null)
&& inCompression.isCompressionExecuted()
&& (isAuthenticated() || (!inCompression.isDelayed()))) {
if (uncompressBuffer == null) {
uncompressBuffer = new SessionWorkBuffer(this);
} else {
uncompressBuffer.forceClear(true);
}
decoderBuffer.wpos(decoderBuffer.rpos() + decoderLength - 1 - pad);
inCompression.uncompress(decoderBuffer, uncompressBuffer);
packet = uncompressBuffer;
} else {
decoderBuffer.wpos(decoderLength + Integer.BYTES - pad);
packet = decoderBuffer;
}
if (log.isTraceEnabled()) {
packet.dumpHex(getSimplifiedLogger(), Level.FINEST,
"decode(" + this + ") packet #" + seqi, this);
}
// Update counters used to track re-keying
inPacketsCount.incrementAndGet();
inBytesCount.addAndGet(packet.available());
// Process decoded packet
handleMessage(packet);
// Set ready to handle next packet
decoderBuffer.rpos(decoderLength + Integer.BYTES + macSize + authSize);
decoderBuffer.wpos(wpos);
decoderBuffer.compact();
decoderState = 0;
} else {
// need more data
break;
}
}
}
}