@ -1830,6 +1830,76 @@ static int SSHParserTest29(void)
PASS ;
}
/** \test A direction parked in banner_wait_eol that then fails a
* \ test record freezes it at kex ; a > = 256 B banner without EOL that
* \ test does not parse as a banner freezes the direction at
* \ test banner at once .
*/
static int SSHParserTest30 ( void )
{
Flow f ;
TcpSession ssn ;
AppLayerParserThreadCtx * alp_tctx = NULL ;
uint8_t longbanner [ 300 ] ;
uint8_t junk [ 300 ] ;
uint8_t contbuf [ 8 + 2 ] ;
uint8_t badrecord [ ] = { 0x00 , 0x00 , 0x00 , 0x00 , 0x08 , 0x21 , 0x00 , 0x00 } ;
void * tx ;
memset ( & f , 0 , sizeof ( f ) ) ;
memset ( & ssn , 0 , sizeof ( ssn ) ) ;
FLOW_INITIALIZE ( & f ) ;
f . protoctx = ( void * ) & ssn ;
f . proto = IPPROTO_TCP ;
f . alproto = ALPROTO_SSH ;
StreamTcpInitConfig ( true ) ;
alp_tctx = AppLayerParserThreadCtxAlloc ( ) ;
FAIL_IF_NULL ( alp_tctx ) ;
// phase 1: 300B banner without EOL -> banner_wait_eol; the line
// completes and the following record is invalid -> the direction
// freezes at kex
memset ( longbanner , ' A ' , sizeof ( longbanner ) ) ;
memcpy ( longbanner , " SSH-2.0- " , 8 ) ;
int r = AppLayerParserParse (
NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOSERVER , longbanner , sizeof ( longbanner ) ) ;
FAIL_IF ( r ! = 0 ) ;
FAIL_IF_NULL ( f . alstate ) ;
tx = SCSshStateGetTx ( f . alstate , 0 ) ;
FAIL_IF ( SCSshTxGetAlStateProgress ( tx , STREAM_TOSERVER ) ! = SshStateBannerWaitEol ) ;
memcpy ( contbuf , " \r \n " , 2 ) ;
memcpy ( contbuf + 2 , badrecord , sizeof ( badrecord ) ) ;
r = AppLayerParserParse (
NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOSERVER , contbuf , sizeof ( contbuf ) ) ;
FAIL_IF ( r ! = - 1 ) ;
FAIL_IF ( SCSshTxGetAlStateProgress ( tx , STREAM_TOSERVER ) ! = SshStateKex ) ;
FAIL_IF ( SCSshTxGetAlStateProgress ( tx , STREAM_TOCLIENT ) ! = SshStateBanner ) ;
// phase 2: 300B of junk without EOL (>= 256, not a valid banner)
// -> frozen at banner at once
FLOW_DESTROY ( & f ) ;
memset ( & ssn , 0 , sizeof ( ssn ) ) ;
memset ( & f , 0 , sizeof ( f ) ) ;
FLOW_INITIALIZE ( & f ) ;
f . protoctx = ( void * ) & ssn ;
f . proto = IPPROTO_TCP ;
f . alproto = ALPROTO_SSH ;
memset ( junk , ' X ' , sizeof ( junk ) ) ;
r = AppLayerParserParse ( NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOSERVER , junk , sizeof ( junk ) ) ;
FAIL_IF ( r ! = - 1 ) ;
FAIL_IF_NULL ( f . alstate ) ;
tx = SCSshStateGetTx ( f . alstate , 0 ) ;
FAIL_IF ( SCSshTxGetAlStateProgress ( tx , STREAM_TOSERVER ) ! = SshStateBanner ) ;
FAIL_IF ( SCSshTxGetAlStateProgress ( tx , STREAM_TOCLIENT ) ! = SshStateBanner ) ;
FLOW_DESTROY ( & f ) ;
AppLayerParserThreadCtxFree ( alp_tctx ) ;
StreamTcpFreeConfig ( true ) ;
PASS ;
}
/** \test A banner line split over two segments: the open-line segment
* \ test publishes nothing but still marks the tx updated at parse
* \ test entry ; the line completion publishes the banner and takes
@ -1945,98 +2015,6 @@ static int SSHParserTest33(void)
PASS ;
}
/** \test a NewKeys record split across two parser calls (segment
* \ test boundary after the 6 - byte record header ) arms the
* \ test no - inspection / bypass flags like a complete record : the
* \ test arming runs on the session transition itself , shared by
* \ test the complete - record and incomplete - header arms . The peer
* \ test direction completes its NewKeys record in one call first ;
* \ test the split direction then delivers the record header ( the
* \ test body byte is stashed ) and the arming must happen on that
* \ test delivery . Also pins BYPASS_READY in bypass mode .
*/
static int SSHParserTest35 ( void )
{
Flow f ;
uint8_t banner [ ] = " SSH-2.0-TestClient-1.0 \r \n " ;
uint32_t bannerlen = sizeof ( banner ) - 1 ;
uint8_t newkeys [ ] = { 0x00 , 0x00 , 0x00 , 0x03 , 0x01 , 21 , 0x00 } ;
uint8_t newkeys_hdr [ 6 ] = { 0x00 , 0x00 , 0x00 , 0x03 , 0x01 , 21 } ;
uint8_t newkeys_body [ 1 ] = { 0x00 } ;
TcpSession ssn ;
AppLayerParserThreadCtx * alp_tctx = AppLayerParserThreadCtxAlloc ( ) ;
FAIL_IF_NULL ( alp_tctx ) ;
memset ( & f , 0 , sizeof ( f ) ) ;
memset ( & ssn , 0 , sizeof ( ssn ) ) ;
FLOW_INITIALIZE ( & f ) ;
f . protoctx = ( void * ) & ssn ;
f . proto = IPPROTO_TCP ;
f . alproto = ALPROTO_SSH ;
StreamTcpInitConfig ( true ) ;
int r = AppLayerParserParse (
NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOSERVER , banner , bannerlen ) ;
FAIL_IF ( r ! = 0 ) ;
r = AppLayerParserParse ( NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOCLIENT , banner , bannerlen ) ;
FAIL_IF ( r ! = 0 ) ;
/* peer direction: complete NewKeys record in one call -> session */
r = AppLayerParserParse (
NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOCLIENT , newkeys , sizeof ( newkeys ) ) ;
FAIL_IF ( r ! = 0 ) ;
void * ssh_state = f . alstate ;
FAIL_IF_NULL ( ssh_state ) ;
void * tx = SCSshStateGetTx ( ssh_state , 0 ) ;
FAIL_IF ( SCSshTxGetFlags ( tx , STREAM_TOCLIENT ) ! = SshStateSession ) ;
/* split direction: the record header only (the body byte is
* stashed and arrives in the next call ) */
r = AppLayerParserParse (
NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOSERVER , newkeys_hdr , sizeof ( newkeys_hdr ) ) ;
FAIL_IF ( r ! = 0 ) ;
tx = SCSshStateGetTx ( ssh_state , 0 ) ;
FAIL_IF ( SCSshTxGetFlags ( tx , STREAM_TOSERVER ) ! = SshStateSession ) ;
/* the session transition armed the flags despite the split */
FAIL_IF ( ! SCAppLayerParserStateIssetFlag ( f . alparser , APP_LAYER_PARSER_NO_INSPECTION ) ) ;
/* the stashed body byte completes the record */
r = AppLayerParserParse (
NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOSERVER , newkeys_body , sizeof ( newkeys_body ) ) ;
FAIL_IF ( r ! = 0 ) ;
FAIL_IF ( ! SCAppLayerParserStateIssetFlag ( f . alparser , APP_LAYER_PARSER_NO_INSPECTION ) ) ;
FLOW_DESTROY ( & f ) ;
/* bypass mode: the transition also arms BYPASS_READY */
SCSshEnableBypass ( ENCRYPTION_HANDLING_BYPASS ) ;
memset ( & f , 0 , sizeof ( f ) ) ;
memset ( & ssn , 0 , sizeof ( ssn ) ) ;
FLOW_INITIALIZE ( & f ) ;
f . protoctx = ( void * ) & ssn ;
f . proto = IPPROTO_TCP ;
f . alproto = ALPROTO_SSH ;
r = AppLayerParserParse ( NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOSERVER , banner , bannerlen ) ;
FAIL_IF ( r ! = 0 ) ;
r = AppLayerParserParse ( NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOCLIENT , banner , bannerlen ) ;
FAIL_IF ( r ! = 0 ) ;
r = AppLayerParserParse (
NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOCLIENT , newkeys , sizeof ( newkeys ) ) ;
FAIL_IF ( r ! = 0 ) ;
r = AppLayerParserParse (
NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOSERVER , newkeys_hdr , sizeof ( newkeys_hdr ) ) ;
FAIL_IF ( r ! = 0 ) ;
FAIL_IF ( ! SCAppLayerParserStateIssetFlag ( f . alparser , APP_LAYER_PARSER_BYPASS_READY ) ) ;
SCSshEnableBypass ( ENCRYPTION_HANDLING_TRACK_ONLY ) ;
FLOW_DESTROY ( & f ) ;
AppLayerParserThreadCtxFree ( alp_tctx ) ;
StreamTcpFreeConfig ( true ) ;
PASS ;
}
/** \test a parse failure ends app-layer parsing for the whole flow
* \ test ( engine path ) : the failure site returns a parse error , so
* \ test StreamTcpDisableAppLayer runs and no further data in either
@ -2138,6 +2116,99 @@ static int SSHParserTest34(void)
StreamTcpUTDeinit ( ra_ctx ) ;
PASS ;
}
/** \test a NewKeys record split across two parser calls (segment
* \ test boundary after the 6 - byte record header ) arms the
* \ test no - inspection / bypass flags like a complete record : the
* \ test arming runs on the session transition itself , shared by
* \ test the complete - record and incomplete - header arms . The peer
* \ test direction completes its NewKeys record in one call first ;
* \ test the split direction then delivers the record header ( the
* \ test body byte is stashed ) and the arming must happen on that
* \ test delivery . Also pins BYPASS_READY in bypass mode .
*/
static int SSHParserTest35 ( void )
{
Flow f ;
uint8_t banner [ ] = " SSH-2.0-TestClient-1.0 \r \n " ;
uint32_t bannerlen = sizeof ( banner ) - 1 ;
uint8_t newkeys [ ] = { 0x00 , 0x00 , 0x00 , 0x03 , 0x01 , 21 , 0x00 } ;
uint8_t newkeys_hdr [ 6 ] = { 0x00 , 0x00 , 0x00 , 0x03 , 0x01 , 21 } ;
uint8_t newkeys_body [ 1 ] = { 0x00 } ;
TcpSession ssn ;
AppLayerParserThreadCtx * alp_tctx = AppLayerParserThreadCtxAlloc ( ) ;
FAIL_IF_NULL ( alp_tctx ) ;
memset ( & f , 0 , sizeof ( f ) ) ;
memset ( & ssn , 0 , sizeof ( ssn ) ) ;
FLOW_INITIALIZE ( & f ) ;
f . protoctx = ( void * ) & ssn ;
f . proto = IPPROTO_TCP ;
f . alproto = ALPROTO_SSH ;
StreamTcpInitConfig ( true ) ;
int r = AppLayerParserParse (
NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOSERVER , banner , bannerlen ) ;
FAIL_IF ( r ! = 0 ) ;
r = AppLayerParserParse ( NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOCLIENT , banner , bannerlen ) ;
FAIL_IF ( r ! = 0 ) ;
/* peer direction: complete NewKeys record in one call -> session */
r = AppLayerParserParse (
NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOCLIENT , newkeys , sizeof ( newkeys ) ) ;
FAIL_IF ( r ! = 0 ) ;
void * ssh_state = f . alstate ;
FAIL_IF_NULL ( ssh_state ) ;
void * tx = SCSshStateGetTx ( ssh_state , 0 ) ;
FAIL_IF ( SCSshTxGetFlags ( tx , STREAM_TOCLIENT ) ! = SshStateSession ) ;
/* split direction: the record header only (the body byte is
* stashed and arrives in the next call ) */
r = AppLayerParserParse (
NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOSERVER , newkeys_hdr , sizeof ( newkeys_hdr ) ) ;
FAIL_IF ( r ! = 0 ) ;
tx = SCSshStateGetTx ( ssh_state , 0 ) ;
FAIL_IF ( SCSshTxGetFlags ( tx , STREAM_TOSERVER ) ! = SshStateSession ) ;
/* the session transition armed the flags despite the split */
FAIL_IF ( ! SCAppLayerParserStateIssetFlag ( f . alparser , APP_LAYER_PARSER_NO_INSPECTION ) ) ;
/* the stashed body byte completes the record */
r = AppLayerParserParse (
NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOSERVER , newkeys_body , sizeof ( newkeys_body ) ) ;
FAIL_IF ( r ! = 0 ) ;
FAIL_IF ( ! SCAppLayerParserStateIssetFlag ( f . alparser , APP_LAYER_PARSER_NO_INSPECTION ) ) ;
FLOW_DESTROY ( & f ) ;
/* bypass mode: the transition also arms BYPASS_READY */
SCSshEnableBypass ( ENCRYPTION_HANDLING_BYPASS ) ;
memset ( & f , 0 , sizeof ( f ) ) ;
memset ( & ssn , 0 , sizeof ( ssn ) ) ;
FLOW_INITIALIZE ( & f ) ;
f . protoctx = ( void * ) & ssn ;
f . proto = IPPROTO_TCP ;
f . alproto = ALPROTO_SSH ;
r = AppLayerParserParse ( NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOSERVER , banner , bannerlen ) ;
FAIL_IF ( r ! = 0 ) ;
r = AppLayerParserParse ( NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOCLIENT , banner , bannerlen ) ;
FAIL_IF ( r ! = 0 ) ;
r = AppLayerParserParse (
NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOCLIENT , newkeys , sizeof ( newkeys ) ) ;
FAIL_IF ( r ! = 0 ) ;
r = AppLayerParserParse (
NULL , alp_tctx , & f , ALPROTO_SSH , STREAM_TOSERVER , newkeys_hdr , sizeof ( newkeys_hdr ) ) ;
FAIL_IF ( r ! = 0 ) ;
FAIL_IF ( ! SCAppLayerParserStateIssetFlag ( f . alparser , APP_LAYER_PARSER_BYPASS_READY ) ) ;
SCSshEnableBypass ( ENCRYPTION_HANDLING_TRACK_ONLY ) ;
FLOW_DESTROY ( & f ) ;
AppLayerParserThreadCtxFree ( alp_tctx ) ;
StreamTcpFreeConfig ( true ) ;
PASS ;
}
# endif /* UNITTESTS */
void SSHParserRegisterTests ( void )
@ -2174,13 +2245,14 @@ void SSHParserRegisterTests(void)
SSHParserTest28 ) ;
UtRegisterTest (
" SSHParserTest29 - stash and fragment deliveries mark the tx updated " , SSHParserTest29 ) ;
UtRegisterTest ( " SSHParserTest30 - wait_eol failure freezes at kex " , SSHParserTest30 ) ;
UtRegisterTest ( " SSHParserTest32 - banner continuation marks the tx updated at parse entry " ,
SSHParserTest32 ) ;
UtRegisterTest ( " SSHParserTest33 - failed peer does not arm no-inspection " , SSHParserTest33 ) ;
UtRegisterTest (
" SSHParserTest35 - split NewKeys arms the no-inspection/bypass flags " , SSHParserTest35 ) ;
UtRegisterTest ( " SSHParserTest34 - failure ends the flow's app-layer parsing (engine path) " ,
SSHParserTest34 ) ;
UtRegisterTest (
" SSHParserTest35 - split NewKeys arms the no-inspection/bypass flags " , SSHParserTest35 ) ;
# endif /* UNITTESTS */
}