Skip to content

Commit 033d1d5

Browse files
[BUG] Fixed OOB read in LOSSREPORT range parsing (#3324)
1 parent d7957ac commit 033d1d5

2 files changed

Lines changed: 47 additions & 0 deletions

File tree

srtcore/core.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9070,6 +9070,14 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt)
90709070
// IF the loss is a range <LO, HI>
90719071
if (IsSet(losslist[i], LOSSDATA_SEQNO_RANGE_FIRST))
90729072
{
9073+
// The range-first marker means the next cell holds HI. Reject if it isn't present
9074+
// in the body — otherwise losslist[i+1] reads past the wire payload.
9075+
if (i + 1 >= n)
9076+
{
9077+
LOGC(inlog.Warn, log << CONID() << "rcv LOSSREPORT: trailing range-first word with no HI - DISCARDING");
9078+
secure = false;
9079+
break;
9080+
}
90739081
// Then it's this is a <LO, HI> specification with HI in a consecutive cell.
90749082
const int32_t losslist_lo = SEQNO_VALUE::unwrap(losslist[i]);
90759083
const int32_t losslist_hi = losslist[i + 1];

test/test_control_packets.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)