@@ -1181,13 +1181,25 @@ static int test_ChannelPutData(void)
11811181 return result ;
11821182}
11831183
1184+ /* Plaintext SSH packet from IoSend (before encryption/MAC): LENGTH_SZ,
1185+ * PAD_LENGTH_SZ, then payload starting with the message ID (RFC 4253;
1186+ * wolfSSH PreparePacket/BundlePacket). Not for encrypted payloads or
1187+ * arbitrary truncated chunks. */
1188+ static int CaptureMsgId (const byte * buf , word32 len )
1189+ {
1190+ word32 off = LENGTH_SZ + PAD_LENGTH_SZ ;
1191+
1192+ if (len <= off )
1193+ return -1 ;
1194+ return (int )buf [off ];
1195+ }
1196+
11841197/* Verify DoChannelRequest sends CHANNEL_SUCCESS for known types and
11851198 * CHANNEL_FAILURE for unrecognized ones (RFC 4254 Section 5.4).
11861199 *
11871200 * A custom IoSend callback captures the outgoing packet in plaintext
1188- * (no cipher negotiated on a fresh session). The SSH packet layout is:
1189- * [4-byte packet_length][1-byte padding_length][1-byte msg_id]...
1190- * so the message ID lives at byte offset 5. */
1201+ * (no cipher negotiated on a fresh session). Message ID is read via
1202+ * CaptureMsgId() using LENGTH_SZ + PAD_LENGTH_SZ. */
11911203static byte s_chanReqCapture [256 ];
11921204static word32 s_chanReqCaptureSz = 0 ;
11931205
@@ -1289,20 +1301,204 @@ static int test_DoChannelRequest(void)
12891301 goto done ;
12901302 }
12911303
1292- if (s_chanReqCaptureSz <= 5 ) {
1293- printf ("DoChannelRequest[%s]: captured packet too short (%u)\n" ,
1294- cases [i ].label , s_chanReqCaptureSz );
1295- result = -410 - i ;
1304+ {
1305+ int capMsgId = CaptureMsgId (s_chanReqCapture , s_chanReqCaptureSz );
1306+
1307+ if (capMsgId < 0 ) {
1308+ printf ("DoChannelRequest[%s]: captured packet too short (%u)\n" ,
1309+ cases [i ].label , s_chanReqCaptureSz );
1310+ result = -410 - i ;
1311+ goto done ;
1312+ }
1313+
1314+ if (capMsgId != (int )cases [i ].expectMsgId ) {
1315+ printf ("DoChannelRequest[%s]: msg_id=0x%02x, expected=0x%02x\n" ,
1316+ cases [i ].label ,
1317+ capMsgId , cases [i ].expectMsgId );
1318+ result = -420 - i ;
1319+ goto done ;
1320+ }
1321+ }
1322+ }
1323+
1324+ done :
1325+ wolfSSH_free (ssh );
1326+ wolfSSH_CTX_free (ctx );
1327+ return result ;
1328+ }
1329+
1330+ /* Capture buffer for the service-name unit test. Separate from the channel-
1331+ * request capture so the two tests can run independently in any order. */
1332+ static byte s_authSvcCapture [256 ];
1333+ static word32 s_authSvcCaptureSz = 0 ;
1334+ static word32 s_authSvcSendCount = 0 ;
1335+
1336+ static int CaptureIoSendAuthSvc (WOLFSSH * ssh , void * buf , word32 sz , void * ctx )
1337+ {
1338+ (void )ssh ; (void )ctx ;
1339+ s_authSvcCaptureSz = (sz < (word32 )sizeof (s_authSvcCapture ))
1340+ ? sz : (word32 )sizeof (s_authSvcCapture );
1341+ WMEMCPY (s_authSvcCapture , buf , s_authSvcCaptureSz );
1342+ s_authSvcSendCount ++ ;
1343+ return (int )sz ;
1344+ }
1345+
1346+ /* Verify DoUserAuthRequest rejects non-"ssh-connection" service names per
1347+ * RFC 4252 Section 5. For each case we assert:
1348+ * 1. ret == WS_SUCCESS (connection stays open for retry)
1349+ * 2. SSH_MSG_USERAUTH_FAILURE is actually sent (see CaptureMsgId():
1350+ * LENGTH_SZ + PAD_LENGTH_SZ then msg id)
1351+ * 3. *idx == len (entire payload consumed; buffer stays aligned)
1352+ *
1353+ * For invalid-service cases the auth-method field is intentionally omitted
1354+ * from the payload. DoUserAuthRequest must short-circuit at the service-name
1355+ * check and still satisfy all three assertions — proving it never tries to
1356+ * parse the missing auth-method field. If the short-circuit were absent,
1357+ * GetSize() for authNameSz would hit end-of-buffer and return WS_BUFFER_E,
1358+ * failing assertion 1.
1359+ *
1360+ * For the valid-service case, auth method "xyz-unknown" (always unsupported
1361+ * regardless of compile-time options) is included. The function reaches
1362+ * auth-method dispatch, falls to the unknown-method else-branch, and sends
1363+ * USERAUTH_FAILURE via that normal path.
1364+ *
1365+ * A second valid-service row appends fake password-style bytes after the
1366+ * method name. That proves DoUserAuthRequest() consumes trailing
1367+ * method-specific payload (begin = len in the unknown-method branch); without
1368+ * it, DoReceive() could advance inputBuffer.idx short of the packet end and
1369+ * misalign decoding. */
1370+ static const byte s_unknownAuthTrailingFakePassword [] = {
1371+ 0x00 , /* "change password" FALSE */
1372+ 0x00 , 0x00 , 0x00 , 0x08 ,
1373+ 'p' , 'a' , 's' , 's' , 'w' , 'o' , 'r' , 'd' ,
1374+ };
1375+
1376+ static int test_DoUserAuthRequest_serviceName (void )
1377+ {
1378+ WOLFSSH_CTX * ctx = NULL ;
1379+ WOLFSSH * ssh = NULL ;
1380+ int result = 0 ;
1381+ struct {
1382+ const char * svcName ;
1383+ word32 svcNameSz ;
1384+ const char * authMethod ; /* NULL = omit field (proves short-circuit) */
1385+ word32 authMethodSz ;
1386+ int expectRet ;
1387+ const char * label ;
1388+ const byte * authTrailing ; /* bytes after auth method; NULL if none */
1389+ word32 authTrailingSz ;
1390+ } cases [] = {
1391+ /* valid service: auth dispatch fires, fails on unknown method */
1392+ { "ssh-connection" , 14 , "xyz-unknown" , 11 , WS_SUCCESS ,
1393+ "valid svc unknown auth" , NULL , 0 },
1394+ /* same but trailing junk must be skipped so *idx reaches len */
1395+ { "ssh-connection" , 14 , "xyz-unknown" , 11 , WS_SUCCESS ,
1396+ "valid svc unknown auth trailing junk" ,
1397+ s_unknownAuthTrailingFakePassword ,
1398+ (word32 )sizeof (s_unknownAuthTrailingFakePassword ) },
1399+ /* invalid service: short-circuit, auth-method field absent */
1400+ { "ssh-agent" , 9 , NULL , 0 , WS_SUCCESS ,
1401+ "invalid ssh-agent svc" , NULL , 0 },
1402+ { "bad" , 3 , NULL , 0 , WS_SUCCESS ,
1403+ "invalid bad svc" , NULL , 0 },
1404+ /* zero-length service name: NameToId("",0)==ID_UNKNOWN, must reject */
1405+ { "" , 0 , NULL , 0 , WS_SUCCESS ,
1406+ "zero-length svc" , NULL , 0 },
1407+ /* ssh-userauth: NameToId returns ID_SERVICE_USERAUTH, not
1408+ * ID_SERVICE_CONNECTION, so must also be rejected */
1409+ { "ssh-userauth" , 12 , NULL , 0 , WS_SUCCESS ,
1410+ "invalid ssh-userauth svc" , NULL , 0 },
1411+ };
1412+ int i ;
1413+
1414+ ctx = wolfSSH_CTX_new (WOLFSSH_ENDPOINT_SERVER , NULL );
1415+ if (ctx == NULL ) return -500 ;
1416+ wolfSSH_SetIOSend (ctx , CaptureIoSendAuthSvc );
1417+
1418+ for (i = 0 ; i < (int )(sizeof (cases )/sizeof (cases [0 ])); i ++ ) {
1419+ byte buf [128 ];
1420+ word32 len = 0 , idx = 0 ;
1421+ word32 snsz = cases [i ].svcNameSz ;
1422+ int ret ;
1423+
1424+ ssh = wolfSSH_new (ctx );
1425+ if (ssh == NULL ) { result = -501 ; goto done ; }
1426+
1427+ s_authSvcCaptureSz = 0 ;
1428+ s_authSvcSendCount = 0 ;
1429+ WMEMSET (s_authSvcCapture , 0 , sizeof (s_authSvcCapture ));
1430+
1431+ /* username: "user" */
1432+ buf [len ++ ] = 0 ; buf [len ++ ] = 0 ; buf [len ++ ] = 0 ; buf [len ++ ] = 4 ;
1433+ WMEMCPY (buf + len , "user" , 4 ); len += 4 ;
1434+
1435+ /* service name */
1436+ buf [len ++ ] = (byte )(snsz >> 24 ); buf [len ++ ] = (byte )(snsz >> 16 );
1437+ buf [len ++ ] = (byte )(snsz >> 8 ); buf [len ++ ] = (byte )snsz ;
1438+ if (snsz > 0 ) { WMEMCPY (buf + len , cases [i ].svcName , snsz ); }
1439+ len += snsz ;
1440+
1441+ /* auth method: omit for invalid-service cases to prove short-circuit */
1442+ if (cases [i ].authMethod != NULL ) {
1443+ word32 amsz = cases [i ].authMethodSz ;
1444+ buf [len ++ ] = (byte )(amsz >> 24 ); buf [len ++ ] = (byte )(amsz >> 16 );
1445+ buf [len ++ ] = (byte )(amsz >> 8 ); buf [len ++ ] = (byte )amsz ;
1446+ WMEMCPY (buf + len , cases [i ].authMethod , amsz ); len += amsz ;
1447+ if (cases [i ].authTrailingSz > 0U ) {
1448+ WMEMCPY (buf + len , cases [i ].authTrailing ,
1449+ cases [i ].authTrailingSz );
1450+ len += cases [i ].authTrailingSz ;
1451+ }
1452+ }
1453+
1454+ ret = wolfSSH_TestDoUserAuthRequest (ssh , buf , len , & idx );
1455+
1456+ if (s_authSvcSendCount != 1 ) {
1457+ printf ("DoUserAuthRequest_svcName[%s]: expected 1 send, got %u\n" ,
1458+ cases [i ].label , s_authSvcSendCount );
1459+ result = -540 - i ;
1460+ goto done ;
1461+ }
1462+
1463+ if (ret != cases [i ].expectRet ) {
1464+ printf ("DoUserAuthRequest_svcName[%s]: ret=%d expected=%d\n" ,
1465+ cases [i ].label , ret , cases [i ].expectRet );
1466+ result = -502 - i ;
1467+ goto done ;
1468+ }
1469+
1470+ /* MSGID_USERAUTH_FAILURE must be in the captured packet. */
1471+ {
1472+ int capMsgId = CaptureMsgId (s_authSvcCapture , s_authSvcCaptureSz );
1473+
1474+ if (capMsgId < 0 || capMsgId != MSGID_USERAUTH_FAILURE ) {
1475+ printf ("DoUserAuthRequest_svcName[%s]: USERAUTH_FAILURE not "
1476+ "sent (capSz=%u msg_id=0x%02x)\n" , cases [i ].label ,
1477+ s_authSvcCaptureSz ,
1478+ capMsgId >= 0 ? capMsgId : 0 );
1479+ result = -520 - i ;
1480+ goto done ;
1481+ }
1482+ }
1483+
1484+ /* All cases must consume the entire payload. */
1485+ if (idx != len ) {
1486+ printf ("DoUserAuthRequest_svcName[%s]: idx=%u expected len=%u\n" ,
1487+ cases [i ].label , idx , len );
1488+ result = -510 - i ;
12961489 goto done ;
12971490 }
12981491
1299- if ( s_chanReqCapture [ 5 ] != cases [ i ]. expectMsgId ) {
1300- printf ( "DoChannelRequest[%s]: msg_id=0x%02x, expected=0x%02x\n" ,
1301- cases [ i ]. label ,
1302- s_chanReqCapture [ 5 ] , cases [i ].expectMsgId );
1303- result = -420 - i ;
1492+ /* Invalid-service cases must NOT record the username. */
1493+ if ( cases [ i ]. authMethod == NULL && ssh -> userName != NULL ) {
1494+ printf ( "DoUserAuthRequest_svcName[%s]: userName set on invalid "
1495+ "service (expected NULL)\n" , cases [i ].label );
1496+ result = -530 - i ;
13041497 goto done ;
13051498 }
1499+
1500+ wolfSSH_free (ssh );
1501+ ssh = NULL ;
13061502 }
13071503
13081504done :
@@ -1609,6 +1805,11 @@ int wolfSSH_UnitTest(int argc, char** argv)
16091805 unitResult = test_ChannelPutData ();
16101806 printf ("ChannelPutData: %s\n" , (unitResult == 0 ? "SUCCESS" : "FAILED" ));
16111807 testResult = testResult || unitResult ;
1808+
1809+ unitResult = test_DoUserAuthRequest_serviceName ();
1810+ printf ("DoUserAuthRequest_serviceName: %s\n" ,
1811+ (unitResult == 0 ? "SUCCESS" : "FAILED" ));
1812+ testResult = testResult || unitResult ;
16121813#endif
16131814
16141815#ifdef WOLFSSH_KEYGEN
0 commit comments