Skip to content

Commit a98fbdd

Browse files
ejohnstownphilljj
authored andcommitted
fix: clear userauth state on bad service
A USERAUTH_REQUEST naming a service other than "ssh-connection" is refused before method dispatch, so it drops the per-method state there too. A following USERAUTH_INFO_RESPONSE is no longer run against a keyboard-interactive exchange the server has already failed. - reset ssh->authId and clear ssh->kbSetupPending on the refusal - charge the abandoned exchange, as the dispatch path does - leave ssh->kbAuth alone; clients free those buffers by promptCount - add wolfSSH_TestDoUserAuthInfoResponse() for the tests - cover the refusal and the stale INFO_RESPONSE in tests/unit.c Issue: F-10575
1 parent 89b8055 commit a98fbdd

3 files changed

Lines changed: 164 additions & 12 deletions

File tree

src/internal.c

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10445,7 +10445,22 @@ static int DoUserAuthRequest(WOLFSSH* ssh,
1044510445
!= ID_SERVICE_CONNECTION) {
1044610446
WLOG(WS_LOG_DEBUG, "DUAR: Invalid service name");
1044710447
serviceValid = 0;
10448-
ret = SendUserAuthFailureCount(ssh, 0, 1);
10448+
/* Refused before method dispatch, which normally updates this
10449+
* state. Drop it here too, else a later INFO_RESPONSE runs
10450+
* against the outstanding exchange. */
10451+
ssh->authId = ID_NONE;
10452+
#ifdef WOLFSSH_KEYBOARD_INTERACTIVE
10453+
/* Charge the exchange this refusal abandons, as the dispatch
10454+
* path does; else a peer stretches the cap by alternating
10455+
* keyboard requests with bad service names. */
10456+
if (ssh->kbSetupPending) {
10457+
ssh->kbSetupPending = 0;
10458+
ret = CountUserAuthFailure(ssh);
10459+
}
10460+
#endif
10461+
if (ret == WS_SUCCESS) {
10462+
ret = SendUserAuthFailureCount(ssh, 0, 1);
10463+
}
1044910464
/* Consume all remaining data */
1045010465
*idx = len;
1045110466
}
@@ -23179,6 +23194,14 @@ int wolfSSH_TestDoUserAuthRequest(WOLFSSH* ssh, byte* buf, word32 len,
2317923194
return DoUserAuthRequest(ssh, buf, len, idx);
2318023195
}
2318123196

23197+
#ifdef WOLFSSH_KEYBOARD_INTERACTIVE
23198+
int wolfSSH_TestDoUserAuthInfoResponse(WOLFSSH* ssh, byte* buf, word32 len,
23199+
word32* idx)
23200+
{
23201+
return DoUserAuthInfoResponse(ssh, buf, len, idx);
23202+
}
23203+
#endif
23204+
2318223205
int wolfSSH_TestSendUserAuthFailure(WOLFSSH* ssh, byte partialSuccess)
2318323206
{
2318423207
return SendUserAuthFailure(ssh, partialSuccess);

tests/unit.c

Lines changed: 136 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7654,6 +7654,10 @@ static int UnitAuthAlwaysFail(byte authType, WS_UserAuthData* authData,
76547654
}
76557655

76567656
#ifdef WOLFSSH_KEYBOARD_INTERACTIVE
7657+
/* Set when the callback sees a response, so a processed INFO_RESPONSE is
7658+
* visible even when rejected. */
7659+
static int s_kbRespCbCalled = 0;
7660+
76577661
/* Keyboard setup that accepts, sending an INFO_REQUEST with no prompts so the
76587662
* exchange goes outstanding (kbSetupPending). Every other outcome fails. */
76597663
static int UnitAuthKbSetupOk(byte authType, WS_UserAuthData* authData,
@@ -7664,25 +7668,31 @@ static int UnitAuthKbSetupOk(byte authType, WS_UserAuthData* authData,
76647668
authData->sf.keyboard.promptCount = 0;
76657669
return WOLFSSH_USERAUTH_SUCCESS;
76667670
}
7671+
if (authType == WOLFSSH_USERAUTH_KEYBOARD) {
7672+
s_kbRespCbCalled = 1;
7673+
}
76677674
return WOLFSSH_USERAUTH_INVALID_PASSWORD;
76687675
}
76697676

7670-
/* Build a USERAUTH_REQUEST payload for auth method `method` with no method-
7671-
* specific fields, as DoUserAuthRequest() sees it with the message id already
7672-
* consumed. The keyboard and none dispatch paths don't parse trailing fields,
7673-
* so three strings (user, service, method) suffice. Returns the payload size,
7674-
* 0 on overflow. */
7677+
/* Build a USERAUTH_REQUEST payload for `service` and auth method `method`
7678+
* with no method-specific fields, as DoUserAuthRequest() sees it with the
7679+
* message id already consumed. The keyboard and none dispatch paths don't
7680+
* parse trailing fields, so three strings (user, service, method) suffice. A
7681+
* NULL method omits that field, which a bad service name never reaches.
7682+
* Returns the payload size, 0 on overflow. */
76757683
static word32 BuildAuthMethodRequest(byte* buf, word32 bufSz,
7676-
const char* method)
7684+
const char* service, const char* method)
76777685
{
76787686
const char* fields[3];
7679-
word32 idx = 0, i, fieldSz;
7687+
word32 idx = 0, i, fieldCount;
7688+
word32 fieldSz;
76807689

76817690
fields[0] = "jill";
7682-
fields[1] = "ssh-connection";
7691+
fields[1] = service;
76837692
fields[2] = method;
7693+
fieldCount = (method == NULL) ? 2 : 3;
76847694

7685-
for (i = 0; i < 3; i++) {
7695+
for (i = 0; i < fieldCount; i++) {
76867696
fieldSz = (word32)WSTRLEN(fields[i]);
76877697
if (idx + UINT32_SZ + fieldSz > bufSz)
76887698
return 0;
@@ -7857,7 +7867,7 @@ static int test_MaxAuthAttempts(void)
78577867
WOLFSSH* ssh = NULL;
78587868
byte kbReq[64];
78597869
word32 kbReqSz = BuildAuthMethodRequest(kbReq, (word32)sizeof(kbReq),
7860-
"keyboard-interactive");
7870+
"ssh-connection", "keyboard-interactive");
78617871

78627872
if (kbReqSz == 0)
78637873
result = -750;
@@ -7906,7 +7916,7 @@ static int test_MaxAuthAttempts(void)
79067916
WOLFSSH* ssh = NULL;
79077917
byte kbReq[64];
79087918
word32 kbReqSz = BuildAuthMethodRequest(kbReq, (word32)sizeof(kbReq),
7909-
"keyboard-interactive");
7919+
"ssh-connection", "keyboard-interactive");
79107920
int attempt;
79117921

79127922
if (kbReqSz == 0)
@@ -8124,6 +8134,114 @@ static int test_DoUserAuthRequest_serviceName(void)
81248134
}
81258135

81268136

8137+
#ifdef WOLFSSH_KEYBOARD_INTERACTIVE
8138+
/* Counts KEYBOARD (response) callbacks, so a processed INFO_RESPONSE is
8139+
* visible even when rejected. */
8140+
/* A USERAUTH_REQUEST naming a service other than "ssh-connection" must not
8141+
* leave a keyboard-interactive exchange outstanding.
8142+
*
8143+
* Asserts, in order: an accepted keyboard request leaves the exchange
8144+
* outstanding; a bad-service request clears authId and kbSetupPending; a
8145+
* following INFO_RESPONSE is rejected without reaching the callback. */
8146+
static int test_DoUserAuthRequest_badServiceClearsKb(void)
8147+
{
8148+
WOLFSSH_CTX* ctx = NULL;
8149+
WOLFSSH* ssh = NULL;
8150+
byte kbReq[64], badReq[64], infoRsp[UINT32_SZ];
8151+
word32 kbReqSz, badReqSz, idx;
8152+
int result = 0;
8153+
int ret;
8154+
8155+
kbReqSz = BuildAuthMethodRequest(kbReq, (word32)sizeof(kbReq),
8156+
"ssh-connection", "keyboard-interactive");
8157+
badReqSz = BuildAuthMethodRequest(badReq, (word32)sizeof(badReq),
8158+
"ssh-userauth", NULL);
8159+
if (kbReqSz == 0 || badReqSz == 0)
8160+
return -800;
8161+
8162+
/* INFO_RESPONSE body: response count of 0, matching promptCount. */
8163+
PutU32BE(infoRsp, 0);
8164+
8165+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
8166+
if (ctx == NULL)
8167+
return -801;
8168+
wolfSSH_SetUserAuth(ctx, UnitAuthKbSetupOk);
8169+
wolfSSH_SetIOSend(ctx, UnitIoSendSink);
8170+
8171+
ssh = wolfSSH_new(ctx);
8172+
if (ssh == NULL) {
8173+
wolfSSH_CTX_free(ctx);
8174+
return -802;
8175+
}
8176+
s_kbRespCbCalled = 0;
8177+
8178+
idx = 0;
8179+
ret = wolfSSH_TestDoUserAuthRequest(ssh, kbReq, kbReqSz, &idx);
8180+
if (ret != WS_SUCCESS) {
8181+
printf("badServiceClearsKb: kb request ret=%d expected %d\n",
8182+
ret, WS_SUCCESS);
8183+
result = -803;
8184+
}
8185+
if (result == 0 && (ssh->authId != ID_USERAUTH_KEYBOARD
8186+
|| !ssh->kbSetupPending)) {
8187+
printf("badServiceClearsKb: kb exchange not outstanding "
8188+
"(authId=%d pending=%d)\n", ssh->authId, ssh->kbSetupPending);
8189+
result = -804;
8190+
}
8191+
8192+
if (result == 0) {
8193+
idx = 0;
8194+
ret = wolfSSH_TestDoUserAuthRequest(ssh, badReq, badReqSz, &idx);
8195+
if (ret != WS_SUCCESS) {
8196+
printf("badServiceClearsKb: bad service ret=%d expected %d\n",
8197+
ret, WS_SUCCESS);
8198+
result = -805;
8199+
}
8200+
}
8201+
8202+
if (result == 0 && ssh->authId == ID_USERAUTH_KEYBOARD) {
8203+
printf("badServiceClearsKb: authId still ID_USERAUTH_KEYBOARD after "
8204+
"bad service name\n");
8205+
result = -806;
8206+
}
8207+
8208+
if (result == 0 && ssh->kbSetupPending) {
8209+
printf("badServiceClearsKb: kbSetupPending still set after bad "
8210+
"service name\n");
8211+
result = -807;
8212+
}
8213+
8214+
/* One charge for the refusal, one for the exchange it abandoned. */
8215+
if (result == 0 && ssh->authFailures != 2) {
8216+
printf("badServiceClearsKb: authFailures=%u expected 2\n",
8217+
ssh->authFailures);
8218+
result = -810;
8219+
}
8220+
8221+
if (result == 0) {
8222+
idx = 0;
8223+
ret = wolfSSH_TestDoUserAuthInfoResponse(ssh, infoRsp,
8224+
(word32)sizeof(infoRsp), &idx);
8225+
if (ret != WS_FATAL_ERROR) {
8226+
printf("badServiceClearsKb: stale INFO_RESPONSE ret=%d "
8227+
"expected %d\n", ret, WS_FATAL_ERROR);
8228+
result = -808;
8229+
}
8230+
}
8231+
8232+
if (result == 0 && s_kbRespCbCalled) {
8233+
printf("badServiceClearsKb: userauth callback reached by stale "
8234+
"INFO_RESPONSE\n");
8235+
result = -809;
8236+
}
8237+
8238+
wolfSSH_free(ssh);
8239+
wolfSSH_CTX_free(ctx);
8240+
return result;
8241+
}
8242+
#endif /* WOLFSSH_KEYBOARD_INTERACTIVE */
8243+
8244+
81278245
/* userauth callback that records whether it was invoked. Returns SUCCESS so
81288246
* that, if it were ever reached for a password-change request, the request
81298247
* would be (incorrectly) authenticated - making a missed rejection visible. */
@@ -17705,6 +17823,13 @@ int wolfSSH_UnitTest(int argc, char** argv)
1770517823
(unitResult == 0 ? "SUCCESS" : "FAILED"));
1770617824
testResult = testResult || unitResult;
1770717825

17826+
#ifdef WOLFSSH_KEYBOARD_INTERACTIVE
17827+
unitResult = test_DoUserAuthRequest_badServiceClearsKb();
17828+
printf("DoUserAuthRequest_badServiceClearsKb: %s\n",
17829+
(unitResult == 0 ? "SUCCESS" : "FAILED"));
17830+
testResult = testResult || unitResult;
17831+
#endif
17832+
1770817833
unitResult = test_DoUserAuthRequest_rejectsPasswordChange();
1770917834
printf("DoUserAuthRequest_rejectsPasswordChange: %s\n",
1771017835
(unitResult == 0 ? "SUCCESS" : "FAILED"));

wolfssh/internal.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1858,6 +1858,10 @@ enum WS_MessageIdLimits {
18581858
const byte* src, word32 srcSz);
18591859
WOLFSSH_API int wolfSSH_TestDoUserAuthRequest(WOLFSSH* ssh, byte* buf,
18601860
word32 len, word32* idx);
1861+
#ifdef WOLFSSH_KEYBOARD_INTERACTIVE
1862+
WOLFSSH_API int wolfSSH_TestDoUserAuthInfoResponse(WOLFSSH* ssh, byte* buf,
1863+
word32 len, word32* idx);
1864+
#endif
18611865
WOLFSSH_API int wolfSSH_TestSendUserAuthFailure(WOLFSSH* ssh,
18621866
byte partialSuccess);
18631867
WOLFSSH_API int wolfSSH_TestHighwaterCheck(WOLFSSH* ssh, byte side);

0 commit comments

Comments
 (0)