@@ -2928,6 +2928,235 @@ static void RepointHarnessInput(ChannelOpenHarness* harness,
29282928 harness -> io .outSz = 0 ;
29292929}
29302930
2931+ /* Builds a plaintext SSH_MSG_CHANNEL_OPEN_CONFIRMATION. */
2932+ static word32 BuildChannelOpenConfPacket (word32 recipientChannelId ,
2933+ word32 senderChannelId , word32 initialWindowSz , word32 maxPacketSz ,
2934+ byte * out , word32 outSz )
2935+ {
2936+ byte payload [32 ];
2937+ word32 idx = 0 ;
2938+
2939+ idx = AppendUint32 (payload , sizeof (payload ), idx , recipientChannelId );
2940+ idx = AppendUint32 (payload , sizeof (payload ), idx , senderChannelId );
2941+ idx = AppendUint32 (payload , sizeof (payload ), idx , initialWindowSz );
2942+ idx = AppendUint32 (payload , sizeof (payload ), idx , maxPacketSz );
2943+
2944+ return WrapPacket (MSGID_CHANNEL_OPEN_CONF , payload , idx , out , outSz );
2945+ }
2946+
2947+ /* Builds a plaintext SSH_MSG_CHANNEL_OPEN_FAILURE with an empty language
2948+ * tag, which is what every sender in the tree emits. */
2949+ static word32 BuildChannelOpenFailPacket (word32 recipientChannelId ,
2950+ word32 reasonId , const char * desc , byte * out , word32 outSz )
2951+ {
2952+ byte payload [128 ];
2953+ word32 idx = 0 ;
2954+
2955+ idx = AppendUint32 (payload , sizeof (payload ), idx , recipientChannelId );
2956+ idx = AppendUint32 (payload , sizeof (payload ), idx , reasonId );
2957+ idx = AppendString (payload , sizeof (payload ), idx , desc );
2958+ idx = AppendString (payload , sizeof (payload ), idx , "" );
2959+
2960+ return WrapPacket (MSGID_CHANNEL_OPEN_FAIL , payload , idx , out , outSz );
2961+ }
2962+
2963+ /* What the open-response callbacks saw. File scope rather than reached
2964+ * through the callback ctx, so a zero reading means the hook did not run and
2965+ * cannot instead mean the ctx stopped being delivered. Each hook has its own
2966+ * count, so a test can tell which one ran. The harness's first channel has
2967+ * id 0, so the recorded id starts at a value no channel can have. */
2968+ #define REGRESS_NO_CHANNEL ((word32)-1)
2969+ static int openConfCbCalls ;
2970+ static int openFailCbCalls ;
2971+ static word32 openRespCbChannel ;
2972+ static void * openRespCbCtx ;
2973+ static int openRespCbReturn ;
2974+
2975+ static int RecordingChannelOpenConfCb (WOLFSSH_CHANNEL * channel , void * ctx )
2976+ {
2977+ AssertNotNull (channel );
2978+ openConfCbCalls ++ ;
2979+ openRespCbChannel = channel -> channel ;
2980+ openRespCbCtx = ctx ;
2981+
2982+ return openRespCbReturn ;
2983+ }
2984+
2985+ static int RecordingChannelOpenFailCb (WOLFSSH_CHANNEL * channel , void * ctx )
2986+ {
2987+ AssertNotNull (channel );
2988+ openFailCbCalls ++ ;
2989+ openRespCbChannel = channel -> channel ;
2990+ openRespCbCtx = ctx ;
2991+
2992+ return openRespCbReturn ;
2993+ }
2994+
2995+ /* Seeds an unconfirmed channel of our own, the state a channel is in while
2996+ * its open is outstanding and the only state the open responses apply to. */
2997+ static WOLFSSH_CHANNEL * SeedUnconfirmedChannel (ChannelOpenHarness * harness )
2998+ {
2999+ WOLFSSH_CHANNEL * channel ;
3000+
3001+ channel = ChannelNew (harness -> ssh , ID_CHANTYPE_SESSION , 1024 , 1024 );
3002+ AssertNotNull (channel );
3003+ AssertIntEQ (ChannelAppend (harness -> ssh , channel ), WS_SUCCESS );
3004+
3005+ return channel ;
3006+ }
3007+
3008+ /* Registers both open-response hooks, set to return cbReturn, and seeds the
3009+ * outstanding channel. */
3010+ static WOLFSSH_CHANNEL * SeedOpenRespHarness (ChannelOpenHarness * harness ,
3011+ void * cbCtx , int cbReturn )
3012+ {
3013+ openConfCbCalls = 0 ;
3014+ openFailCbCalls = 0 ;
3015+ openRespCbChannel = REGRESS_NO_CHANNEL ;
3016+ openRespCbCtx = NULL ;
3017+ openRespCbReturn = cbReturn ;
3018+
3019+ InitChannelOpenHarness (harness , NULL , 0 );
3020+ AssertIntEQ (wolfSSH_CTX_SetChannelOpenRespCb (harness -> ctx ,
3021+ RecordingChannelOpenConfCb , RecordingChannelOpenFailCb ),
3022+ WS_SUCCESS );
3023+ AssertIntEQ (wolfSSH_SetChannelOpenCtx (harness -> ssh , cbCtx ), WS_SUCCESS );
3024+
3025+ return SeedUnconfirmedChannel (harness );
3026+ }
3027+
3028+ /* The confirmation callback is the application's only notice that a channel
3029+ * it opened is usable, and it runs with the peer's parameters already
3030+ * recorded. */
3031+ static void TestChannelOpenConfCallbackRuns (void )
3032+ {
3033+ ChannelOpenHarness harness ;
3034+ WOLFSSH_CHANNEL * channel ;
3035+ byte in [64 ];
3036+ word32 inSz ;
3037+ word32 selfChannelId ;
3038+ int cbCtx = 0 ;
3039+ int ret ;
3040+
3041+ channel = SeedOpenRespHarness (& harness , & cbCtx , WS_SUCCESS );
3042+ selfChannelId = channel -> channel ;
3043+
3044+ inSz = BuildChannelOpenConfPacket (selfChannelId , 7 , 0x4000 , 0x8000 ,
3045+ in , sizeof (in ));
3046+ RepointHarnessInput (& harness , in , inSz );
3047+
3048+ ret = DoReceive (harness .ssh );
3049+ AssertIntEQ (ret , WS_SUCCESS );
3050+ AssertIntEQ (harness .io .inOff , harness .io .inSz );
3051+ AssertIntEQ (openConfCbCalls , 1 );
3052+ AssertIntEQ (openFailCbCalls , 0 );
3053+ AssertIntEQ (openRespCbChannel , selfChannelId );
3054+ AssertTrue (openRespCbCtx == & cbCtx );
3055+
3056+ /* The peer's numbers are in place before the callback can use them. */
3057+ AssertIntEQ (channel -> peerChannel , 7 );
3058+ AssertIntEQ (channel -> peerWindowSz , 0x4000 );
3059+ AssertIntEQ (channel -> peerMaxPacketSz , 0x8000 );
3060+
3061+ FreeChannelOpenHarness (& harness );
3062+ }
3063+
3064+ /* The failure callback runs while the channel is still findable, since it is
3065+ * removed immediately afterward and the application would otherwise have no
3066+ * way to tell which open was refused. */
3067+ static void TestChannelOpenFailCallbackRuns (void )
3068+ {
3069+ ChannelOpenHarness harness ;
3070+ WOLFSSH_CHANNEL * channel ;
3071+ byte in [128 ];
3072+ word32 inSz ;
3073+ word32 selfChannelId ;
3074+ int cbCtx = 0 ;
3075+ int ret ;
3076+
3077+ channel = SeedOpenRespHarness (& harness , & cbCtx , WS_SUCCESS );
3078+ selfChannelId = channel -> channel ;
3079+
3080+ inSz = BuildChannelOpenFailPacket (selfChannelId ,
3081+ OPEN_ADMINISTRATIVELY_PROHIBITED , "no" , in , sizeof (in ));
3082+ RepointHarnessInput (& harness , in , inSz );
3083+
3084+ /* DoReceive() reports WS_CHANOPEN_FAILED through ssh->error; only the
3085+ * statuses it can resume from come back as themselves. */
3086+ ret = DoReceive (harness .ssh );
3087+ AssertIntEQ (ret , WS_FATAL_ERROR );
3088+ AssertIntEQ (wolfSSH_get_error (harness .ssh ), WS_CHANOPEN_FAILED );
3089+ AssertIntEQ (harness .io .inOff , harness .io .inSz );
3090+ AssertIntEQ (openFailCbCalls , 1 );
3091+ AssertIntEQ (openConfCbCalls , 0 );
3092+ AssertIntEQ (openRespCbChannel , selfChannelId );
3093+ AssertTrue (openRespCbCtx == & cbCtx );
3094+ AssertIntEQ (harness .ssh -> channelListSz , 0 );
3095+
3096+ FreeChannelOpenHarness (& harness );
3097+ }
3098+
3099+ /* A confirm callback that returns an error fails the receive with that
3100+ * error, and the open is not marked done: the session state and default
3101+ * peer channel stay as they were. */
3102+ static void TestChannelOpenConfCallbackRejects (void )
3103+ {
3104+ ChannelOpenHarness harness ;
3105+ WOLFSSH_CHANNEL * channel ;
3106+ byte in [64 ];
3107+ word32 inSz ;
3108+ word32 defaultPeerChannelId ;
3109+ int serverState ;
3110+ int cbCtx = 0 ;
3111+ int ret ;
3112+
3113+ channel = SeedOpenRespHarness (& harness , & cbCtx , WS_BAD_ARGUMENT );
3114+ serverState = harness .ssh -> serverState ;
3115+ defaultPeerChannelId = harness .ssh -> defaultPeerChannelId ;
3116+
3117+ inSz = BuildChannelOpenConfPacket (channel -> channel , 7 , 0x4000 , 0x8000 ,
3118+ in , sizeof (in ));
3119+ RepointHarnessInput (& harness , in , inSz );
3120+
3121+ ret = DoReceive (harness .ssh );
3122+ AssertIntEQ (ret , WS_FATAL_ERROR );
3123+ AssertIntEQ (wolfSSH_get_error (harness .ssh ), WS_BAD_ARGUMENT );
3124+ AssertIntEQ (harness .io .inOff , harness .io .inSz );
3125+ AssertIntEQ (openConfCbCalls , 1 );
3126+ AssertIntEQ (harness .ssh -> serverState , serverState );
3127+ AssertIntEQ (harness .ssh -> defaultPeerChannelId , defaultPeerChannelId );
3128+
3129+ FreeChannelOpenHarness (& harness );
3130+ }
3131+
3132+ /* A failure callback that returns an error is reported in place of
3133+ * WS_CHANOPEN_FAILED, and the refused channel is left on the list for the
3134+ * application to retire. */
3135+ static void TestChannelOpenFailCallbackRejects (void )
3136+ {
3137+ ChannelOpenHarness harness ;
3138+ WOLFSSH_CHANNEL * channel ;
3139+ byte in [128 ];
3140+ word32 inSz ;
3141+ int cbCtx = 0 ;
3142+ int ret ;
3143+
3144+ channel = SeedOpenRespHarness (& harness , & cbCtx , WS_BAD_ARGUMENT );
3145+
3146+ inSz = BuildChannelOpenFailPacket (channel -> channel ,
3147+ OPEN_ADMINISTRATIVELY_PROHIBITED , "no" , in , sizeof (in ));
3148+ RepointHarnessInput (& harness , in , inSz );
3149+
3150+ ret = DoReceive (harness .ssh );
3151+ AssertIntEQ (ret , WS_FATAL_ERROR );
3152+ AssertIntEQ (wolfSSH_get_error (harness .ssh ), WS_BAD_ARGUMENT );
3153+ AssertIntEQ (harness .io .inOff , harness .io .inSz );
3154+ AssertIntEQ (openFailCbCalls , 1 );
3155+ AssertIntEQ (harness .ssh -> channelListSz , 1 );
3156+
3157+ FreeChannelOpenHarness (& harness );
3158+ }
3159+
29313160/* A username change after the first userauth request must end the session. */
29323161static void TestUsernameChangeDisconnects (void )
29333162{
@@ -12122,6 +12351,10 @@ int main(int argc, char** argv)
1212212351 TestServerServiceRequestRejectedDuringKeying ();
1212312352 TestFailedSendClearsPendingPlaintext ();
1212412353 TestChannelOpenCallbackRejectSendsOpenFail ();
12354+ TestChannelOpenConfCallbackRuns ();
12355+ TestChannelOpenFailCallbackRuns ();
12356+ TestChannelOpenConfCallbackRejects ();
12357+ TestChannelOpenFailCallbackRejects ();
1212512358 TestSecondSessionChannelRejected ();
1212612359 TestUsernameChangeDisconnects ();
1212712360 TestSameUserRetryAllowed ();
0 commit comments