Skip to content

Commit fc0b7fa

Browse files
committed
tests: cover the channel open response callbacks
The confirmation and failure hooks reach an application only through DoChannelOpenConf() and DoChannelOpenFail(), and nothing in tests/ or apps/ registered either, so both arms shipped unexercised. - assert the confirm callback runs with the peer's channel id, window and packet size already recorded - assert the failure callback runs while the channel is still findable, before DoChannelOpenFail() removes it - count each hook separately, so a test can tell which one ran - a rejecting confirm hook fails the receive and leaves the open unfinished; a rejecting failure hook fails it and leaves the channel on the list - seed the unconfirmed channel through ChannelNew() and ChannelAppend(), the state an outstanding open leaves behind
1 parent 557f3df commit fc0b7fa

1 file changed

Lines changed: 233 additions & 0 deletions

File tree

tests/regress.c

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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. */
29323161
static 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

Comments
 (0)