keyboard-interactive: send USERAUTH_FAILURE on response-count mismatch#1070
keyboard-interactive: send USERAUTH_FAILURE on response-count mismatch#1070yosuke-wolfssl wants to merge 1 commit into
Conversation
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1070
Scan targets checked: wolfssh-bugs, wolfssh-src
No new issues found in the changed files. ✅
aidangarske
left a comment
There was a problem hiding this comment.
🐺 Skoll Code Review
Overall recommendation: COMMENT
Findings: 1 total — 1 posted, 0 skipped
Posted findings
- [Medium] Malformed keyboard response failure leaves challenge state active —
src/internal.c:7278-7385
Review generated by Skoll.
| if ((ret == WS_SUCCESS) && | ||
| (ssh->kbAuth.promptCount != kb->responseCount)) { | ||
| WLOG(WS_LOG_DEBUG, "DUARKB: Invalid number of responses received"); | ||
| /* Respond with USERAUTH_FAILURE rather than |
There was a problem hiding this comment.
🟡 [Medium] Malformed keyboard response failure leaves challenge state active
💡 SUGGEST bug
The new count-mismatch and too-many branches now turn a malformed INFO_RESPONSE into a successful SendUserAuthFailure() path, but the pending keyboard-interactive state is left intact. ssh->authId remains ID_USERAUTH_KEYBOARD and ssh->kbAuth.promptCount remains the old challenge count, so after receiving USERAUTH_FAILURE a malformed client can send another MSGID_USERAUTH_INFO_RESPONSE without first sending a new USERAUTH_REQUEST. Before this PR these malformed count cases tore down the connection, so this retry-without-new-request path is introduced by the change. That is at least protocol-state drift from the intended retry flow described in the PR.
Recommendation: After sending USERAUTH_FAILURE for a rejected keyboard-interactive INFO_RESPONSE, clear the pending keyboard challenge state or add an explicit pending-challenge flag so a later INFO_RESPONSE is rejected unless a fresh USERAUTH_REQUEST caused a new INFO_REQUEST. Add a regression test that sends a malformed INFO_RESPONSE, observes USERAUTH_FAILURE, then sends a second INFO_RESPONSE without a new USERAUTH_REQUEST and verifies it is not accepted as an answer to the old challenge.
Description
DoUserAuthInfoResponse()tore down the transport instead of sending anSSH_MSG_USERAUTH_FAILUREwhen a keyboard-interactiveINFO_RESPONSEcarried aresponseCountthat did not match the server's pendingpromptCount, or a countgreater than
WOLFSSH_MAX_PROMPTS.Both guards set
ret = WS_USER_AUTH_E. The failure-response gate only callsSendUserAuthFailure()whenauthFailure || partialSuccessis set, so no SSHmessage was emitted;
DoReceive()convertsWS_USER_AUTH_EtoWS_FATAL_ERRORand the connection is torn down. RFC 4256 requires the server to answer every
INFO_RESPONSEwithSUCCESS,FAILURE, or anotherINFO_REQUEST. Impact islimited to malformed/malicious clients, but the response is non-conformant.
Addressed by f_6514.
Fix
In both guards, set
authFailureso the existing gate sendsSSH_MSG_USERAUTH_FAILUREand the connection survives (the client may retry).retis kept non-success in these branches so the response allocation, parse,and user-auth callback are skipped:
responseCount(up to2^32-1) would otherwise drive alarge allocation, and
The failure path also reports the whole payload consumed (
*idx = len),consistent with the success path's contract.
Testing
Added
tests/regress.ccoverage using the in-memory packet harness, which feedsa crafted
INFO_RESPONSEstraight intoDoReceive()and inspects the bytes theserver writes back:
TestKbInfoResponseCountMismatchSendsFailure—responseCount(2) disagreeswith
promptCount(1).TestKbInfoResponseTooManySendsFailure—responseCount > WOLFSSH_MAX_PROMPTS.Both assert
DoReceive()returnsWS_SUCCESSand the reply isMSGID_USERAUTH_FAILURE(via the sharedAssertKbInfoResponseSentFailurehelper).
authFailurechange makes the new testsabort at the
WS_SUCCESSassertion withret = -1001(WS_FATAL_ERROR),confirming they catch the regression.
-fsanitize=address,undefined(leak detection offon macOS);
regress.testis clean.Notes
*idx = lenline is a contract-consistency safeguard. In the currentreceive path it is not behaviorally observable (
DoReceiveforce-shrinks theinput buffer after every packet), but it keeps the handler consistent with its
siblings and robust to future buffer-management changes.