Skip to content

Commit 985107a

Browse files
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 partial progress
1 parent 3d77ce5 commit 985107a

5 files changed

Lines changed: 69 additions & 24 deletions

File tree

src/internal.c

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,6 +1337,7 @@ WOLFSSH_CTX* CtxInit(WOLFSSH_CTX* ctx, byte side, void* heap)
13371337
ctx->maxPacketSz = DEFAULT_MAX_PACKET_SZ;
13381338
ctx->maxAuthAttempts = DEFAULT_MAX_AUTH_ATTEMPTS;
13391339
ctx->sshProtoIdStr = sshProtoIdStr;
1340+
ctx->sshProtoIdStrSz = (word32)(sizeof(sshProtoIdStr) - 1);
13401341
ctx->algoListKex = cannedKexAlgoNames;
13411342
if (side == WOLFSSH_ENDPOINT_CLIENT) {
13421343
ctx->algoListKey = cannedKeyAlgoNamesHostKey;
@@ -6533,9 +6534,8 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
65336534
}
65346535

65356536
if (ret == WS_SUCCESS) {
6536-
byte SSH_PROTO_EOL_SZ = 2;
6537-
6538-
strSz = (word32)WSTRLEN(ssh->ctx->sshProtoIdStr) - SSH_PROTO_EOL_SZ;
6537+
/* The ID is hashed without its terminator. */
6538+
strSz = ssh->ctx->sshProtoIdStrSz - SSH_PROTO_EOL_SZ;
65396539
c32toa(strSz, scratchLen);
65406540
ret = HashUpdate(hash, hashId, scratchLen, LENGTH_SZ);
65416541
}
@@ -14185,21 +14185,38 @@ int DoProtoId(WOLFSSH* ssh)
1418514185
/* Validates a locally configured proto ID string */
1418614186
int ValidateProtoId(const char* protoIdStr, word32 len)
1418714187
{
14188-
/* Length is checked first: the prefix, terminator, and body checks below
14189-
* index and subtract from len. The minimum is the "SSH-2.0-" prefix plus
14190-
* one body byte plus CRLF. */
14191-
if (protoIdStr == NULL || len < SSH_PROTO_SZ + 3 ||
14192-
protoIdStr[len-1] != '\n' || protoIdStr[len-2] != '\r' ||
14193-
len > WOLFSSH_PROTOID_LIMIT ||
14194-
WSTRNCMP(protoIdStr, sshProtoIdPrefix, SSH_PROTO_SZ) != 0 ||
14195-
WSTRNSTR(protoIdStr, "\n", len - 2) != NULL ||
14196-
WSTRNSTR(protoIdStr, "\r", len - 2) != NULL) {
14188+
word32 i;
14189+
14190+
if (protoIdStr == NULL ||
14191+
len < SSH_PROTO_SZ + 1 + SSH_PROTO_EOL_SZ ||
14192+
len > WOLFSSH_PROTOID_LIMIT) {
14193+
WLOG(WS_LOG_ERROR, "Proto Id was invalid: it must be between %d and "
14194+
"%d bytes, counting the prefix and the terminator",
14195+
SSH_PROTO_SZ + 1 + SSH_PROTO_EOL_SZ, WOLFSSH_PROTOID_LIMIT);
14196+
return WS_BAD_ARGUMENT;
14197+
}
14198+
14199+
if (WSTRNCMP(protoIdStr, sshProtoIdPrefix, SSH_PROTO_SZ) != 0) {
1419714200
WLOG(WS_LOG_ERROR, "Proto Id was invalid: it must start with "
14198-
"\"SSH-2.0-\", end in \\r\\n, be no longer than %d bytes, "
14199-
"and must not contain \\r or \\n in the body of the line",
14200-
WOLFSSH_PROTOID_LIMIT);
14201+
"\"SSH-2.0-\"");
1420114202
return WS_BAD_ARGUMENT;
1420214203
}
14204+
14205+
if (protoIdStr[len - 1] != '\n' || protoIdStr[len - 2] != '\r') {
14206+
WLOG(WS_LOG_ERROR, "Proto Id was invalid: it must end in \\r\\n");
14207+
return WS_BAD_ARGUMENT;
14208+
}
14209+
14210+
for (i = 0; i < len - SSH_PROTO_EOL_SZ; i++) {
14211+
byte c = (byte)protoIdStr[i];
14212+
14213+
if (c < 0x20 || c > 0x7e) {
14214+
WLOG(WS_LOG_ERROR, "Proto Id was invalid: byte %u is "
14215+
"not printable US-ASCII", i);
14216+
return WS_BAD_ARGUMENT;
14217+
}
14218+
}
14219+
1420314220
return WS_SUCCESS;
1420414221
}
1420514222

@@ -14214,7 +14231,7 @@ int SendProtoId(WOLFSSH* ssh)
1421414231

1421514232
if (ret == WS_SUCCESS) {
1421614233
WLOG(WS_LOG_DEBUG, "%s", ssh->ctx->sshProtoIdStr);
14217-
sshProtoIdStrSz = (word32)WSTRLEN(ssh->ctx->sshProtoIdStr);
14234+
sshProtoIdStrSz = ssh->ctx->sshProtoIdStrSz;
1421814235
ret = GrowBuffer(&ssh->outputBuffer, sshProtoIdStrSz);
1421914236
}
1422014237

src/ssh.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3472,19 +3472,23 @@ int wolfSSH_GetMaxAuthAttempts(WOLFSSH* ssh)
34723472
int wolfSSH_CTX_SetSshProtoIdStr(WOLFSSH_CTX* ctx,
34733473
const char* protoIdStr)
34743474
{
3475+
word32 protoIdStrSz;
34753476
int ret;
3477+
34763478
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_SetSshProtoIdStr()");
34773479

34783480
if (!ctx || !protoIdStr) {
34793481
return WS_BAD_ARGUMENT;
34803482
}
34813483

3482-
if ((ret = ValidateProtoId(protoIdStr, (word32)WSTRLEN(protoIdStr))) !=
3483-
WS_SUCCESS) {
3484+
protoIdStrSz = (word32)WSTRLEN(protoIdStr);
3485+
ret = ValidateProtoId(protoIdStr, protoIdStrSz);
3486+
if (ret != WS_SUCCESS) {
34843487
return ret;
34853488
}
34863489

34873490
ctx->sshProtoIdStr = protoIdStr;
3491+
ctx->sshProtoIdStrSz = protoIdStrSz;
34883492
return WS_SUCCESS;
34893493
}
34903494

tests/unit.c

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -559,20 +559,40 @@ static int test_DoProtoId(void)
559559

560560
/* Ensure a malformed local protoId cannot be loaded. */
561561
{
562+
static char tooLongProtoId[257];
563+
static char justRightProtoId[256];
562564
static const struct {
563565
const char* name;
564566
const char* id;
565567
int expectSuccess;
566568
} 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+
{ "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 },
569574
{ "LF terminator only", "SSH-2.0-this_is_my_app\n", 0 },
570575
{ "CR terminator only", "SSH-2.0-this_is_my_app\r", 0 },
571576
{ "empty string", "", 0 },
572577
{ "prefix with no body", "SSH-2.0-\r\n", 0 },
573-
{ "missing prefix", "hello\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 },
574584
};
575585
int pc = (int)(sizeof(protoIds) / sizeof(protoIds[0]));
586+
WMEMSET(tooLongProtoId, 'a', sizeof(tooLongProtoId));
587+
WMEMCPY(tooLongProtoId, "SSH-2.0-", sizeof("SSH-2.0-") - 1);
588+
tooLongProtoId[256] = '\0';
589+
tooLongProtoId[255] = '\n';
590+
tooLongProtoId[254] = '\r';
591+
WMEMSET(justRightProtoId, 'a', sizeof(justRightProtoId));
592+
WMEMCPY(justRightProtoId, "SSH-2.0-", sizeof("SSH-2.0-") - 1);
593+
justRightProtoId[255] = '\0';
594+
justRightProtoId[254] = '\n';
595+
justRightProtoId[253] = '\r';
576596

577597
for (i = 0; i < pc; i++) {
578598
ret = wolfSSH_CTX_SetSshProtoIdStr(clientCtx, protoIds[i].id);

wolfssh/internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ enum NameIdType {
576576
#define UINT32_SZ 4
577577
#define LENGTH_SZ UINT32_SZ
578578
#define SSH_PROTO_SZ 8 /* "SSH-2.0-" */
579+
#define SSH_PROTO_EOL_SZ 2 /* "\r\n" */
579580
#define TERMINAL_MODE_SZ 5 /* opcode byte + argument uint32 */
580581
#define TERMINAL_MODES_MAX_SZ 4096
581582
#define TERMINAL_WIDTH_DEFAULT 80 /* used when there is no terminal */
@@ -862,6 +863,7 @@ struct WOLFSSH_CTX {
862863
const char* algoListMac;
863864
const char* algoListKeyAccepted;
864865
word32 bannerSz;
866+
word32 sshProtoIdStrSz; /* validated, counting the CRLF */
865867
word32 windowSz;
866868
word32 maxPacketSz;
867869
word32 maxAuthAttempts; /* server cap on failed userauth */

wolfssh/ssh.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -656,11 +656,13 @@ WOLFSSH_API int wolfSSH_CTX_SetBanner(WOLFSSH_CTX* ctx, const char* newBanner);
656656
* MUST be between 11 and 255 bytes in length, counting the "SSH-2.0-"
657657
* prefix and the trailing "\r\n"
658658
* MUST end with '\r\n'
659-
* MUST NOT contain '\r' or '\n' in the body
659+
* MUST carry only printable US-ASCII (0x20 - 0x7e) in the body, which
660+
* rules out an embedded '\r' or '\n'
660661
* 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+
* and not load the ProtoId into the WOLFSSH_CTX struct.
662663
* ProtoIdStr is stored by reference and is not copied, so it must remain
663-
* valid for the lifetime of the WOLFSSH_CTX. */
664+
* valid and unmodified for the lifetime of the WOLFSSH_CTX. It is validated
665+
* once, here; a later in-place rewrite of the buffer is not revalidated. */
664666
WOLFSSH_API int wolfSSH_CTX_SetSshProtoIdStr(WOLFSSH_CTX* ctx,
665667
const char* protoIdStr);
666668
/* Set the server-side limit on failed userauth attempts per connection. The

0 commit comments

Comments
 (0)