Skip to content

Commit c71202f

Browse files
ejohnstownphilljj
authored andcommitted
Guard remote forwards and disallowed msg ids
A client answers tcpip-forward and cancel-tcpip-forward with a failure, RFC 4254 section 7.1, before the request body is parsed. DoPacket() disconnects on a refused id this build implements and on any refused id of 80 or higher, the range RFC 4252 section 6 names; a refused id below 80 is answered UNIMPLEMENTED, RFC 4253 section 11.4. - MsgIdKnown() carries DoPacket()'s dispatch ids, build guards included - answer a refused id off the dispatch, so one cannot reach a handler if MsgIdKnown() falls behind - skip the disconnect once the session is over, RFC 4253 section 11.1 forbids sending after one - cover ids 53, 79, 200 and a channel open before user auth on one keyed-server helper - cover a client refusing both request names, with a reply asked for and without, and a server still succeeding Issue: #1047 (3), #1047 (7), F-10576, F-10581, F-12574
1 parent 9cedb5a commit c71202f

2 files changed

Lines changed: 302 additions & 6 deletions

File tree

src/internal.c

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11617,6 +11617,22 @@ static int DoGlobalRequest(WOLFSSH* ssh,
1161711617
}
1161811618

1161911619
if (ret == WS_SUCCESS) {
11620+
#ifdef WOLFSSH_FWD
11621+
/* RFC 4254 section 7.1: a remote forward is the client's to ask for,
11622+
* so a client that receives the request answers a failure rather than
11623+
* registering a forward on the peer's say-so. Answered here and not
11624+
* in DoGlobalRequestFwd(), so the request body is never parsed and no
11625+
* forward state is touched. */
11626+
if ((globReqId == ID_GLOBREQ_TCPIP_FWD
11627+
|| globReqId == ID_GLOBREQ_TCPIP_FWD_CANCEL)
11628+
&& ssh->ctx->side == WOLFSSH_ENDPOINT_CLIENT) {
11629+
WLOG(WS_LOG_WARN, "DGR: rejecting %s received by a client", name);
11630+
if (wantReply) {
11631+
ret = SendRequestSuccess(ssh, 0);
11632+
}
11633+
}
11634+
else
11635+
#endif
1162011636
switch (globReqId) {
1162111637
#ifdef WOLFSSH_FWD
1162211638
case ID_GLOBREQ_TCPIP_FWD:
@@ -12928,6 +12944,57 @@ static int DoChannelExtendedData(WOLFSSH* ssh,
1292812944
}
1292912945

1293012946

12947+
/* Has DoPacket()'s dispatch a case for this id? Keep in step with it, guards
12948+
* included: a message compiled out is one this build does not recognize. */
12949+
INLINE static int MsgIdKnown(byte msg)
12950+
{
12951+
switch (msg) {
12952+
case MSGID_DISCONNECT:
12953+
case MSGID_IGNORE:
12954+
case MSGID_UNIMPLEMENTED:
12955+
case MSGID_REQUEST_SUCCESS:
12956+
case MSGID_REQUEST_FAILURE:
12957+
case MSGID_DEBUG:
12958+
case MSGID_EXT_INFO:
12959+
case MSGID_KEXINIT:
12960+
case MSGID_NEWKEYS:
12961+
case MSGID_KEXDH_INIT:
12962+
case MSGID_KEXDH_REPLY:
12963+
#ifndef WOLFSSH_NO_DH_GEX_SHA256
12964+
case MSGID_KEXDH_GEX_REQUEST:
12965+
#endif
12966+
case MSGID_KEXDH_GEX_INIT:
12967+
case MSGID_KEXDH_GEX_REPLY:
12968+
case MSGID_SERVICE_REQUEST:
12969+
case MSGID_SERVICE_ACCEPT:
12970+
case MSGID_USERAUTH_REQUEST:
12971+
#ifdef WOLFSSH_KEYBOARD_INTERACTIVE
12972+
case MSGID_USERAUTH_INFO_RESPONSE:
12973+
case MSGID_USERAUTH_INFO_REQUEST:
12974+
#endif
12975+
case MSGID_USERAUTH_FAILURE:
12976+
case MSGID_USERAUTH_SUCCESS:
12977+
case MSGID_USERAUTH_BANNER:
12978+
case MSGID_GLOBAL_REQUEST:
12979+
case MSGID_CHANNEL_OPEN:
12980+
case MSGID_CHANNEL_OPEN_CONF:
12981+
case MSGID_CHANNEL_OPEN_FAIL:
12982+
case MSGID_CHANNEL_WINDOW_ADJUST:
12983+
case MSGID_CHANNEL_DATA:
12984+
case MSGID_CHANNEL_EXTENDED_DATA:
12985+
case MSGID_CHANNEL_EOF:
12986+
case MSGID_CHANNEL_CLOSE:
12987+
case MSGID_CHANNEL_REQUEST:
12988+
case MSGID_CHANNEL_SUCCESS:
12989+
case MSGID_CHANNEL_FAILURE:
12990+
return 1;
12991+
12992+
default:
12993+
return 0;
12994+
}
12995+
}
12996+
12997+
1293112998
static int DoPacket(WOLFSSH* ssh, byte* bufferConsumed)
1293212999
{
1293313000
byte* buf = (byte*)ssh->inputBuffer.buffer;
@@ -12939,6 +13006,7 @@ static int DoPacket(WOLFSSH* ssh, byte* bufferConsumed)
1293913006
byte padSz;
1294013007
byte msg;
1294113008
word32 payloadIdx = 0;
13009+
int msgAllowed;
1294213010
int ret = WS_SUCCESS;
1294313011

1294413012
WLOG(WS_LOG_DEBUG, "DoPacket sequence number: %d", ssh->peerSeq);
@@ -12973,7 +13041,15 @@ static int DoPacket(WOLFSSH* ssh, byte* bufferConsumed)
1297313041
return WS_OVERFLOW_E;
1297413042
}
1297513043

12976-
if (!IsMessageAllowed(ssh, msg, WS_MSG_RECV)) {
13044+
msgAllowed = IsMessageAllowed(ssh, msg, WS_MSG_RECV);
13045+
13046+
if (!msgAllowed && (MsgIdKnown(msg) || MSGIDLIMIT_POST_USERAUTH(msg))) {
13047+
/* RFC 4252 section 6: disconnect on a known id at the wrong time,
13048+
* and on any id of 80 or higher, which IsMessageAllowed() refuses
13049+
* only before auth. Silent once over, RFC 4253 section 11.1. */
13050+
if (!ssh->disconnected) {
13051+
(void)SendDisconnect(ssh, WOLFSSH_DISCONNECT_PROTOCOL_ERROR);
13052+
}
1297713053
return WS_MSGID_NOT_ALLOWED_E;
1297813054
}
1297913055

@@ -12987,6 +13063,12 @@ static int DoPacket(WOLFSSH* ssh, byte* bufferConsumed)
1298713063
WLOG(WS_LOG_DEBUG, "Ignoring message ID %u after a disconnect",
1298813064
(word32)msg);
1298913065
}
13066+
else if (!msgAllowed) {
13067+
/* Refused, unimplemented, below 80. Answered off the dispatch so a
13068+
* refused id cannot reach a handler if MsgIdKnown() drifts. */
13069+
WLOG(WS_LOG_DEBUG, "Unimplemented message ID (%d)", msg);
13070+
ret = SendUnimplemented(ssh);
13071+
}
1299013072
else switch (msg) {
1299113073

1299213074
case MSGID_DISCONNECT:
@@ -13190,6 +13272,7 @@ static int DoPacket(WOLFSSH* ssh, byte* bufferConsumed)
1319013272
break;
1319113273

1319213274
default:
13275+
/* RFC 4253 section 11.4, reached whatever the state allows. */
1319313276
WLOG(WS_LOG_DEBUG, "Unimplemented message ID (%d)", msg);
1319413277
#ifdef SHOW_UNIMPLEMENTED
1319513278
DumpOctetString(buf + idx, payloadSz);

tests/regress.c

Lines changed: 218 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2297,8 +2297,9 @@ static void TestClientServiceAcceptBlockedDuringKeying(WOLFSSH* ssh)
22972297

22982298

22992299
/* Drive the whole receive path with a CHANNEL_OPEN sent from a pre-auth
2300-
* connectState: no channel created, no reply emitted. The connectState
2301-
* gate does the rejecting; isKeying below is scene-setting only. */
2300+
* connectState: no channel created, and the only thing sent back is the
2301+
* disconnect RFC 4252 section 6 asks for. The connectState gate does the
2302+
* rejecting; isKeying below is scene-setting only. */
23022303
static void TestChannelOpenRejectedBeforeKex(byte connectState)
23032304
{
23042305
WOLFSSH_CTX* ctx;
@@ -2330,7 +2331,11 @@ static void TestChannelOpenRejectedBeforeKex(byte connectState)
23302331
AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E);
23312332
AssertNull(ssh->channelList);
23322333
AssertIntEQ(ssh->channelListSz, 0);
2333-
AssertIntEQ(io.outSz, 0);
2334+
/* Not silence: the peer is told why the session ended, and the message
2335+
* is a disconnect rather than anything answering the channel open. */
2336+
AssertTrue(io.outSz > 0);
2337+
AssertIntEQ(ParseMsgId(io.out, io.outSz), MSGID_DISCONNECT);
2338+
AssertTrue(ssh->disconnected);
23342339

23352340
wolfSSH_free(ssh);
23362341
wolfSSH_CTX_free(ctx);
@@ -2403,6 +2408,103 @@ static void TestServerUserauthBlockedBeforeKeyed(WOLFSSH* ssh)
24032408
}
24042409

24052410

2411+
/* One packet fed to a keyed but unauthenticated server. The cases below
2412+
* differ only in the packet and the answer it draws. */
2413+
static void RunServerMsgIdAtKeyed(const byte* pkt, word32 pktSz,
2414+
int expectRet, int expectErr, byte expectReplyMsgId,
2415+
int expectDisconnected)
2416+
{
2417+
WOLFSSH_CTX* ctx;
2418+
WOLFSSH* ssh;
2419+
MemIo io;
2420+
byte out[256];
2421+
2422+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
2423+
AssertNotNull(ctx);
2424+
wolfSSH_SetIORecv(ctx, MemRecv);
2425+
wolfSSH_SetIOSend(ctx, MemSend);
2426+
2427+
ssh = wolfSSH_new(ctx);
2428+
AssertNotNull(ssh);
2429+
2430+
MemIoInit(&io, (byte*)pkt, pktSz, out, sizeof(out));
2431+
wolfSSH_SetIOReadCtx(ssh, &io);
2432+
wolfSSH_SetIOWriteCtx(ssh, &io);
2433+
2434+
/* Past the key exchange, short of user auth being answered. */
2435+
ssh->acceptState = ACCEPT_KEYED;
2436+
2437+
AssertIntEQ(wolfSSH_TestDoReceive(ssh), expectRet);
2438+
AssertIntEQ(ssh->error, expectErr);
2439+
/* Nothing the filter refuses may leave a channel behind. */
2440+
AssertNull(ssh->channelList);
2441+
AssertIntEQ(ssh->channelListSz, 0);
2442+
AssertTrue(io.outSz > 0);
2443+
AssertIntEQ(ParseMsgId(io.out, io.outSz), expectReplyMsgId);
2444+
AssertIntEQ(ssh->disconnected != 0, expectDisconnected);
2445+
2446+
wolfSSH_free(ssh);
2447+
wolfSSH_CTX_free(ctx);
2448+
}
2449+
2450+
2451+
/* The case RFC 4252 section 6 names: an id of 80 or higher before user auth.
2452+
* Keyed, so the post-userauth limit rejects it, not the pre-keyed range. */
2453+
static void TestServerHighMsgIdBeforeAuthDisconnects(void)
2454+
{
2455+
byte pkt[256];
2456+
word32 pktSz;
2457+
2458+
pktSz = BuildChannelOpenPacket("session", 0, 131072, 16384, NULL, 0,
2459+
pkt, sizeof(pkt));
2460+
2461+
RunServerMsgIdAtKeyed(pkt, pktSz, WS_FATAL_ERROR, WS_MSGID_NOT_ALLOWED_E,
2462+
MSGID_DISCONNECT, 1);
2463+
}
2464+
2465+
2466+
/* Id 79 is refused too, but it is unimplemented and below 80, so RFC 4253
2467+
* section 11.4 rules: UNIMPLEMENTED, and the session lives. */
2468+
static void TestServerUnknownMsgIdBeforeAuthUnimplemented(void)
2469+
{
2470+
byte pkt[256];
2471+
word32 pktSz;
2472+
2473+
pktSz = WrapPacket(79, NULL, 0, pkt, sizeof(pkt));
2474+
2475+
RunServerMsgIdAtKeyed(pkt, pktSz, WS_SUCCESS, WS_SUCCESS,
2476+
MSGID_UNIMPLEMENTED, 0);
2477+
}
2478+
2479+
2480+
/* Below 80 the dispatch decides: 53 has a case, so it disconnects where
2481+
* 79 does not. */
2482+
static void TestServerKnownAuthMsgIdBeforeAuthDisconnects(void)
2483+
{
2484+
byte pkt[256];
2485+
word32 pktSz;
2486+
2487+
pktSz = WrapPacket(MSGID_USERAUTH_BANNER, NULL, 0, pkt, sizeof(pkt));
2488+
2489+
RunServerMsgIdAtKeyed(pkt, pktSz, WS_FATAL_ERROR, WS_MSGID_NOT_ALLOWED_E,
2490+
MSGID_DISCONNECT, 1);
2491+
}
2492+
2493+
2494+
/* At 80 and above the range decides instead: id 200 disconnects though
2495+
* nothing dispatches it. */
2496+
static void TestServerUnknownHighMsgIdBeforeAuthDisconnects(void)
2497+
{
2498+
byte pkt[256];
2499+
word32 pktSz;
2500+
2501+
pktSz = WrapPacket(200, NULL, 0, pkt, sizeof(pkt));
2502+
2503+
RunServerMsgIdAtKeyed(pkt, pktSz, WS_FATAL_ERROR, WS_MSGID_NOT_ALLOWED_E,
2504+
MSGID_DISCONNECT, 1);
2505+
}
2506+
2507+
24062508
/* Reject the user auth messages that only the server sends, while still
24072509
* accepting the keyboard-interactive info response that it receives. */
24082510
static void TestServerOnlyUserauthMsgsBlocked(WOLFSSH* ssh)
@@ -2492,8 +2594,11 @@ static void TestServerServiceRequestRejectedDuringKeying(void)
24922594
/* Not dispatched, so acceptState did not advance. */
24932595
AssertIntEQ(ssh->clientState, CLIENT_BEGIN);
24942596
AssertIntEQ(ssh->acceptState, ACCEPT_KEYED);
2495-
/* Nothing emitted in reply. */
2496-
AssertIntEQ(io.outSz, 0);
2597+
/* Not silence: nothing answers the service request, but the peer is told
2598+
* the session ended on a protocol error. */
2599+
AssertTrue(io.outSz > 0);
2600+
AssertIntEQ(ParseMsgId(io.out, io.outSz), MSGID_DISCONNECT);
2601+
AssertTrue(ssh->disconnected);
24972602

24982603
/* Allowed when only this side has started a rekey. */
24992604
ssh->error = 0;
@@ -3077,6 +3182,104 @@ static void TestGlobalRequestFwdNoCbSendsFailure(void)
30773182
FreeChannelOpenHarness(&harness);
30783183
}
30793184

3185+
#ifndef NO_WOLFSSH_CLIENT
3186+
/* A client is the side that asks for a remote forward, so a tcpip-forward it
3187+
* receives is answered with a failure and never registered. RFC 4254 section
3188+
* 7.1. A fwdCb is registered throughout: without the role check that callback
3189+
* is the only gate, and it would answer success. */
3190+
static void TestGlobalRequestFwdOnClientSendsFailure(void)
3191+
{
3192+
ChannelOpenHarness harness;
3193+
byte in[256];
3194+
word32 inSz;
3195+
int ret;
3196+
3197+
inSz = BuildGlobalRequestFwdPacket("0.0.0.0", 2222, 0, 1, in, sizeof(in));
3198+
InitChannelOpenHarnessClient(&harness, in, inSz);
3199+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx, AcceptFwdCb, NULL),
3200+
WS_SUCCESS);
3201+
3202+
ret = DoReceive(harness.ssh);
3203+
3204+
AssertIntEQ(ret, WS_SUCCESS);
3205+
AssertGlobalRequestReply(&harness, MSGID_REQUEST_FAILURE);
3206+
3207+
FreeChannelOpenHarness(&harness);
3208+
}
3209+
3210+
/* cancel-tcpip-forward travels the same direction, so a client refuses it on
3211+
* the same grounds. */
3212+
static void TestGlobalRequestFwdCancelOnClientSendsFailure(void)
3213+
{
3214+
ChannelOpenHarness harness;
3215+
byte in[256];
3216+
word32 inSz;
3217+
int ret;
3218+
3219+
inSz = BuildGlobalRequestFwdPacket("0.0.0.0", 2222, 1, 1, in, sizeof(in));
3220+
InitChannelOpenHarnessClient(&harness, in, inSz);
3221+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx, AcceptFwdCb, NULL),
3222+
WS_SUCCESS);
3223+
3224+
ret = DoReceive(harness.ssh);
3225+
3226+
AssertIntEQ(ret, WS_SUCCESS);
3227+
AssertGlobalRequestReply(&harness, MSGID_REQUEST_FAILURE);
3228+
3229+
FreeChannelOpenHarness(&harness);
3230+
}
3231+
3232+
/* The refusal is silent when the peer did not ask for a reply: nothing goes
3233+
* back, and the session carries on. Silence alone would also be the answer
3234+
* without the role check, since a request the callback accepts and that asks
3235+
* for no reply sends nothing either, so the callback is the discriminator
3236+
* here: the request must be turned away before it reaches one. */
3237+
static void TestGlobalRequestFwdOnClientNoReplyStaysQuiet(void)
3238+
{
3239+
ChannelOpenHarness harness;
3240+
byte in[256];
3241+
word32 inSz;
3242+
int ret;
3243+
3244+
inSz = BuildGlobalRequestFwdPacket("0.0.0.0", 2222, 0, 0, in, sizeof(in));
3245+
InitChannelOpenHarnessClient(&harness, in, inSz);
3246+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx, CountingFwdCb, NULL),
3247+
WS_SUCCESS);
3248+
fwdCbCallCount = 0;
3249+
3250+
ret = DoReceive(harness.ssh);
3251+
3252+
AssertIntEQ(ret, WS_SUCCESS);
3253+
AssertIntEQ(harness.io.outSz, 0);
3254+
AssertIntEQ(fwdCbCallCount, 0);
3255+
3256+
FreeChannelOpenHarness(&harness);
3257+
}
3258+
3259+
#endif /* !NO_WOLFSSH_CLIENT */
3260+
3261+
/* The role check must not cost the server anything: the same request that a
3262+
* client refuses is still honoured here. */
3263+
static void TestGlobalRequestFwdOnServerStillSucceeds(void)
3264+
{
3265+
ChannelOpenHarness harness;
3266+
byte in[256];
3267+
word32 inSz;
3268+
int ret;
3269+
3270+
inSz = BuildGlobalRequestFwdPacket("0.0.0.0", 2222, 0, 1, in, sizeof(in));
3271+
InitChannelOpenHarness(&harness, in, inSz);
3272+
AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx, AcceptFwdCb, NULL),
3273+
WS_SUCCESS);
3274+
3275+
ret = DoReceive(harness.ssh);
3276+
3277+
AssertIntEQ(ret, WS_SUCCESS);
3278+
AssertGlobalRequestReply(&harness, MSGID_REQUEST_SUCCESS);
3279+
3280+
FreeChannelOpenHarness(&harness);
3281+
}
3282+
30803283
static void TestGlobalRequestFwdNoCbNoReplyKeepsConnection(void)
30813284
{
30823285
ChannelOpenHarness harness;
@@ -10816,6 +11019,10 @@ int main(int argc, char** argv)
1081611019
TestServerChannelBlockedBeforeAuth(serverSsh);
1081711020
TestServerChannelAllowedAfterAuth(serverSsh);
1081811021
TestServerUserauthBlockedBeforeKeyed(serverSsh);
11022+
TestServerHighMsgIdBeforeAuthDisconnects();
11023+
TestServerUnknownMsgIdBeforeAuthUnimplemented();
11024+
TestServerKnownAuthMsgIdBeforeAuthDisconnects();
11025+
TestServerUnknownHighMsgIdBeforeAuthDisconnects();
1081911026
TestServerOnlyUserauthMsgsBlocked(serverSsh);
1082011027
TestServerServiceRequestStateGated(serverSsh);
1082111028
TestServerServiceRequestRejectedDuringKeying();
@@ -10836,6 +11043,12 @@ int main(int argc, char** argv)
1083611043
TestDirectTcpipFwdCbRejectsChannelId();
1083711044
TestForwardedTcpipOnServerSendsOpenFail();
1083811045
TestGlobalRequestFwdNoCbSendsFailure();
11046+
#ifndef NO_WOLFSSH_CLIENT
11047+
TestGlobalRequestFwdOnClientSendsFailure();
11048+
TestGlobalRequestFwdCancelOnClientSendsFailure();
11049+
TestGlobalRequestFwdOnClientNoReplyStaysQuiet();
11050+
#endif
11051+
TestGlobalRequestFwdOnServerStillSucceeds();
1083911052
TestGlobalRequestFwdNoCbNoReplyKeepsConnection();
1084011053
TestGlobalRequestFwdWithCbSendsSuccess();
1084111054
TestGlobalRequestFwdPort0ReturnsAllocatedPort();

0 commit comments

Comments
 (0)