📄 ssh2_rw.c
字号:
Secure mode: length < ID_SIZE + PADLENGTH_SIZE +
SSH2_MIN_PADLENGTH_SIZE. In this case there's an (implicit) MAC
present so the packet (length + extraLength) will always be
larger than the (remaining) data that we've already read. For
this case we need to check that the data payload is at least as
long as the minimum-length packet */
sMemConnect( &stream, headerBufPtr, MIN_PACKET_SIZE );
length = readUint32( &stream );
assert( SSH2_HEADER_REMAINDER_SIZE == MIN_PACKET_SIZE - LENGTH_SIZE );
if( sessionInfoPtr->flags & SESSION_ISSECURE_READ )
{
/* The MAC size isn't included in the packet length so we have to
add it manually */
extraLength = sessionInfoPtr->authBlocksize;
}
if( cryptStatusError( length ) || \
length + extraLength < SSH2_HEADER_REMAINDER_SIZE || \
length < ID_SIZE + PADLENGTH_SIZE + SSH2_MIN_PADLENGTH_SIZE || \
length + extraLength >= sessionInfoPtr->receiveBufSize )
{
sMemDisconnect( &stream );
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Invalid packet length %ld, should be %d...%d",
cryptStatusError( length ) ? 0 : length,
ID_SIZE + PADLENGTH_SIZE + SSH2_MIN_PADLENGTH_SIZE,
sessionInfoPtr->receiveBufSize - extraLength ) );
}
assert( ( isHandshake && sessionInfoPtr->receiveBufPos == 0 ) || \
!isHandshake );
status = sread( &stream, sessionInfoPtr->receiveBuffer + \
sessionInfoPtr->receiveBufPos,
SSH2_HEADER_REMAINDER_SIZE );
sMemDisconnect( &stream );
if( cryptStatusError( status ) )
return( status );
*packetLength = length;
*packetExtraLength = extraLength;
return( CRYPT_OK );
}
/* Read an SSHv2 handshake packet. This function is only used during the
handshake phase (the data transfer phase has its own read/write code) so
we can perform some special-case handling based on this. In particular
we know that packets will always be read into the start of the receive
buffer so we don't have to perform special buffer-space-remaining
calculations */
int readHSPacketSSH2( SESSION_INFO *sessionInfoPtr, int expectedType,
const int minPacketSize )
{
SSH_INFO *sshInfo = sessionInfoPtr->sessionSSH;
long length;
int padLength = 0, packetType, minPacketLength = minPacketSize;
int iterationCount = 0, status;
assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
assert( expectedType >= SSH2_MSG_DISCONNECT && \
expectedType <= SSH2_MSG_SPECIAL_REQUEST );
assert( minPacketSize >= 1 && minPacketSize < 1024 );
/* Alongside the expected handshake packets the server can send us all
sorts of no-op messages, ranging from explicit no-ops
(SSH2_MSG_IGNORE) through to general chattiness (SSH2_MSG_DEBUG,
SSH2_MSG_USERAUTH_BANNER). Because we can receive any quantity of
these at any time, we have to run the receive code in a (bounds-
checked) loop to strip them out */
do
{
int extraLength;
/* Read the SSHv2 handshake packet header:
uint32 length (excluding MAC size)
byte padLen
[ byte type - checked but not removed ]
byte[] data
byte[] padding
byte[] MAC
The reason why the length and pad length precede the packet type
and other information is that these two fields are part of the
SSHv2 transport layer while the type and payload are seen as part
of the connection layer, although the different RFCs tend to mix
them up quite thoroughly */
assert( sessionInfoPtr->receiveBufPos == 0 && \
sessionInfoPtr->receiveBufEnd == 0 );
status = readPacketHeaderSSH2( sessionInfoPtr, expectedType, &length,
&extraLength, NULL );
if( cryptStatusError( status ) )
return( status );
assert( length + extraLength >= SSH2_HEADER_REMAINDER_SIZE && \
length + extraLength < sessionInfoPtr->receiveBufSize );
/* Guaranteed by readPacketHeaderSSH2() */
/* Read the remainder of the handshake-packet message. The change
cipherspec message has length 0 so we only perform the read if
there's packet data present */
if( length + extraLength > SSH2_HEADER_REMAINDER_SIZE )
{
const long remainingLength = length + extraLength - \
SSH2_HEADER_REMAINDER_SIZE;
/* Because this code is called conditionally, we can't make the
read part of the fixed-header read but have to do independent
handling of shortfalls due to read timeouts */
status = sread( &sessionInfoPtr->stream,
sessionInfoPtr->receiveBuffer + \
SSH2_HEADER_REMAINDER_SIZE,
remainingLength );
if( cryptStatusError( status ) )
{
sNetGetErrorInfo( &sessionInfoPtr->stream,
&sessionInfoPtr->errorInfo );
return( status );
}
if( status != remainingLength )
{
retExt( CRYPT_ERROR_TIMEOUT,
( CRYPT_ERROR_TIMEOUT, SESSION_ERRINFO,
"Timeout during handshake packet remainder read, "
"only got %d of %ld bytes", status,
remainingLength ) );
}
}
/* Decrypt and MAC the packet if required */
if( sessionInfoPtr->flags & SESSION_ISSECURE_READ )
{
/* Decrypt the remainder of the packet except for the MAC.
Sometimes the payload can be zero-length, so we have to check
for this before we try the decrypt */
if( length > SSH2_HEADER_REMAINDER_SIZE )
{
status = krnlSendMessage( sessionInfoPtr->iCryptInContext,
IMESSAGE_CTX_DECRYPT,
sessionInfoPtr->receiveBuffer + \
SSH2_HEADER_REMAINDER_SIZE,
length - SSH2_HEADER_REMAINDER_SIZE );
if( cryptStatusError( status ) )
return( status );
}
/* MAC the decrypted payload */
status = checkMacSSH( sessionInfoPtr->iAuthInContext,
sshInfo->readSeqNo,
sessionInfoPtr->receiveBuffer,
length + extraLength, length, 0, MAC_ALL,
extraLength );
if( cryptStatusError( status ) )
{
/* If we're expecting a service control packet after a change
cipherspec packet and don't get it then it's more likely
that the problem is due to the wrong key being used than
data corruption, so we return a wrong key error instead
of bad data */
if( expectedType == SSH2_MSG_SERVICE_REQUEST || \
expectedType == SSH2_MSG_SERVICE_ACCEPT )
{
retExt( CRYPT_ERROR_WRONGKEY,
( CRYPT_ERROR_WRONGKEY, SESSION_ERRINFO,
"Bad message MAC for handshake packet type "
"%d, length %ld, probably due to an "
"incorrect key being used to generate the "
"MAC", sessionInfoPtr->receiveBuffer[ 1 ],
length ) );
}
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Bad message MAC for handshake packet type %d, "
"length %ld", sessionInfoPtr->receiveBuffer[ 1 ],
length ) );
}
}
padLength = sessionInfoPtr->receiveBuffer[ 0 ];
packetType = sessionInfoPtr->receiveBuffer[ 1 ];
sshInfo->readSeqNo++;
}
while( ( packetType == SSH2_MSG_IGNORE || \
packetType == SSH2_MSG_DEBUG || \
packetType == SSH2_MSG_USERAUTH_BANNER ) && \
( iterationCount++ < FAILSAFE_ITERATIONS_SMALL ) );
if( iterationCount >= FAILSAFE_ITERATIONS_SMALL )
{
/* We have to be a bit careful here in case this is a strange
implementation that sends large numbers of no-op packets as cover
traffic. Complaining after FAILSAFE_ITERATIONS_SMALL consecutive
no-ops seems to be a safe tradeoff between catching DoS's and
handling cover traffic */
retExt( CRYPT_ERROR_OVERFLOW,
( CRYPT_ERROR_OVERFLOW, SESSION_ERRINFO,
"Peer sent an excessive number of consecutive no-op "
"packets, it may be stuck in a loop" ) );
}
sshInfo->packetType = packetType;
/* Adjust the length to account for the fixed-size fields, remember
where the data starts, and make sure that there's some payload
present (there should always be at least one byte, the packet type) */
length -= PADLENGTH_SIZE + padLength;
if( packetType == SSH2_MSG_DISCONNECT )
{
/* If we're expecting a standard data packet and we get a disconnect
packet due to an error, the length can be less than the expected
mimimum length, so we adjust the length to the minimum packet
length of a disconnect packet */
minPacketLength = ID_SIZE + UINT32_SIZE + \
sizeofString32( "", 1 ) + sizeofString32( "", 0 );
}
if( length < minPacketLength || \
length > sessionInfoPtr->receiveBufSize - PADLENGTH_SIZE )
{
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Invalid length %ld for handshake packet type %d, should "
"be %d...%d", length, packetType, minPacketLength,
sessionInfoPtr->receiveBufSize - PADLENGTH_SIZE ) );
}
/* Move the data down in the buffer to get rid of the header info.
This isn't as inefficient as it seems since it's only used for the
short handshake messages */
memmove( sessionInfoPtr->receiveBuffer,
sessionInfoPtr->receiveBuffer + PADLENGTH_SIZE, length );
/* If the other side has gone away, report the details */
if( packetType == SSH2_MSG_DISCONNECT )
{
STREAM stream;
sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
assert( sPeek( &stream ) == SSH2_MSG_DISCONNECT );
status = sgetc( &stream ); /* Skip packet type */
if( !cryptStatusError( status ) )
status = getDisconnectInfo( sessionInfoPtr, &stream );
sMemDisconnect( &stream );
return( status );
}
/* Make sure that we either got what we asked for or one of the allowed
special-case packets */
switch( expectedType )
{
case SSH2_MSG_SPECIAL_USERAUTH:
/* If we're reading a response to a user authentication message
then getting a failure response is valid (even if it's not
what we're expecting) since it's an indication that an
incorrect password was used rather than that there was some
general type of failure */
expectedType = ( packetType == SSH2_MSG_USERAUTH_FAILURE ) ? \
SSH2_MSG_USERAUTH_FAILURE : \
SSH2_MSG_USERAUTH_SUCCESS;
break;
case SSH2_MSG_SPECIAL_USERAUTH_PAM:
/* PAM authentication can go through multiple iterations of back-
and-forth negotiation, for this case an info-request is also
a valid response, otherwise the responses are as for
SSH2_MSG_SPECIAL_USERAUTH */
expectedType = ( packetType == SSH2_MSG_USERAUTH_INFO_REQUEST ) ? \
SSH2_MSG_USERAUTH_INFO_REQUEST : \
( packetType == SSH2_MSG_USERAUTH_FAILURE ) ? \
SSH2_MSG_USERAUTH_FAILURE : \
SSH2_MSG_USERAUTH_SUCCESS;
break;
case SSH2_MSG_SPECIAL_CHANNEL:
/* If we're reading a response to a channel open message then
getting a failure response is valid (even if it's not what
we're expecting) since it's an indication that the channel
open (for example a port-forwarding operation) failed rather
than that there was some general type of failure */
expectedType = ( packetType == SSH2_MSG_CHANNEL_OPEN_FAILURE ) ? \
SSH2_MSG_CHANNEL_OPEN_FAILURE : \
SSH2_MSG_CHANNEL_OPEN_CONFIRMATION;
break;
case SSH2_MSG_SPECIAL_REQUEST:
/* If we're at the end of the handshake phase we can get either
a global or a channel request to tell us what to do next */
if( packetType != SSH2_MSG_GLOBAL_REQUEST && \
packetType != SSH2_MSG_CHANNEL_REQUEST )
{
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Invalid handshake packet type %d, expected "
"global or channel request", packetType ) );
}
expectedType = packetType;
break;
case SSH2_MSG_KEXDH_GEX_REQUEST_OLD:
/* The ephemeral DH key exchange spec was changed halfway
through to try and work around problems with key negotiation,
because of this we can see two different types of ephemeral
DH request, although they're functionally identical */
if( packetType == SSH2_MSG_KEXDH_GEX_REQUEST_NEW )
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -