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