@@ -17,8 +17,10 @@ namespace srt {
1717 CUDT* core;
1818
1919 void processCtrlDropReq (const CPacket& pkt) { core->processCtrlDropReq (pkt); }
20+ void processCtrlLossReport (const CPacket& pkt) { core->processCtrlLossReport (pkt); }
2021 int32_t rcvCurrSeqNo () const { return core->m_iRcvCurrSeqNo ; }
2122 void setRcvCurrSeqNo (int32_t v) { core->m_iRcvCurrSeqNo = v; }
23+ bool isBroken () const { return core->m_bBroken ; }
2224 };
2325}
2426
@@ -55,3 +57,40 @@ TEST(ControlPackets, DropReqRejectsShortPayload)
5557 pkt.deallocate ();
5658 srt_close (sid1);
5759}
60+
61+ // processCtrlLossReport must reject a LOSSREPORT whose final cell carries
62+ // the LOSSDATA_SEQNO_RANGE_FIRST marker but has no HI cell behind it;
63+ // otherwise losslist[i+1] reads past the wire payload (4-byte OOB read of
64+ // adjacent heap). The handler should mark the connection broken via the
65+ // `secure = false` path.
66+ TEST (ControlPackets, LossReportRejectsTrailingRangeFirst)
67+ {
68+ srt::TestInit srtinit;
69+
70+ CUDTSocket* s = NULL ;
71+ SRTSOCKET sid = CUDT::uglobal ().newSocket (&s);
72+ ASSERT_NE (sid, SRT_INVALID_SOCK);
73+
74+ TestMockControlPackets m;
75+ m.core = &s->core ();
76+ ASSERT_FALSE (m.isBroken ()) << " fresh socket should not be marked broken" ;
77+
78+ // Single 4-byte payload, high bit set => LOSSDATA_SEQNO_RANGE_FIRST.
79+ // Without the guard, the handler would dereference losslist[1] (the
80+ // missing HI cell), reading 4 bytes past the packet payload.
81+ CPacket pkt;
82+ pkt.allocate (64 );
83+ int32_t * data = (int32_t *) pkt.m_pcData ;
84+ data[0 ] = SEQNO_VALUE::wrap (100 ) | LOSSDATA_SEQNO_RANGE_FIRST;
85+ pkt.setLength (sizeof (int32_t ));
86+ pkt.setControl (UMSG_LOSSREPORT);
87+
88+ m.processCtrlLossReport (pkt);
89+
90+ EXPECT_TRUE (m.isBroken ())
91+ << " LOSSREPORT with trailing range-first marker must take the "
92+ " secure=false bail path and mark the connection broken" ;
93+
94+ pkt.deallocate ();
95+ srt_close (sid);
96+ }
0 commit comments