@@ -918,6 +918,136 @@ static int test_DoChannelRequest(void)
918918 return result ;
919919}
920920
921+ /* Capture buffer for the service-name unit test. Separate from the channel-
922+ * request capture so the two tests can run independently in any order. */
923+ static byte s_authSvcCapture [256 ];
924+ static word32 s_authSvcCaptureSz = 0 ;
925+
926+ static int CaptureIoSendAuthSvc (WOLFSSH * ssh , void * buf , word32 sz , void * ctx )
927+ {
928+ (void )ssh ; (void )ctx ;
929+ s_authSvcCaptureSz = (sz < (word32 )sizeof (s_authSvcCapture ))
930+ ? sz : (word32 )sizeof (s_authSvcCapture );
931+ WMEMCPY (s_authSvcCapture , buf , s_authSvcCaptureSz );
932+ return (int )sz ;
933+ }
934+
935+ /* Verify DoUserAuthRequest rejects non-"ssh-connection" service names per
936+ * RFC 4252 Section 5. For each case we assert:
937+ * 1. ret == WS_SUCCESS (connection stays open for retry)
938+ * 2. SSH_MSG_USERAUTH_FAILURE is actually sent (captured at packet byte 5:
939+ * [4-byte packet_length][1-byte padding_length][1-byte msg_id]...)
940+ * 3. *idx == len (entire payload consumed; buffer stays aligned)
941+ *
942+ * For invalid-service cases the auth-method field is intentionally omitted
943+ * from the payload. DoUserAuthRequest must short-circuit at the service-name
944+ * check and still satisfy all three assertions — proving it never tries to
945+ * parse the missing auth-method field. If the short-circuit were absent,
946+ * GetSize() for authNameSz would hit end-of-buffer and return WS_BUFFER_E,
947+ * failing assertion 1.
948+ *
949+ * For the valid-service case, auth method "xyz-unknown" (always unsupported
950+ * regardless of compile-time options) is included. The function reaches
951+ * auth-method dispatch, falls to the unknown-method else-branch, and sends
952+ * USERAUTH_FAILURE via that normal path. */
953+ static int test_DoUserAuthRequest_serviceName (void )
954+ {
955+ WOLFSSH_CTX * ctx = NULL ;
956+ WOLFSSH * ssh = NULL ;
957+ int result = 0 ;
958+ struct {
959+ const char * svcName ;
960+ word32 svcNameSz ;
961+ const char * authMethod ; /* NULL = omit field (proves short-circuit) */
962+ word32 authMethodSz ;
963+ int expectRet ;
964+ const char * label ;
965+ } cases [] = {
966+ /* valid service: auth dispatch fires, fails on unknown method */
967+ { "ssh-connection" , 14 , "xyz-unknown" , 11 , WS_SUCCESS ,
968+ "valid svc unknown auth" },
969+ /* invalid service: short-circuit, auth-method field absent */
970+ { "ssh-agent" , 9 , NULL , 0 , WS_SUCCESS ,
971+ "invalid ssh-agent svc" },
972+ { "bad" , 3 , NULL , 0 , WS_SUCCESS ,
973+ "invalid bad svc" },
974+ /* zero-length service name: NameToId("",0)==ID_UNKNOWN, must reject */
975+ { "" , 0 , NULL , 0 , WS_SUCCESS ,
976+ "zero-length svc" },
977+ /* ssh-userauth: NameToId returns ID_SERVICE_USERAUTH, not
978+ * ID_SERVICE_CONNECTION, so must also be rejected */
979+ { "ssh-userauth" , 12 , NULL , 0 , WS_SUCCESS ,
980+ "invalid ssh-userauth svc" },
981+ };
982+ int i ;
983+
984+ ctx = wolfSSH_CTX_new (WOLFSSH_ENDPOINT_SERVER , NULL );
985+ if (ctx == NULL ) return -500 ;
986+ wolfSSH_SetIOSend (ctx , CaptureIoSendAuthSvc );
987+
988+ ssh = wolfSSH_new (ctx );
989+ if (ssh == NULL ) { wolfSSH_CTX_free (ctx ); return -501 ; }
990+
991+ for (i = 0 ; i < (int )(sizeof (cases )/sizeof (cases [0 ])); i ++ ) {
992+ byte buf [128 ];
993+ word32 len = 0 , idx = 0 ;
994+ word32 snsz = cases [i ].svcNameSz ;
995+ int ret ;
996+
997+ s_authSvcCaptureSz = 0 ;
998+ WMEMSET (s_authSvcCapture , 0 , sizeof (s_authSvcCapture ));
999+
1000+ /* username: "user" */
1001+ buf [len ++ ] = 0 ; buf [len ++ ] = 0 ; buf [len ++ ] = 0 ; buf [len ++ ] = 4 ;
1002+ WMEMCPY (buf + len , "user" , 4 ); len += 4 ;
1003+
1004+ /* service name */
1005+ buf [len ++ ] = (byte )(snsz >> 24 ); buf [len ++ ] = (byte )(snsz >> 16 );
1006+ buf [len ++ ] = (byte )(snsz >> 8 ); buf [len ++ ] = (byte )snsz ;
1007+ WMEMCPY (buf + len , cases [i ].svcName , snsz ); len += snsz ;
1008+
1009+ /* auth method: omit for invalid-service cases to prove short-circuit */
1010+ if (cases [i ].authMethod != NULL ) {
1011+ word32 amsz = cases [i ].authMethodSz ;
1012+ buf [len ++ ] = (byte )(amsz >> 24 ); buf [len ++ ] = (byte )(amsz >> 16 );
1013+ buf [len ++ ] = (byte )(amsz >> 8 ); buf [len ++ ] = (byte )amsz ;
1014+ WMEMCPY (buf + len , cases [i ].authMethod , amsz ); len += amsz ;
1015+ }
1016+
1017+ ret = wolfSSH_TestDoUserAuthRequest (ssh , buf , len , & idx );
1018+
1019+ if (ret != cases [i ].expectRet ) {
1020+ printf ("DoUserAuthRequest_svcName[%s]: ret=%d expected=%d\n" ,
1021+ cases [i ].label , ret , cases [i ].expectRet );
1022+ result = -502 - i ;
1023+ break ;
1024+ }
1025+
1026+ /* MSGID_USERAUTH_FAILURE must be in the captured packet. */
1027+ if (s_authSvcCaptureSz <= 5 ||
1028+ s_authSvcCapture [5 ] != MSGID_USERAUTH_FAILURE ) {
1029+ printf ("DoUserAuthRequest_svcName[%s]: USERAUTH_FAILURE not sent "
1030+ "(capSz=%u byte5=0x%02x)\n" , cases [i ].label ,
1031+ s_authSvcCaptureSz ,
1032+ s_authSvcCaptureSz > 5 ? s_authSvcCapture [5 ] : 0 );
1033+ result = -520 - i ;
1034+ break ;
1035+ }
1036+
1037+ /* All cases must consume the entire payload. */
1038+ if (idx != len ) {
1039+ printf ("DoUserAuthRequest_svcName[%s]: idx=%u expected len=%u\n" ,
1040+ cases [i ].label , idx , len );
1041+ result = -510 - i ;
1042+ break ;
1043+ }
1044+ }
1045+
1046+ wolfSSH_free (ssh );
1047+ wolfSSH_CTX_free (ctx );
1048+ return result ;
1049+ }
1050+
9211051#if !defined(WOLFSSH_NO_RSA )
9221052
9231053/* 2048-bit RSA private key (PKCS#1 DER).
@@ -1210,6 +1340,11 @@ int wolfSSH_UnitTest(int argc, char** argv)
12101340 unitResult = test_ChannelPutData ();
12111341 printf ("ChannelPutData: %s\n" , (unitResult == 0 ? "SUCCESS" : "FAILED" ));
12121342 testResult = testResult || unitResult ;
1343+
1344+ unitResult = test_DoUserAuthRequest_serviceName ();
1345+ printf ("DoUserAuthRequest_serviceName: %s\n" ,
1346+ (unitResult == 0 ? "SUCCESS" : "FAILED" ));
1347+ testResult = testResult || unitResult ;
12131348#endif
12141349
12151350#ifdef WOLFSSH_KEYGEN
0 commit comments