protected void decode()

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;
                }
            }
        }
    }