Skip to content

Commit 557f3df

Browse files
aidankeefe2022ejohnstown
authored andcommitted
Validate a custom SSH proto ID string
wolfSSH_CTX_SetSshProtoIdStr() now rejects a string that is not CRLF-terminated, exceeds 255 bytes counting the terminator, or carries a CR or LF in the body. DoKexInit() subtracts the two terminator bytes from the length when hashing it, so an unterminated string underflowed the hash length. Issue: F-10571
1 parent ea530fe commit 557f3df

5 files changed

Lines changed: 66 additions & 31 deletions

File tree

src/internal.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14168,6 +14168,28 @@ int DoProtoId(WOLFSSH* ssh)
1416814168
}
1416914169

1417014170

14171+
/* Validates a locally configured proto ID string */
14172+
int ValidateProtoId(const char* protoIdStr, word32 len)
14173+
{
14174+
/* Length is checked first: the prefix, terminator, and body checks below
14175+
* index and subtract from len. The minimum is the "SSH-2.0-" prefix plus
14176+
* one body byte plus CRLF. */
14177+
if (protoIdStr == NULL || len < SSH_PROTO_SZ + 3 ||
14178+
protoIdStr[len-1] != '\n' || protoIdStr[len-2] != '\r' ||
14179+
len > WOLFSSH_PROTOID_LIMIT ||
14180+
WSTRNCMP(protoIdStr, sshProtoIdPrefix, SSH_PROTO_SZ) != 0 ||
14181+
WSTRNSTR(protoIdStr, "\n", len - 2) != NULL ||
14182+
WSTRNSTR(protoIdStr, "\r", len - 2) != NULL) {
14183+
WLOG(WS_LOG_ERROR, "Proto Id was invalid: it must start with "
14184+
"\"SSH-2.0-\", end in \\r\\n, be no longer than %d bytes, "
14185+
"and must not contain \\r or \\n in the body of the line",
14186+
WOLFSSH_PROTOID_LIMIT);
14187+
return WS_BAD_ARGUMENT;
14188+
}
14189+
return WS_SUCCESS;
14190+
}
14191+
14192+
1417114193
int SendProtoId(WOLFSSH* ssh)
1417214194
{
1417314195
int ret = WS_SUCCESS;

src/ssh.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3472,10 +3472,18 @@ int wolfSSH_GetMaxAuthAttempts(WOLFSSH* ssh)
34723472
int wolfSSH_CTX_SetSshProtoIdStr(WOLFSSH_CTX* ctx,
34733473
const char* protoIdStr)
34743474
{
3475+
int ret;
3476+
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_SetSshProtoIdStr()");
3477+
34753478
if (!ctx || !protoIdStr) {
34763479
return WS_BAD_ARGUMENT;
34773480
}
34783481

3482+
if ((ret = ValidateProtoId(protoIdStr, (word32)WSTRLEN(protoIdStr))) !=
3483+
WS_SUCCESS) {
3484+
return ret;
3485+
}
3486+
34793487
ctx->sshProtoIdStr = protoIdStr;
34803488
return WS_SUCCESS;
34813489
}

tests/unit.c

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -557,42 +557,33 @@ static int test_DoProtoId(void)
557557
}
558558
}
559559

560-
/* A non-conforming local proto ID doesn't reject a conforming peer. */
560+
/* Ensure a malformed local protoId cannot be loaded. */
561561
{
562-
static const ProtoIdTestVector customTv = {
563-
"custom local proto ID accepts peer",
564-
"SSH-2.0-OpenSSH_8.9\r\n",
565-
0, WS_SUCCESS, WOLFSSH_ENDPOINT_CLIENT
562+
static const struct {
563+
const char* name;
564+
const char* id;
565+
int expectSuccess;
566+
} protoIds[] = {
567+
{ "conforming custom ID", "SSH-2.0-this_is_my_app\r\n", 1 },
568+
{ "wrong version prefix", "SSH-2-this_is_my_app\r\n", 0 },
569+
{ "LF terminator only", "SSH-2.0-this_is_my_app\n", 0 },
570+
{ "CR terminator only", "SSH-2.0-this_is_my_app\r", 0 },
571+
{ "empty string", "", 0 },
572+
{ "prefix with no body", "SSH-2.0-\r\n", 0 },
573+
{ "missing prefix", "hello\r\n", 0 },
566574
};
567-
ProtoIdTestState state;
568-
569-
state.tv = &customTv;
570-
state.offset = 0;
575+
int pc = (int)(sizeof(protoIds) / sizeof(protoIds[0]));
571576

572-
wolfSSH_SetIORecv(clientCtx, RecvFromPtr);
573-
if (wolfSSH_CTX_SetSshProtoIdStr(clientCtx,
574-
"SSH-2.0_NonConforming\r\n") != WS_SUCCESS) {
575-
fprintf(stderr, "\t\"%s\" FAIL: SetSshProtoIdStr failed\n",
576-
customTv.name);
577-
failures++;
578-
}
579-
else {
580-
ssh = wolfSSH_new(clientCtx);
581-
if (ssh == NULL) {
582-
fprintf(stderr, "\t\"%s\" FAIL: wolfSSH_new returned NULL\n",
583-
customTv.name);
577+
for (i = 0; i < pc; i++) {
578+
ret = wolfSSH_CTX_SetSshProtoIdStr(clientCtx, protoIds[i].id);
579+
if ((ret == WS_SUCCESS) != protoIds[i].expectSuccess) {
580+
fprintf(stderr,
581+
"\t[protoId %d] \"%s\" FAIL: got %d, expected %s\n",
582+
i, protoIds[i].name, ret,
583+
protoIds[i].expectSuccess ? "WS_SUCCESS"
584+
: "WS_BAD_ARGUMENT");
584585
failures++;
585586
}
586-
else {
587-
wolfSSH_SetIOReadCtx(ssh, &state);
588-
ret = wolfSSH_TestDoProtoId(ssh);
589-
if (ret != customTv.expected) {
590-
fprintf(stderr, "\t\"%s\" FAIL: got %d, expected %d\n",
591-
customTv.name, ret, customTv.expected);
592-
failures++;
593-
}
594-
wolfSSH_free(ssh);
595-
}
596587
}
597588
}
598589

wolfssh/internal.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,8 @@ enum NameIdType {
716716
#ifndef WOLFSSH_MAX_BANNER_LINES
717717
#define WOLFSSH_MAX_BANNER_LINES 10
718718
#endif
719+
/* RFC 4253 4.2. Documented as a literal 255 on
720+
* wolfSSH_CTX_SetSshProtoIdStr() in the public ssh.h; keep in sync. */
719721
#define WOLFSSH_PROTOID_LIMIT 255
720722

721723
/* Keep track of keying state for both sides of the connection.
@@ -1645,6 +1647,7 @@ WOLFSSH_LOCAL int DoProtoId(WOLFSSH* ssh);
16451647
WOLFSSH_LOCAL int wolfSSH_SendPacket(WOLFSSH* ssh);
16461648
WOLFSSH_LOCAL int wolfSSH_OutputPending(WOLFSSH* ssh);
16471649
WOLFSSH_LOCAL int SendProtoId(WOLFSSH* ssh);
1650+
WOLFSSH_LOCAL int ValidateProtoId(const char* protoIdStr, word32 len);
16481651
WOLFSSH_LOCAL int SendKexInit(WOLFSSH* ssh);
16491652
WOLFSSH_LOCAL int SendKexDhInit(WOLFSSH* ssh);
16501653
WOLFSSH_LOCAL int SendKexDhReply(WOLFSSH* ssh);

wolfssh/ssh.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,17 @@ WOLFSSH_API int wolfSSH_SetUsername(WOLFSSH* ssh, const char* username);
650650
WOLFSSH_API char* wolfSSH_GetUsername(WOLFSSH* ssh);
651651

652652
WOLFSSH_API int wolfSSH_CTX_SetBanner(WOLFSSH_CTX* ctx, const char* newBanner);
653+
/* ProtoIdStr is checked for validity and will be rejected unless
654+
* it adheres to these criteria:
655+
* MUST begin with "SSH-2.0-"
656+
* MUST be between 11 and 255 bytes in length, counting the "SSH-2.0-"
657+
* prefix and the trailing "\r\n"
658+
* MUST end with '\r\n'
659+
* MUST NOT contain '\r' or '\n' in the body
660+
* If these are not adhered to the function will return WS_BAD_ARGUMENT
661+
* and not load the ProtoId in to the WOLFSSH_CTX struct.
662+
* ProtoIdStr is stored by reference and is not copied, so it must remain
663+
* valid for the lifetime of the WOLFSSH_CTX. */
653664
WOLFSSH_API int wolfSSH_CTX_SetSshProtoIdStr(WOLFSSH_CTX* ctx,
654665
const char* protoIdStr);
655666
/* Set the server-side limit on failed userauth attempts per connection. The

0 commit comments

Comments
 (0)