Skip to content

Commit 28de6e1

Browse files
Skoll review and Github review
Added more test vectors as well as locking in behavior of VerifyProtoId Added round trip test for proto id Comment fixes New define for proto id min size
1 parent 0b1405a commit 28de6e1

4 files changed

Lines changed: 152 additions & 29 deletions

File tree

src/internal.c

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14187,12 +14187,15 @@ int ValidateProtoId(const char* protoIdStr, word32 len)
1418714187
{
1418814188
word32 i;
1418914189

14190+
/* The length check must stay first: every check below indexes into
14191+
* protoIdStr or subtracts from the unsigned len. The minimum is the
14192+
* "SSH-2.0-" prefix plus one body byte plus CRLF. */
1419014193
if (protoIdStr == NULL ||
14191-
len < SSH_PROTO_SZ + 1 + SSH_PROTO_EOL_SZ ||
14194+
len < SSH_PROTO_MIN ||
1419214195
len > WOLFSSH_PROTOID_LIMIT) {
1419314196
WLOG(WS_LOG_ERROR, "Proto Id was invalid: it must be between %d and "
1419414197
"%d bytes, counting the prefix and the terminator",
14195-
SSH_PROTO_SZ + 1 + SSH_PROTO_EOL_SZ, WOLFSSH_PROTOID_LIMIT);
14198+
SSH_PROTO_MIN, WOLFSSH_PROTOID_LIMIT);
1419614199
return WS_BAD_ARGUMENT;
1419714200
}
1419814201

@@ -14202,14 +14205,23 @@ int ValidateProtoId(const char* protoIdStr, word32 len)
1420214205
return WS_BAD_ARGUMENT;
1420314206
}
1420414207

14208+
/* RFC 4253 section 4.2 splits the line as "SSH-2.0-" softwareversion
14209+
* [SP comments] CRLF. A leading space would make softwareversion
14210+
* empty, so reject it. */
14211+
if (protoIdStr[SSH_PROTO_SZ] == ' ') {
14212+
WLOG(WS_LOG_ERROR, "Proto Id was invalid: the body must start with a "
14213+
"non-space character");
14214+
return WS_BAD_ARGUMENT;
14215+
}
14216+
1420514217
if (protoIdStr[len - 1] != '\n' || protoIdStr[len - 2] != '\r') {
1420614218
WLOG(WS_LOG_ERROR, "Proto Id was invalid: it must end in \\r\\n");
1420714219
return WS_BAD_ARGUMENT;
1420814220
}
1420914221

1421014222
for (i = 0; i < len - SSH_PROTO_EOL_SZ; i++) {
1421114223
byte c = (byte)protoIdStr[i];
14212-
14224+
/* spaces are intetionally allowed */
1421314225
if (c < 0x20 || c > 0x7e) {
1421414226
WLOG(WS_LOG_ERROR, "Proto Id was invalid: byte %u is "
1421514227
"not printable US-ASCII", i);
@@ -24268,6 +24280,11 @@ int wolfSSH_TestDoProtoId(WOLFSSH* ssh)
2426824280
return DoProtoId(ssh);
2426924281
}
2427024282

24283+
int wolfSSH_TestSendProtoId(WOLFSSH* ssh)
24284+
{
24285+
return SendProtoId(ssh);
24286+
}
24287+
2427124288
int wolfSSH_TestIsMessageAllowed(WOLFSSH* ssh, byte msg, byte state)
2427224289
{
2427324290
return IsMessageAllowed(ssh, msg, state);

tests/unit.c

Lines changed: 124 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,30 @@ static const ProtoIdScriptVector protoIdScriptVectors[] = {
464464
WOLFSSH_ENDPOINT_CLIENT, WS_VERSION_E },
465465
};
466466

467+
/* Capture-to-buffer send callback for the SendProtoId() vectors. The
468+
* proto ID is the first thing on the wire, so everything the callback
469+
* sees is the ID line itself. */
470+
typedef struct ProtoIdSendState {
471+
byte buf[WOLFSSH_PROTOID_LIMIT + 1];
472+
word32 len;
473+
} ProtoIdSendState;
474+
475+
static int ProtoIdCaptureSend(WOLFSSH* ssh, void* buf, word32 sz, void* ctx)
476+
{
477+
ProtoIdSendState* s = (ProtoIdSendState*)ctx;
478+
479+
WOLFSSH_UNUSED(ssh);
480+
481+
if (sz > sizeof(s->buf) - s->len)
482+
return WS_CBIO_ERR_GENERAL;
483+
484+
WMEMCPY(s->buf + s->len, buf, sz);
485+
s->len += sz;
486+
487+
return (int)sz;
488+
}
489+
490+
467491
/* DoProtoId() Unit Test */
468492
static int test_DoProtoId(void)
469493
{
@@ -557,44 +581,54 @@ static int test_DoProtoId(void)
557581
}
558582
}
559583

584+
{
585+
static char tooLongProtoId[WOLFSSH_PROTOID_LIMIT + 2];
586+
static char justRightProtoId[WOLFSSH_PROTOID_LIMIT + 1];
560587
/* Ensure a malformed local protoId cannot be loaded. */
561588
{
562-
static char tooLongProtoId[257];
563-
static char justRightProtoId[256];
564589
static const struct {
565590
const char* name;
566591
const char* id;
567592
int expectSuccess;
568593
} protoIds[] = {
569-
{ "conforming custom ID", "SSH-2.0-this_is_my_app\r\n", 1 },
570-
{ "shortest valid Id", "SSH-2.0-t\r\n", 1 },
571-
{ "exact len custom ID", justRightProtoId, 1 },
572-
{ "wrong version prefix", "SSH-2-this_is_my_app\r\n", 0 },
573-
{ "bad casing prefix", "sSH-2.0-this_is_my_app\r\n", 0 },
574-
{ "LF terminator only", "SSH-2.0-this_is_my_app\n", 0 },
575-
{ "CR terminator only", "SSH-2.0-this_is_my_app\r", 0 },
576-
{ "empty string", "", 0 },
577-
{ "prefix with no body", "SSH-2.0-\r\n", 0 },
578-
{ "missing prefix", "hello-this-is\r\n", 0 },
579-
{ "non ascii char", "SSH-2.0-\x90s\r\n", 0 },
580-
{ "Body End in CR", "SSH-2.0-s\r\r\n", 0 },
581-
{ "Body End in TAB", "SSH-2.0-s\t\r\n", 0 },
582-
{ "Body Have bad char", "SSH-2.0-\x02-a\t\r\n", 0 },
583-
{ "too long id", tooLongProtoId, 0 },
594+
{ "conforming custom ID", "SSH-2.0-this_is_my_app\r\n", 1 },
595+
{ "shortest valid Id", "SSH-2.0-t\r\n", 1 },
596+
{ "exact len custom ID", justRightProtoId, 1 },
597+
/* Pin the printable-ASCII range as inclusive at both ends: an
598+
* interior 0x20 (the RFC 4253 "SP comments" suffix) and a
599+
* 0x7e must both be accepted. */
600+
{ "body w/ SP comments", "SSH-2.0-app comment\r\n", 1 },
601+
{ "body w/ tilde", "SSH-2.0-app~1\r\n", 1 },
602+
/* Failing Tests */
603+
{ "wrong version prefix", "SSH-2-this_is_my_app\r\n", 0 },
604+
{ "bad casing prefix", "sSH-2.0-this_is_my_app\r\n", 0 },
605+
{ "LF terminator only", "SSH-2.0-this_is_my_app\n", 0 },
606+
{ "CR terminator only", "SSH-2.0-this_is_my_app\r", 0 },
607+
{ "empty string", "", 0 },
608+
{ "prefix with no body", "SSH-2.0-\r\n", 0 },
609+
{ "missing prefix", "hello-this-is\r\n", 0 },
610+
{ "non ascii char", "SSH-2.0-\x90s\r\n", 0 },
611+
{ "Body End in CR", "SSH-2.0-s\r\r\n", 0 },
612+
{ "Body End in TAB", "SSH-2.0-s\t\r\n", 0 },
613+
{ "body starts w/ space", "SSH-2.0-\x20-a-b\r\n", 0 },
614+
{ "body has embedded TAB", "SSH-2.0-\x7e-a\t\r\n", 0 },
615+
{ "too long id", tooLongProtoId, 0 },
616+
{ "null pointer", NULL, 0 },
584617
};
585618
int pc = (int)(sizeof(protoIds) / sizeof(protoIds[0]));
586619
WMEMSET(tooLongProtoId, 'a', sizeof(tooLongProtoId));
587620
WMEMCPY(tooLongProtoId, "SSH-2.0-", sizeof("SSH-2.0-") - 1);
588-
tooLongProtoId[256] = '\0';
589-
tooLongProtoId[255] = '\n';
590-
tooLongProtoId[254] = '\r';
621+
tooLongProtoId[WOLFSSH_PROTOID_LIMIT + 1] = '\0';
622+
tooLongProtoId[WOLFSSH_PROTOID_LIMIT] = '\n';
623+
tooLongProtoId[WOLFSSH_PROTOID_LIMIT - 1] = '\r';
591624
WMEMSET(justRightProtoId, 'a', sizeof(justRightProtoId));
592625
WMEMCPY(justRightProtoId, "SSH-2.0-", sizeof("SSH-2.0-") - 1);
593-
justRightProtoId[255] = '\0';
594-
justRightProtoId[254] = '\n';
595-
justRightProtoId[253] = '\r';
626+
justRightProtoId[WOLFSSH_PROTOID_LIMIT] = '\0';
627+
justRightProtoId[WOLFSSH_PROTOID_LIMIT - 1] = '\n';
628+
justRightProtoId[WOLFSSH_PROTOID_LIMIT - 2] = '\r';
596629

597630
for (i = 0; i < pc; i++) {
631+
const char* prevId = clientCtx->sshProtoIdStr;
598632
ret = wolfSSH_CTX_SetSshProtoIdStr(clientCtx, protoIds[i].id);
599633
if ((ret == WS_SUCCESS) != protoIds[i].expectSuccess) {
600634
fprintf(stderr,
@@ -604,17 +638,81 @@ static int test_DoProtoId(void)
604638
: "WS_BAD_ARGUMENT");
605639
failures++;
606640
}
607-
if ((ret == WS_SUCCESS) && clientCtx->sshProtoIdStrSz !=
608-
WSTRLEN(protoIds[i].id)) {
641+
if (!protoIds[i].expectSuccess &&
642+
clientCtx->sshProtoIdStr != prevId) {
643+
fprintf(stderr,
644+
"\t[protoId %d] \"%s\" FAIL: invalid proto id "
645+
"was stored\n",
646+
i, protoIds[i].name);
647+
failures++;
648+
}
649+
if (clientCtx->sshProtoIdStrSz !=
650+
(word32)WSTRLEN(clientCtx->sshProtoIdStr)) {
609651
fprintf(stderr,
610-
"\t[protoId %d] \"%s\" FAIL: stored sshProtoIdSz "
652+
"\t[protoId %d] \"%s\" FAIL: stored sshProtoIdStrSz "
611653
"was not retained\n",
612654
i, protoIds[i].name);
613655
failures++;
614656
}
615657
}
616658
}
617659

660+
/* A configured proto ID must reach the wire byte for byte. */
661+
{
662+
static const char* const sendIds[] = {
663+
"SSH-2.0-this_is_my_app\r\n",
664+
"SSH-2.0-t\r\n",
665+
"SSH-2.0-app comment\r\n",
666+
justRightProtoId,
667+
};
668+
int sc = (int)(sizeof(sendIds) / sizeof(sendIds[0]));
669+
670+
wolfSSH_SetIOSend(clientCtx, ProtoIdCaptureSend);
671+
672+
for (i = 0; i < sc; i++) {
673+
ProtoIdSendState sendState;
674+
word32 expectSz = (word32)WSTRLEN(sendIds[i]);
675+
676+
ret = wolfSSH_CTX_SetSshProtoIdStr(clientCtx, sendIds[i]);
677+
if (ret != WS_SUCCESS) {
678+
fprintf(stderr,
679+
"\t[send %d] FAIL: set proto id returned %d\n",
680+
i, ret);
681+
failures++;
682+
continue;
683+
}
684+
685+
ssh = wolfSSH_new(clientCtx);
686+
if (ssh == NULL) {
687+
fprintf(stderr,
688+
"\t[send %d] FAIL: wolfSSH_new returned NULL\n", i);
689+
failures++;
690+
continue;
691+
}
692+
693+
WMEMSET(&sendState, 0, sizeof(sendState));
694+
wolfSSH_SetIOWriteCtx(ssh, &sendState);
695+
696+
ret = wolfSSH_TestSendProtoId(ssh);
697+
if (ret != WS_SUCCESS) {
698+
fprintf(stderr,
699+
"\t[send %d] FAIL: SendProtoId returned %d\n",
700+
i, ret);
701+
failures++;
702+
}
703+
else if (sendState.len != expectSz ||
704+
WMEMCMP(sendState.buf, sendIds[i], expectSz) != 0) {
705+
fprintf(stderr,
706+
"\t[send %d] FAIL: wrote %u bytes, expected the "
707+
"%u byte proto id back verbatim\n",
708+
i, sendState.len, expectSz);
709+
failures++;
710+
}
711+
wolfSSH_free(ssh);
712+
}
713+
}
714+
}
715+
618716
wolfSSH_CTX_free(serverCtx);
619717
wolfSSH_CTX_free(clientCtx);
620718

wolfssh/internal.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,9 @@ enum NameIdType {
577577
#define LENGTH_SZ UINT32_SZ
578578
#define SSH_PROTO_SZ 8 /* "SSH-2.0-" */
579579
#define SSH_PROTO_EOL_SZ 2 /* "\r\n" */
580+
/* Minimum size for a valid proto id *
581+
* "SSH-2.0-" <at least one body char> "\r\n" */
582+
#define SSH_PROTO_MIN (SSH_PROTO_SZ + 1 + SSH_PROTO_EOL_SZ)
580583
#define TERMINAL_MODE_SZ 5 /* opcode byte + argument uint32 */
581584
#define TERMINAL_MODES_MAX_SZ 4096
582585
#define TERMINAL_WIDTH_DEFAULT 80 /* used when there is no terminal */
@@ -1938,6 +1941,7 @@ enum WS_MessageIdLimits {
19381941

19391942
#ifdef WOLFSSH_TEST_INTERNAL
19401943
WOLFSSH_API int wolfSSH_TestDoProtoId(WOLFSSH* ssh);
1944+
WOLFSSH_API int wolfSSH_TestSendProtoId(WOLFSSH* ssh);
19411945
WOLFSSH_API int wolfSSH_TestIsMessageAllowed(WOLFSSH* ssh, byte msg,
19421946
byte state);
19431947
WOLFSSH_API int wolfSSH_TestDoReceive(WOLFSSH* ssh);

wolfssh/ssh.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,10 @@ WOLFSSH_API int wolfSSH_CTX_SetBanner(WOLFSSH_CTX* ctx, const char* newBanner);
658658
* MUST end with '\r\n'
659659
* MUST carry only printable US-ASCII (0x20 - 0x7e) in the body, which
660660
* rules out an embedded '\r' or '\n'
661+
* MUST NOT begin the body with a space; RFC 4253 section 4.2 reads the
662+
* body as softwareversion [SP comments], so a leading space would
663+
* make softwareversion empty. A space later in the body is accepted
664+
* and starts the optional comments field.
661665
* If these are not adhered to the function will return WS_BAD_ARGUMENT
662666
* and not load the ProtoId into the WOLFSSH_CTX struct.
663667
* ProtoIdStr is stored by reference and is not copied, so it must remain

0 commit comments

Comments
 (0)