@@ -3006,6 +3006,252 @@ static void RepointHarnessInput(ChannelOpenHarness* harness,
30063006 harness -> io .outSz = 0 ;
30073007}
30083008
3009+ /* Builds a plaintext SSH_MSG_CHANNEL_OPEN_CONFIRMATION. */
3010+ static word32 BuildChannelOpenConfPacket (word32 recipientChannelId ,
3011+ word32 senderChannelId , word32 initialWindowSz , word32 maxPacketSz ,
3012+ byte * out , word32 outSz )
3013+ {
3014+ byte payload [32 ];
3015+ word32 idx = 0 ;
3016+
3017+ idx = AppendUint32 (payload , sizeof (payload ), idx , recipientChannelId );
3018+ idx = AppendUint32 (payload , sizeof (payload ), idx , senderChannelId );
3019+ idx = AppendUint32 (payload , sizeof (payload ), idx , initialWindowSz );
3020+ idx = AppendUint32 (payload , sizeof (payload ), idx , maxPacketSz );
3021+
3022+ return WrapPacket (MSGID_CHANNEL_OPEN_CONF , payload , idx , out , outSz );
3023+ }
3024+
3025+ /* Builds a plaintext SSH_MSG_CHANNEL_OPEN_FAILURE with an empty language
3026+ * tag, which is what every sender in the tree emits. */
3027+ static word32 BuildChannelOpenFailPacket (word32 recipientChannelId ,
3028+ word32 reasonId , const char * desc , byte * out , word32 outSz )
3029+ {
3030+ byte payload [128 ];
3031+ word32 idx = 0 ;
3032+
3033+ idx = AppendUint32 (payload , sizeof (payload ), idx , recipientChannelId );
3034+ idx = AppendUint32 (payload , sizeof (payload ), idx , reasonId );
3035+ idx = AppendString (payload , sizeof (payload ), idx , desc );
3036+ idx = AppendString (payload , sizeof (payload ), idx , "" );
3037+
3038+ return WrapPacket (MSGID_CHANNEL_OPEN_FAIL , payload , idx , out , outSz );
3039+ }
3040+
3041+ /* What the open-response callbacks saw. File scope rather than reached
3042+ * through the callback ctx, so a zero reading means the hook did not run and
3043+ * cannot instead mean the ctx stopped being delivered. Each hook has its own
3044+ * count, so a test can tell which one ran. The harness's first channel has
3045+ * id 0, so the recorded id starts at a value no channel can have. */
3046+ #define REGRESS_NO_CHANNEL ((word32)-1)
3047+ static int openConfCbCalls ;
3048+ static int openFailCbCalls ;
3049+ static word32 openRespCbChannel ;
3050+ static void * openRespCbCtx ;
3051+ static word32 openConfCbPeerChannel ;
3052+ static word32 openConfCbPeerWindowSz ;
3053+ static word32 openConfCbPeerMaxPacketSz ;
3054+ static word32 openFailCbListSz ;
3055+ static int openRespCbReturn ;
3056+
3057+ static int RecordingChannelOpenConfCb (WOLFSSH_CHANNEL * channel , void * ctx )
3058+ {
3059+ AssertNotNull (channel );
3060+ openConfCbCalls ++ ;
3061+ openRespCbChannel = channel -> channel ;
3062+ openRespCbCtx = ctx ;
3063+ openConfCbPeerChannel = channel -> peerChannel ;
3064+ openConfCbPeerWindowSz = channel -> peerWindowSz ;
3065+ openConfCbPeerMaxPacketSz = channel -> peerMaxPacketSz ;
3066+
3067+ return openRespCbReturn ;
3068+ }
3069+
3070+ static int RecordingChannelOpenFailCb (WOLFSSH_CHANNEL * channel , void * ctx )
3071+ {
3072+ AssertNotNull (channel );
3073+ AssertNotNull (channel -> ssh );
3074+ openFailCbCalls ++ ;
3075+ openRespCbChannel = channel -> channel ;
3076+ openRespCbCtx = ctx ;
3077+ openFailCbListSz = channel -> ssh -> channelListSz ;
3078+
3079+ return openRespCbReturn ;
3080+ }
3081+
3082+ /* Seeds an unconfirmed channel of our own, the state a channel is in while
3083+ * its open is outstanding and the only state the open responses apply to. */
3084+ static WOLFSSH_CHANNEL * SeedUnconfirmedChannel (ChannelOpenHarness * harness )
3085+ {
3086+ WOLFSSH_CHANNEL * channel ;
3087+
3088+ channel = ChannelNew (harness -> ssh , ID_CHANTYPE_SESSION , 1024 , 1024 );
3089+ AssertNotNull (channel );
3090+ AssertIntEQ (ChannelAppend (harness -> ssh , channel ), WS_SUCCESS );
3091+
3092+ return channel ;
3093+ }
3094+
3095+ /* Registers both open-response hooks, set to return cbReturn, and seeds the
3096+ * outstanding channel. */
3097+ static WOLFSSH_CHANNEL * SeedOpenRespHarness (ChannelOpenHarness * harness ,
3098+ void * cbCtx , int cbReturn )
3099+ {
3100+ openConfCbCalls = 0 ;
3101+ openFailCbCalls = 0 ;
3102+ openRespCbChannel = REGRESS_NO_CHANNEL ;
3103+ openRespCbCtx = NULL ;
3104+ openConfCbPeerChannel = 0 ;
3105+ openConfCbPeerWindowSz = 0 ;
3106+ openConfCbPeerMaxPacketSz = 0 ;
3107+ openFailCbListSz = 0 ;
3108+ openRespCbReturn = cbReturn ;
3109+
3110+ InitChannelOpenHarness (harness , NULL , 0 );
3111+ AssertIntEQ (wolfSSH_CTX_SetChannelOpenRespCb (harness -> ctx ,
3112+ RecordingChannelOpenConfCb , RecordingChannelOpenFailCb ),
3113+ WS_SUCCESS );
3114+ AssertIntEQ (wolfSSH_SetChannelOpenCtx (harness -> ssh , cbCtx ), WS_SUCCESS );
3115+
3116+ return SeedUnconfirmedChannel (harness );
3117+ }
3118+
3119+ /* The confirmation callback is the application's only notice that a channel
3120+ * it opened is usable, and it runs with the peer's parameters already
3121+ * recorded. */
3122+ static void TestChannelOpenConfCallbackRuns (void )
3123+ {
3124+ ChannelOpenHarness harness ;
3125+ WOLFSSH_CHANNEL * channel ;
3126+ byte in [64 ];
3127+ word32 inSz ;
3128+ word32 selfChannelId ;
3129+ int cbCtx = 0 ;
3130+ int ret ;
3131+
3132+ channel = SeedOpenRespHarness (& harness , & cbCtx , WS_SUCCESS );
3133+ selfChannelId = channel -> channel ;
3134+
3135+ inSz = BuildChannelOpenConfPacket (selfChannelId , 7 , 0x4000 , 0x8000 ,
3136+ in , sizeof (in ));
3137+ RepointHarnessInput (& harness , in , inSz );
3138+
3139+ ret = DoReceive (harness .ssh );
3140+ AssertIntEQ (ret , WS_SUCCESS );
3141+ AssertIntEQ (harness .io .inOff , harness .io .inSz );
3142+ AssertIntEQ (openConfCbCalls , 1 );
3143+ AssertIntEQ (openFailCbCalls , 0 );
3144+ AssertIntEQ (openRespCbChannel , selfChannelId );
3145+ AssertTrue (openRespCbCtx == & cbCtx );
3146+
3147+ /* Read in the callback: the peer's numbers are already in place when it
3148+ * runs, not merely by the time DoReceive() returns. */
3149+ AssertIntEQ (openConfCbPeerChannel , 7 );
3150+ AssertIntEQ (openConfCbPeerWindowSz , 0x4000 );
3151+ AssertIntEQ (openConfCbPeerMaxPacketSz , 0x8000 );
3152+
3153+ FreeChannelOpenHarness (& harness );
3154+ }
3155+
3156+ /* The failure callback runs while the channel is still findable, since it is
3157+ * removed immediately afterward and the application would otherwise have no
3158+ * way to tell which open was refused. */
3159+ static void TestChannelOpenFailCallbackRuns (void )
3160+ {
3161+ ChannelOpenHarness harness ;
3162+ WOLFSSH_CHANNEL * channel ;
3163+ byte in [128 ];
3164+ word32 inSz ;
3165+ word32 selfChannelId ;
3166+ int cbCtx = 0 ;
3167+ int ret ;
3168+
3169+ channel = SeedOpenRespHarness (& harness , & cbCtx , WS_SUCCESS );
3170+ selfChannelId = channel -> channel ;
3171+
3172+ inSz = BuildChannelOpenFailPacket (selfChannelId ,
3173+ OPEN_ADMINISTRATIVELY_PROHIBITED , "no" , in , sizeof (in ));
3174+ RepointHarnessInput (& harness , in , inSz );
3175+
3176+ /* DoReceive() reports WS_CHANOPEN_FAILED through ssh->error; only the
3177+ * statuses it can resume from come back as themselves. */
3178+ ret = DoReceive (harness .ssh );
3179+ AssertIntEQ (ret , WS_FATAL_ERROR );
3180+ AssertIntEQ (wolfSSH_get_error (harness .ssh ), WS_CHANOPEN_FAILED );
3181+ AssertIntEQ (harness .io .inOff , harness .io .inSz );
3182+ AssertIntEQ (openFailCbCalls , 1 );
3183+ AssertIntEQ (openConfCbCalls , 0 );
3184+ AssertIntEQ (openRespCbChannel , selfChannelId );
3185+ AssertTrue (openRespCbCtx == & cbCtx );
3186+ /* Still on the list when the callback ran, gone by the time the caller
3187+ * is told. */
3188+ AssertIntEQ (openFailCbListSz , 1 );
3189+ AssertIntEQ (harness .ssh -> channelListSz , 0 );
3190+
3191+ FreeChannelOpenHarness (& harness );
3192+ }
3193+
3194+ /* A confirm callback that returns an error fails the receive with that
3195+ * error, and the open is not marked done: the session state and default
3196+ * peer channel stay as they were. */
3197+ static void TestChannelOpenConfCallbackRejects (void )
3198+ {
3199+ ChannelOpenHarness harness ;
3200+ WOLFSSH_CHANNEL * channel ;
3201+ byte in [64 ];
3202+ word32 inSz ;
3203+ word32 defaultPeerChannelId ;
3204+ int serverState ;
3205+ int cbCtx = 0 ;
3206+ int ret ;
3207+
3208+ channel = SeedOpenRespHarness (& harness , & cbCtx , WS_BAD_ARGUMENT );
3209+ serverState = harness .ssh -> serverState ;
3210+ defaultPeerChannelId = harness .ssh -> defaultPeerChannelId ;
3211+
3212+ inSz = BuildChannelOpenConfPacket (channel -> channel , 7 , 0x4000 , 0x8000 ,
3213+ in , sizeof (in ));
3214+ RepointHarnessInput (& harness , in , inSz );
3215+
3216+ ret = DoReceive (harness .ssh );
3217+ AssertIntEQ (ret , WS_FATAL_ERROR );
3218+ AssertIntEQ (wolfSSH_get_error (harness .ssh ), WS_BAD_ARGUMENT );
3219+ AssertIntEQ (harness .io .inOff , harness .io .inSz );
3220+ AssertIntEQ (openConfCbCalls , 1 );
3221+ AssertIntEQ (harness .ssh -> serverState , serverState );
3222+ AssertIntEQ (harness .ssh -> defaultPeerChannelId , defaultPeerChannelId );
3223+
3224+ FreeChannelOpenHarness (& harness );
3225+ }
3226+
3227+ /* A failure callback that returns an error is reported in place of
3228+ * WS_CHANOPEN_FAILED, and the refused channel is left on the list for the
3229+ * application to retire. */
3230+ static void TestChannelOpenFailCallbackRejects (void )
3231+ {
3232+ ChannelOpenHarness harness ;
3233+ WOLFSSH_CHANNEL * channel ;
3234+ byte in [128 ];
3235+ word32 inSz ;
3236+ int cbCtx = 0 ;
3237+ int ret ;
3238+
3239+ channel = SeedOpenRespHarness (& harness , & cbCtx , WS_BAD_ARGUMENT );
3240+
3241+ inSz = BuildChannelOpenFailPacket (channel -> channel ,
3242+ OPEN_ADMINISTRATIVELY_PROHIBITED , "no" , in , sizeof (in ));
3243+ RepointHarnessInput (& harness , in , inSz );
3244+
3245+ ret = DoReceive (harness .ssh );
3246+ AssertIntEQ (ret , WS_FATAL_ERROR );
3247+ AssertIntEQ (wolfSSH_get_error (harness .ssh ), WS_BAD_ARGUMENT );
3248+ AssertIntEQ (harness .io .inOff , harness .io .inSz );
3249+ AssertIntEQ (openFailCbCalls , 1 );
3250+ AssertIntEQ (harness .ssh -> channelListSz , 1 );
3251+
3252+ FreeChannelOpenHarness (& harness );
3253+ }
3254+
30093255/* A username change after the first userauth request must end the session. */
30103256static void TestUsernameChangeDisconnects (void )
30113257{
@@ -12204,6 +12450,10 @@ int main(int argc, char** argv)
1220412450 TestServerServiceRequestRejectedDuringKeying ();
1220512451 TestFailedSendClearsPendingPlaintext ();
1220612452 TestChannelOpenCallbackRejectSendsOpenFail ();
12453+ TestChannelOpenConfCallbackRuns ();
12454+ TestChannelOpenFailCallbackRuns ();
12455+ TestChannelOpenConfCallbackRejects ();
12456+ TestChannelOpenFailCallbackRejects ();
1220712457 TestSecondSessionChannelRejected ();
1220812458 TestUsernameChangeDisconnects ();
1220912459 TestSameUserRetryAllowed ();
0 commit comments