Skip to content

Commit 54b08ff

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
1 parent 7325678 commit 54b08ff

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
@@ -13196,6 +13196,28 @@ int DoProtoId(WOLFSSH* ssh)
1319613196
}
1319713197

1319813198

13199+
/* Validates a locally configured proto ID string */
13200+
int ValidateProtoId(const char* protoIdStr, word32 len)
13201+
{
13202+
/* Length is checked first: the prefix, terminator, and body checks below
13203+
* index and subtract from len. The minimum is the "SSH-2.0-" prefix plus
13204+
* one body byte plus CRLF. */
13205+
if (protoIdStr == NULL || len < SSH_PROTO_SZ + 3 ||
13206+
protoIdStr[len-1] != '\n' || protoIdStr[len-2] != '\r' ||
13207+
len > WOLFSSH_PROTOID_LIMIT ||
13208+
WSTRNCMP(protoIdStr, sshProtoIdPrefix, SSH_PROTO_SZ) != 0 ||
13209+
WSTRNSTR(protoIdStr, "\n", len - 2) != NULL ||
13210+
WSTRNSTR(protoIdStr, "\r", len - 2) != NULL) {
13211+
WLOG(WS_LOG_ERROR, "Proto Id was invalid: it must start with "
13212+
"\"SSH-2.0-\", end in \\r\\n, be no longer than %d bytes, "
13213+
"and must not contain \\r or \\n in the body of the line",
13214+
WOLFSSH_PROTOID_LIMIT);
13215+
return WS_BAD_ARGUMENT;
13216+
}
13217+
return WS_SUCCESS;
13218+
}
13219+
13220+
1319913221
int SendProtoId(WOLFSSH* ssh)
1320013222
{
1320113223
int ret = WS_SUCCESS;

src/ssh.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3447,10 +3447,18 @@ int wolfSSH_GetMaxAuthAttempts(WOLFSSH* ssh)
34473447
int wolfSSH_CTX_SetSshProtoIdStr(WOLFSSH_CTX* ctx,
34483448
const char* protoIdStr)
34493449
{
3450+
int ret;
3451+
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_SetSshProtoIdStr()");
3452+
34503453
if (!ctx || !protoIdStr) {
34513454
return WS_BAD_ARGUMENT;
34523455
}
34533456

3457+
if ((ret = ValidateProtoId(protoIdStr, (word32)WSTRLEN(protoIdStr))) !=
3458+
WS_SUCCESS) {
3459+
return ret;
3460+
}
3461+
34543462
ctx->sshProtoIdStr = protoIdStr;
34553463
return WS_SUCCESS;
34563464
}

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
@@ -708,6 +708,8 @@ enum NameIdType {
708708
#ifndef WOLFSSH_MAX_BANNER_LINES
709709
#define WOLFSSH_MAX_BANNER_LINES 10
710710
#endif
711+
/* RFC 4253 4.2. Documented as a literal 255 on
712+
* wolfSSH_CTX_SetSshProtoIdStr() in the public ssh.h; keep in sync. */
711713
#define WOLFSSH_PROTOID_LIMIT 255
712714

713715
/* Keep track of keying state for both sides of the connection.
@@ -1547,6 +1549,7 @@ WOLFSSH_LOCAL int DoProtoId(WOLFSSH* ssh);
15471549
WOLFSSH_LOCAL int wolfSSH_SendPacket(WOLFSSH* ssh);
15481550
WOLFSSH_LOCAL int wolfSSH_OutputPending(WOLFSSH* ssh);
15491551
WOLFSSH_LOCAL int SendProtoId(WOLFSSH* ssh);
1552+
WOLFSSH_LOCAL int ValidateProtoId(const char* protoIdStr, word32 len);
15501553
WOLFSSH_LOCAL int SendKexInit(WOLFSSH* ssh);
15511554
WOLFSSH_LOCAL int SendKexDhInit(WOLFSSH* ssh);
15521555
WOLFSSH_LOCAL int SendKexDhReply(WOLFSSH* ssh);

wolfssh/ssh.h

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

590590
WOLFSSH_API int wolfSSH_CTX_SetBanner(WOLFSSH_CTX* ctx, const char* newBanner);
591+
/* ProtoIdStr is checked for validity and will be rejected unless
592+
* it adheres to these criteria:
593+
* MUST begin with "SSH-2.0-"
594+
* MUST be between 11 and 255 bytes in length, counting the "SSH-2.0-"
595+
* prefix and the trailing "\r\n"
596+
* MUST end with '\r\n'
597+
* MUST NOT contain '\r' or '\n' in the body
598+
* If these are not adhered to the function will return WS_BAD_ARGUMENT
599+
* and not load the ProtoId in to the WOLFSSH_CTX struct.
600+
* ProtoIdStr is stored by reference and is not copied, so it must remain
601+
* valid for the lifetime of the WOLFSSH_CTX. */
591602
WOLFSSH_API int wolfSSH_CTX_SetSshProtoIdStr(WOLFSSH_CTX* ctx,
592603
const char* protoIdStr);
593604
/* Set the server-side limit on failed userauth attempts per connection. The

0 commit comments

Comments
 (0)