@@ -765,6 +765,162 @@ static void test_pubkey_auth_wrong_key(void)
765765}
766766#endif /* !WOLFSSH_NO_RSA && !WOLFSSH_NO_ECC */
767767
768+ /* -----------------------------------------------------------------------
769+ * Password auth: unknown callback return value must not grant auth (issue 2486)
770+ * ----------------------------------------------------------------------- */
771+
772+ /* Tracks how many times the server password auth callback has been invoked;
773+ * must be reset to 0 before each test run. */
774+ static int invalidPwAttempts = 0 ;
775+
776+ /* Server userAuth callback for test_invalid_cb_password.
777+ * First call returns an out-of-enum value (-999) to exercise the default else
778+ * branch in DoUserAuthRequestPassword. Second call returns REJECTED so the
779+ * connection terminates cleanly and wolfSSH_accept() can return an error. */
780+ static int invalidPasswordServerAuth (byte authType , WS_UserAuthData * authData ,
781+ void * ctx )
782+ {
783+ (void )authData ;
784+ (void )ctx ;
785+ if (authType != WOLFSSH_USERAUTH_PASSWORD )
786+ return WOLFSSH_USERAUTH_FAILURE ;
787+ if (invalidPwAttempts ++ == 0 )
788+ return -999 ; /* unknown value: exercises default else; authFailure=1 */
789+ return WOLFSSH_USERAUTH_REJECTED ; /* clean termination on retry */
790+ }
791+
792+ /* Client userAuth callback for password tests: supplies a dummy password so
793+ * the auth request reaches the server-side callback. */
794+ static int clientPasswordUserAuth (byte authType , WS_UserAuthData * authData ,
795+ void * ctx )
796+ {
797+ static const byte pw [] = "dummypass" ;
798+ (void )ctx ;
799+ if (authType != WOLFSSH_USERAUTH_PASSWORD )
800+ return WOLFSSH_USERAUTH_FAILURE ;
801+ authData -> sf .password .password = pw ;
802+ authData -> sf .password .passwordSz = (word32 )(sizeof (pw ) - 1 );
803+ return WOLFSSH_USERAUTH_SUCCESS ;
804+ }
805+
806+ /* Server thread for test_invalid_cb_password. Mirrors pubkey_server_thread
807+ * but registers invalidPasswordServerAuth and stores the return code cleanly
808+ * (no ES_ERROR abort) so the test can assert on it. */
809+ static THREAD_RETURN WOLFSSH_THREAD password_server_thread (void * args )
810+ {
811+ thread_args * serverArgs = (thread_args * )args ;
812+ int ret = WS_SUCCESS ;
813+ word16 port = 0 ;
814+ WOLFSSH_CTX * ctx = NULL ;
815+ WOLFSSH * ssh = NULL ;
816+ byte buf [EXAMPLE_KEYLOAD_BUFFER_SZ ];
817+ word32 bufSz ;
818+ WS_SOCKET_T listenFd = WOLFSSH_SOCKET_INVALID ;
819+ WS_SOCKET_T clientFd = WOLFSSH_SOCKET_INVALID ;
820+ SOCKADDR_IN_T clientAddr ;
821+ socklen_t clientAddrSz = sizeof (clientAddr );
822+
823+ serverArgs -> return_code = EXIT_SUCCESS ;
824+
825+ tcp_listen (& listenFd , & port , 1 );
826+ SignalTcpReady (serverArgs -> signal , port );
827+
828+ ctx = wolfSSH_CTX_new (WOLFSSH_ENDPOINT_SERVER , NULL );
829+ if (ctx == NULL ) { serverArgs -> return_code = WS_MEMORY_E ; goto cleanup ; }
830+
831+ wolfSSH_SetUserAuth (ctx , invalidPasswordServerAuth );
832+
833+ ssh = wolfSSH_new (ctx );
834+ if (ssh == NULL ) { serverArgs -> return_code = WS_MEMORY_E ; goto cleanup ; }
835+
836+ #ifndef WOLFSSH_NO_ECDSA
837+ bufSz = (word32 )load_key (1 , buf , sizeof (buf ));
838+ #else
839+ bufSz = (word32 )load_key (0 , buf , sizeof (buf ));
840+ #endif
841+ if (bufSz == 0 || wolfSSH_CTX_UsePrivateKey_buffer (ctx , buf , bufSz ,
842+ WOLFSSH_FORMAT_ASN1 ) < 0 ) {
843+ serverArgs -> return_code = WS_BAD_FILE_E ; goto cleanup ;
844+ }
845+
846+ clientFd = accept (listenFd , (struct sockaddr * )& clientAddr , & clientAddrSz );
847+ if (clientFd == WOLFSSH_SOCKET_INVALID ) {
848+ serverArgs -> return_code = WS_SOCKET_ERROR_E ; goto cleanup ;
849+ }
850+ wolfSSH_set_fd (ssh , (int )clientFd );
851+
852+ ret = wolfSSH_accept (ssh );
853+ serverArgs -> return_code = ret ;
854+
855+ cleanup :
856+ if (ssh != NULL && clientFd != WOLFSSH_SOCKET_INVALID )
857+ wolfSSH_shutdown (ssh );
858+ if (ssh != NULL ) wolfSSH_free (ssh );
859+ if (ctx != NULL ) wolfSSH_CTX_free (ctx );
860+ if (clientFd != WOLFSSH_SOCKET_INVALID ) WCLOSESOCKET (clientFd );
861+ if (listenFd != WOLFSSH_SOCKET_INVALID ) WCLOSESOCKET (listenFd );
862+
863+ WOLFSSL_RETURN_FROM_THREAD (0 );
864+ }
865+
866+ /* Test: server password-auth callback returning an unknown value must not
867+ * grant authentication. Flow:
868+ * 1. Client sends password → server callback returns -999 → authFailure=1
869+ * → SendUserAuthFailure (else branch in DoUserAuthRequestPassword hit).
870+ * 2. Client retries → server callback returns WOLFSSH_USERAUTH_REJECTED
871+ * → WS_USER_AUTH_E → server sends disconnect.
872+ * 3. wolfSSH_connect() returns WS_FATAL_ERROR; server return_code != WS_SUCCESS. */
873+ static void test_invalid_cb_password (void )
874+ {
875+ thread_args serverArgs ;
876+ tcp_ready ready ;
877+ THREAD_TYPE serThread ;
878+ WOLFSSH_CTX * clientCtx = NULL ;
879+ WOLFSSH * clientSsh = NULL ;
880+ SOCKET_T sockFd = WOLFSSH_SOCKET_INVALID ;
881+ SOCKADDR_IN_T clientAddr ;
882+ socklen_t clientAddrSz = sizeof (clientAddr );
883+ int ret ;
884+
885+ printf ("Testing password auth with unknown callback return value\n" );
886+ invalidPwAttempts = 0 ;
887+
888+ serverArgs .signal = & ready ;
889+ serverArgs .pubkeyServerCtx = NULL ;
890+ InitTcpReady (serverArgs .signal );
891+
892+ ThreadStart (password_server_thread , (void * )& serverArgs , & serThread );
893+ WaitTcpReady (& ready );
894+
895+ clientCtx = wolfSSH_CTX_new (WOLFSSH_ENDPOINT_CLIENT , NULL );
896+ AssertNotNull (clientCtx );
897+ wolfSSH_CTX_SetPublicKeyCheck (clientCtx , AcceptAnyServerHostKey );
898+ wolfSSH_SetUserAuth (clientCtx , clientPasswordUserAuth );
899+
900+ clientSsh = wolfSSH_new (clientCtx );
901+ AssertNotNull (clientSsh );
902+ wolfSSH_SetUsername (clientSsh , "jill" );
903+
904+ build_addr (& clientAddr , (char * )wolfSshIp , ready .port );
905+ tcp_socket (& sockFd , ((struct sockaddr_in * )& clientAddr )-> sin_family );
906+ AssertIntEQ (connect (sockFd , (const struct sockaddr * )& clientAddr ,
907+ clientAddrSz ), 0 );
908+ wolfSSH_set_fd (clientSsh , (int )sockFd );
909+
910+ ret = wolfSSH_connect (clientSsh );
911+ AssertIntEQ (ret , WS_FATAL_ERROR );
912+
913+ wolfSSH_shutdown (clientSsh );
914+ WCLOSESOCKET (sockFd );
915+ wolfSSH_free (clientSsh );
916+ wolfSSH_CTX_free (clientCtx );
917+
918+ ThreadJoin (serThread );
919+ AssertIntNE (serverArgs .return_code , WS_SUCCESS ); /* auth must NOT be granted */
920+
921+ FreeTcpReady (& ready );
922+ }
923+
768924#endif /* pubkey test guard */
769925
770926#if !defined(NO_WOLFSSH_SERVER ) && !defined(NO_WOLFSSH_CLIENT ) && \
@@ -1153,6 +1309,165 @@ static void test_unbalanced_client_KeyboardInteractive(void)
11531309 test_client ();
11541310 unbalanced = 0 ;
11551311}
1312+
1313+ /* -----------------------------------------------------------------------
1314+ * Keyboard-interactive auth: unknown callback return value must not grant
1315+ * authentication (issue 2486)
1316+ * ----------------------------------------------------------------------- */
1317+
1318+ /* Server userAuth callback for test_invalid_cb_keyboard.
1319+ * KEYBOARD_SETUP is handled normally so the exchange reaches
1320+ * DoUserAuthInfoResponse. For the KEYBOARD (response-validation) step the
1321+ * callback returns -999, an out-of-enum value, to exercise the default else
1322+ * branch in DoUserAuthInfoResponse. */
1323+ static int invalidKbServerAuth (byte authType , WS_UserAuthData * authData ,
1324+ void * ctx )
1325+ {
1326+ WS_UserAuthData_Keyboard * prompts = (WS_UserAuthData_Keyboard * )ctx ;
1327+
1328+ if (authType == WOLFSSH_USERAUTH_KEYBOARD_SETUP ) {
1329+ WMEMCPY (& authData -> sf .keyboard , prompts , sizeof (WS_UserAuthData_Keyboard ));
1330+ return WS_SUCCESS ;
1331+ }
1332+ if (authType == WOLFSSH_USERAUTH_KEYBOARD )
1333+ return -999 ; /* unknown value: exercises default else; authFailure=1 */
1334+ return WOLFSSH_USERAUTH_FAILURE ;
1335+ }
1336+
1337+ /* Server thread for test_invalid_cb_keyboard. Sets up one keyboard prompt and
1338+ * registers invalidKbServerAuth. Stores the return code cleanly (no ES_ERROR
1339+ * abort) so the test can assert on it. */
1340+ static THREAD_RETURN WOLFSSH_THREAD kb_invalid_server_thread (void * args )
1341+ {
1342+ thread_args * serverArgs = (thread_args * )args ;
1343+ int ret = WS_SUCCESS ;
1344+ word16 port = 0 ;
1345+ WOLFSSH_CTX * ctx = NULL ;
1346+ WOLFSSH * ssh = NULL ;
1347+ byte buf [EXAMPLE_KEYLOAD_BUFFER_SZ ];
1348+ word32 bufSz ;
1349+ WS_SOCKET_T listenFd = WOLFSSH_SOCKET_INVALID ;
1350+ WS_SOCKET_T clientFd = WOLFSSH_SOCKET_INVALID ;
1351+ SOCKADDR_IN_T clientAddr ;
1352+ socklen_t clientAddrSz = sizeof (clientAddr );
1353+ WS_UserAuthData_Keyboard localPrompts ;
1354+ byte * kbPrompts [1 ];
1355+ word32 kbPromptLengths [1 ];
1356+ byte kbPromptEcho [1 ];
1357+
1358+ serverArgs -> return_code = EXIT_SUCCESS ;
1359+
1360+ kbPrompts [0 ] = (byte * )"Password: " ;
1361+ kbPromptLengths [0 ] = 10 ;
1362+ kbPromptEcho [0 ] = 0 ;
1363+ WMEMSET (& localPrompts , 0 , sizeof (localPrompts ));
1364+ localPrompts .promptCount = 1 ;
1365+ localPrompts .prompts = kbPrompts ;
1366+ localPrompts .promptLengths = kbPromptLengths ;
1367+ localPrompts .promptEcho = kbPromptEcho ;
1368+
1369+ tcp_listen (& listenFd , & port , 1 );
1370+ SignalTcpReady (serverArgs -> signal , port );
1371+
1372+ ctx = wolfSSH_CTX_new (WOLFSSH_ENDPOINT_SERVER , NULL );
1373+ if (ctx == NULL ) { serverArgs -> return_code = WS_MEMORY_E ; goto cleanup ; }
1374+
1375+ wolfSSH_SetUserAuth (ctx , invalidKbServerAuth );
1376+
1377+ ssh = wolfSSH_new (ctx );
1378+ if (ssh == NULL ) { serverArgs -> return_code = WS_MEMORY_E ; goto cleanup ; }
1379+
1380+ wolfSSH_SetUserAuthCtx (ssh , & localPrompts );
1381+
1382+ bufSz = (word32 )load_key (1 , buf , sizeof (buf ));
1383+ if (bufSz == 0 ) bufSz = (word32 )load_key (0 , buf , sizeof (buf ));
1384+ if (bufSz == 0 || wolfSSH_CTX_UsePrivateKey_buffer (ctx , buf , bufSz ,
1385+ WOLFSSH_FORMAT_ASN1 ) < 0 ) {
1386+ serverArgs -> return_code = WS_BAD_FILE_E ; goto cleanup ;
1387+ }
1388+
1389+ clientFd = accept (listenFd , (struct sockaddr * )& clientAddr , & clientAddrSz );
1390+ if (clientFd == WOLFSSH_SOCKET_INVALID ) {
1391+ serverArgs -> return_code = WS_SOCKET_ERROR_E ; goto cleanup ;
1392+ }
1393+ wolfSSH_set_fd (ssh , (int )clientFd );
1394+
1395+ ret = wolfSSH_accept (ssh );
1396+ serverArgs -> return_code = ret ;
1397+
1398+ cleanup :
1399+ if (ssh != NULL && clientFd != WOLFSSH_SOCKET_INVALID )
1400+ wolfSSH_shutdown (ssh );
1401+ if (ssh != NULL ) wolfSSH_free (ssh );
1402+ if (ctx != NULL ) wolfSSH_CTX_free (ctx );
1403+ if (clientFd != WOLFSSH_SOCKET_INVALID ) WCLOSESOCKET (clientFd );
1404+ if (listenFd != WOLFSSH_SOCKET_INVALID ) WCLOSESOCKET (listenFd );
1405+
1406+ WOLFSSL_RETURN_FROM_THREAD (0 );
1407+ }
1408+
1409+ /* Test: server keyboard-interactive callback returning an unknown value must
1410+ * not grant authentication. Flow:
1411+ * 1. Server provides one prompt; client sends one response.
1412+ * 2. Server callback returns -999 → authFailure=1 → SendUserAuthFailure
1413+ * (else branch in DoUserAuthInfoResponse hit).
1414+ * 3. Client retries keyboard auth; after ssh->kbAuthAttempts reaches 3 the
1415+ * client stops trying keyboard and DoUserAuthFailure returns WS_USER_AUTH_E.
1416+ * 4. wolfSSH_connect() returns WS_FATAL_ERROR; server return_code != WS_SUCCESS. */
1417+ static void test_invalid_cb_keyboard (void )
1418+ {
1419+ thread_args serverArgs ;
1420+ tcp_ready ready ;
1421+ THREAD_TYPE serThread ;
1422+ WOLFSSH_CTX * clientCtx = NULL ;
1423+ WOLFSSH * clientSsh = NULL ;
1424+ SOCKET_T sockFd = WOLFSSH_SOCKET_INVALID ;
1425+ SOCKADDR_IN_T clientAddr ;
1426+ socklen_t clientAddrSz = sizeof (clientAddr );
1427+ int ret ;
1428+
1429+ printf ("Testing keyboard-interactive auth with unknown callback return value\n" );
1430+
1431+ kbResponses [0 ] = (byte * )testText1 ;
1432+ kbResponseLengths [0 ] = 4 ;
1433+ kbResponseCount = 1 ;
1434+
1435+ serverArgs .signal = & ready ;
1436+ serverArgs .pubkeyServerCtx = NULL ;
1437+ InitTcpReady (serverArgs .signal );
1438+
1439+ ThreadStart (kb_invalid_server_thread , (void * )& serverArgs , & serThread );
1440+ WaitTcpReady (& ready );
1441+
1442+ clientCtx = wolfSSH_CTX_new (WOLFSSH_ENDPOINT_CLIENT , NULL );
1443+ AssertNotNull (clientCtx );
1444+ wolfSSH_CTX_SetPublicKeyCheck (clientCtx , AcceptAnyServerHostKey );
1445+ wolfSSH_SetUserAuth (clientCtx , keyboardUserAuth );
1446+
1447+ clientSsh = wolfSSH_new (clientCtx );
1448+ AssertNotNull (clientSsh );
1449+ wolfSSH_SetUsername (clientSsh , "test" );
1450+
1451+ build_addr (& clientAddr , (char * )wolfSshIp , ready .port );
1452+ tcp_socket (& sockFd , ((struct sockaddr_in * )& clientAddr )-> sin_family );
1453+ AssertIntEQ (connect (sockFd , (const struct sockaddr * )& clientAddr ,
1454+ clientAddrSz ), 0 );
1455+ wolfSSH_set_fd (clientSsh , (int )sockFd );
1456+
1457+ ret = wolfSSH_connect (clientSsh );
1458+ AssertIntEQ (ret , WS_FATAL_ERROR );
1459+
1460+ wolfSSH_shutdown (clientSsh );
1461+ WCLOSESOCKET (sockFd );
1462+ wolfSSH_free (clientSsh );
1463+ wolfSSH_CTX_free (clientCtx );
1464+
1465+ ThreadJoin (serThread );
1466+ AssertIntNE (serverArgs .return_code , WS_SUCCESS ); /* auth must NOT be granted */
1467+
1468+ FreeTcpReady (& ready );
1469+ }
1470+
11561471#endif /* WOLFSSH_TEST_BLOCK */
11571472
11581473int wolfSSH_AuthTest (int argc , char * * argv )
@@ -1204,6 +1519,12 @@ int wolfSSH_AuthTest(int argc, char** argv)
12041519 test_unbalanced_client_KeyboardInteractive ();
12051520#endif
12061521
1522+ /* Unknown callback return value must not grant auth (issue 2486) */
1523+ test_invalid_cb_password ();
1524+ #if !defined(NO_FILESYSTEM ) && defined(WOLFSSH_KEYBOARD_INTERACTIVE )
1525+ test_invalid_cb_keyboard ();
1526+ #endif
1527+
12071528 AssertIntEQ (wolfSSH_Cleanup (), WS_SUCCESS );
12081529
12091530 return 0 ;
0 commit comments