Skip to content

Commit 9a90a87

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 79456c2 commit 9a90a87

1 file changed

Lines changed: 250 additions & 0 deletions

File tree

tests/regress.c

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

Comments
 (0)