Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a1253a1
rocDecode/Parser: Stop the start code scan underflowing on a short pa…
jeffqjiangNew Oct 1, 2026
631519b
rocDecode/Parser: Make the NAL unit offsets and sizes consistently un…
jeffqjiangNew Oct 1, 2026
68fe033
rocDecode/Parser: Bound the SEI message parse to the NAL unit.
jeffqjiangNew Oct 1, 2026
2615b7d
rocDecode/Parser: Report an invalid emulation prevention sequence ins…
jeffqjiangNew Oct 1, 2026
c42f68b
rocDecode/Parser: Subtract the removed emulation prevention bytes fro…
jeffqjiangNew Oct 2, 2026
d37d6ba
rocDecode/Parser: Update the EbspToRbsp() documentation for the new s…
jeffqjiangNew Oct 2, 2026
36946de
rocDecode/Parser: Grow the SEI payload buffer geometrically.
jeffqjiangNew Oct 2, 2026
c96255e
rocDecode/Parser: Guard the short packet case at the top of GetNalUni…
jeffqjiangNew Oct 2, 2026
759003e
Merge branch 'develop' into users/jeffqjiangNew/rocDecode_parser_erro…
jeffqjiangNew Oct 2, 2026
e0ce9c8
rocDecode/Parser: Derive the NAL payload size once, under its floor.
jeffqjiangNew Oct 6, 2026
d0b26e0
Merge branch 'users/jeffqjiangNew/rocDecode_parser_error_handle' of g…
jeffqjiangNew Oct 6, 2026
296539b
rocDecode/Parser: Log a failed SEI message parse at the call sites.
jeffqjiangNew Oct 6, 2026
8c366a0
rocDecode/Parser: Report malformed frame data as PARSER_INVALID_FORMAT.
jeffqjiangNew Oct 6, 2026
85c2565
rocDecode/Parser: Record the parser hardening in the changelog, and t…
jeffqjiangNew Oct 6, 2026
844e974
rocDecode/Doc: Say what the parser bounds fixes were, in the changelog.
jeffqjiangNew Oct 6, 2026
3d0a553
* rocDecode/Doc: Updated change log based on review comment.
jeffqjiangNew Oct 7, 2026
1b878a5
Merge branch 'develop' into users/jeffqjiangNew/rocDecode_parser_erro…
jeffqjiangNew Oct 8, 2026
076e6bd
Update projects/rocdecode/CHANGELOG.md
jeffqjiangNew Oct 8, 2026
da5b49d
rocDecode/Parser: Fail rather than truncate an oversized SEI payload …
jeffqjiangNew Oct 8, 2026
48f3b24
rocDecode/Parser: Spell out the SEI message field conversions.
jeffqjiangNew Oct 8, 2026
2f7557c
rocDecode/Parser: Make the slice and SEI message counters unsigned.
jeffqjiangNew Oct 8, 2026
10e0994
rocDecode/Parser: Parenthesize INIT_SEI_PAYLOAD_BUF_SIZE and make it …
jeffqjiangNew Oct 8, 2026
970e35c
rocDecode/Parser: Widen pic_stream_data_size_ to match what feeds it.
jeffqjiangNew Oct 8, 2026
fd32ab8
rocDecode/Parser: Say what the invalid format return gives up, not ju…
jeffqjiangNew Oct 8, 2026
fc5d236
rocDecode/Parser: Tidy the EbspToRbsp() signature and complete its co…
jeffqjiangNew Oct 8, 2026
764f7dd
Merge branch 'develop' into users/jeffqjiangNew/rocDecode_parser_erro…
jeffqjiangNew Oct 8, 2026
8df5810
rocDecode/Parser: Skip SEI messages whose type the public field canno…
jeffqjiangNew Oct 8, 2026
a5eec1b
Merge branch 'users/jeffqjiangNew/rocDecode_parser_error_handle' of g…
jeffqjiangNew Oct 8, 2026
fe19a15
rocDecode/Parser: Stop the SEI capacity doubling overshooting the 32 …
jeffqjiangNew Oct 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions projects/rocdecode/src/parser/avc_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,16 @@ ParserResult AvcVideoParser::ParsePictureData(const uint8_t *p_stream, uint32_t
// Parse the NAL unit
if (nal_unit_size_ >= 4) {
// start code + NAL unit header = 4 bytes
int ebsp_size = nal_unit_size_ - 4 > RBSP_BUF_SIZE ? RBSP_BUF_SIZE : nal_unit_size_ - 4; // only copy enough bytes for header parsing
uint32_t ebsp_size = nal_unit_size_ - 4 > RBSP_BUF_SIZE ? RBSP_BUF_SIZE : nal_unit_size_ - 4; // only copy enough bytes for header parsing

nal_unit_header_ = ParseNalUnitHeader(pic_data_buffer_ptr_[curr_start_code_offset_ + 3]);
switch (nal_unit_header_.nal_unit_type) {
case kAvcNalTypeSeq_Parameter_Set: {
memcpy(rbsp_buf_, (pic_data_buffer_ptr_ + curr_start_code_offset_ + 4), ebsp_size);
rbsp_size_ = EbspToRbsp(rbsp_buf_, 0, ebsp_size);
if (EbspToRbsp(rbsp_buf_, 0, ebsp_size, &rbsp_size_) != PARSER_OK) {
ErrorLog(g_rocdec_logger, "This NAL unit is skipped.");
break;
}
if ((ret2 = ParseSps(rbsp_buf_, rbsp_size_)) != PARSER_OK) {
ErrorLog(g_rocdec_logger, "Error occurred in SPS parsing. This SPS NAL unit is skipped.");
}
Expand All @@ -140,7 +143,10 @@ ParserResult AvcVideoParser::ParsePictureData(const uint8_t *p_stream, uint32_t

case kAvcNalTypePic_Parameter_Set: {
memcpy(rbsp_buf_, (pic_data_buffer_ptr_ + curr_start_code_offset_ + 4), ebsp_size);
rbsp_size_ = EbspToRbsp(rbsp_buf_, 0, ebsp_size);
if (EbspToRbsp(rbsp_buf_, 0, ebsp_size, &rbsp_size_) != PARSER_OK) {
ErrorLog(g_rocdec_logger, "This NAL unit is skipped.");
break;
}
if ((ret2 = ParsePps(rbsp_buf_, rbsp_size_)) != PARSER_OK) {
ErrorLog(g_rocdec_logger, "Error occurred in PPS parsing. This PPS NAL unit is skipped.");
}
Expand All @@ -155,7 +161,10 @@ ParserResult AvcVideoParser::ParsePictureData(const uint8_t *p_stream, uint32_t
// Parse the slice header into a temporary first, so we can decide whether this
// slice begins a new primary coded picture before committing it.
memcpy(rbsp_buf_, (pic_data_buffer_ptr_ + curr_start_code_offset_ + 4), ebsp_size);
rbsp_size_ = EbspToRbsp(rbsp_buf_, 0, ebsp_size);
if (EbspToRbsp(rbsp_buf_, 0, ebsp_size, &rbsp_size_) != PARSER_OK) {
ErrorLog(g_rocdec_logger, "This NAL unit is skipped.");
break;
}
AvcSliceHeader curr_slice_header;
if ((ret2 = ParseSliceHeader(rbsp_buf_, rbsp_size_, &curr_slice_header)) != PARSER_OK) {
ErrorLog(g_rocdec_logger, "Error occurred in slice header parsing. This slice NAL unit is skipped.");
Expand Down Expand Up @@ -252,7 +261,7 @@ ParserResult AvcVideoParser::ParsePictureData(const uint8_t *p_stream, uint32_t

case kAvcNalTypeSEI_Info: {
if (pfn_get_sei_message_cb_) {
int sei_ebsp_size = nal_unit_size_ - 4; // copy the entire NAL unit
uint32_t sei_ebsp_size = nal_unit_size_ - 4; // copy the entire NAL unit
Comment thread
jeffqjiangNew marked this conversation as resolved.
Resolved
if (sei_rbsp_buf_) {
if (sei_ebsp_size > sei_rbsp_buf_size_) {
delete [] sei_rbsp_buf_;
Expand All @@ -264,7 +273,10 @@ ParserResult AvcVideoParser::ParsePictureData(const uint8_t *p_stream, uint32_t
sei_rbsp_buf_ = new uint8_t [sei_rbsp_buf_size_];
}
memcpy(sei_rbsp_buf_, (pic_data_buffer_ptr_ + curr_start_code_offset_ + 4), sei_ebsp_size);
rbsp_size_ = EbspToRbsp(sei_rbsp_buf_, 0, sei_ebsp_size);
if (EbspToRbsp(sei_rbsp_buf_, 0, sei_ebsp_size, &rbsp_size_) != PARSER_OK) {
ErrorLog(g_rocdec_logger, "This NAL unit is skipped.");
break;
}
ParseSeiMessage(sei_rbsp_buf_, rbsp_size_);
Comment thread
jeffqjiangNew marked this conversation as resolved.
Resolved
}
break;
Expand Down
29 changes: 22 additions & 7 deletions projects/rocdecode/src/parser/hevc_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -589,27 +589,36 @@ ParserResult HevcVideoParser::ParsePictureData(const uint8_t* p_stream, uint32_t
// Parse the NAL unit
if (nal_unit_size_ >= 5) {
// start code + NAL unit header = 5 bytes
int ebsp_size = nal_unit_size_ - 5 > RBSP_BUF_SIZE ? RBSP_BUF_SIZE : nal_unit_size_ - 5; // only copy enough bytes for header parsing
uint32_t ebsp_size = nal_unit_size_ - 5 > RBSP_BUF_SIZE ? RBSP_BUF_SIZE : nal_unit_size_ - 5; // only copy enough bytes for header parsing

nal_unit_header_ = ParseNalUnitHeader(&pic_data_buffer_ptr_[curr_start_code_offset_ + 3]);
switch (nal_unit_header_.nal_unit_type) {
case NAL_UNIT_VPS: {
memcpy(rbsp_buf_, (pic_data_buffer_ptr_ + curr_start_code_offset_ + 5), ebsp_size);
rbsp_size_ = EbspToRbsp(rbsp_buf_, 0, ebsp_size);
if (EbspToRbsp(rbsp_buf_, 0, ebsp_size, &rbsp_size_) != PARSER_OK) {
ErrorLog(g_rocdec_logger, "This NAL unit is skipped.");
break;
}
ParseVps(rbsp_buf_, rbsp_size_);
break;
}

case NAL_UNIT_SPS: {
memcpy(rbsp_buf_, (pic_data_buffer_ptr_ + curr_start_code_offset_ + 5), ebsp_size);
rbsp_size_ = EbspToRbsp(rbsp_buf_, 0, ebsp_size);
if (EbspToRbsp(rbsp_buf_, 0, ebsp_size, &rbsp_size_) != PARSER_OK) {
ErrorLog(g_rocdec_logger, "This NAL unit is skipped.");
break;
}
ParseSps(rbsp_buf_, rbsp_size_);
break;
}

case NAL_UNIT_PPS: {
memcpy(rbsp_buf_, (pic_data_buffer_ptr_ + curr_start_code_offset_ + 5), ebsp_size);
rbsp_size_ = EbspToRbsp(rbsp_buf_, 0, ebsp_size);
if (EbspToRbsp(rbsp_buf_, 0, ebsp_size, &rbsp_size_) != PARSER_OK) {
ErrorLog(g_rocdec_logger, "This NAL unit is skipped.");
break;
}
ParsePps(rbsp_buf_, rbsp_size_);
break;
}
Expand Down Expand Up @@ -639,7 +648,10 @@ ParserResult HevcVideoParser::ParsePictureData(const uint8_t* p_stream, uint32_t
}

memcpy(rbsp_buf_, (pic_data_buffer_ptr_ + curr_start_code_offset_ + 5), ebsp_size);
rbsp_size_ = EbspToRbsp(rbsp_buf_, 0, ebsp_size);
if (EbspToRbsp(rbsp_buf_, 0, ebsp_size, &rbsp_size_) != PARSER_OK) {
ErrorLog(g_rocdec_logger, "This NAL unit is skipped.");
break;
}
HevcSliceSegHeader *p_slice_header = &slice_info_list_[num_slices_].slice_header;
if ((ret2 = ParseSliceHeader(rbsp_buf_, rbsp_size_, p_slice_header)) != PARSER_OK) {
// we got an error while parsing this NAL unit. ignore and continue with next NAL unit
Expand Down Expand Up @@ -717,7 +729,7 @@ ParserResult HevcVideoParser::ParsePictureData(const uint8_t* p_stream, uint32_t
case NAL_UNIT_PREFIX_SEI:
case NAL_UNIT_SUFFIX_SEI: {
if (pfn_get_sei_message_cb_) {
int sei_ebsp_size = nal_unit_size_ - 5; // copy the entire NAL unit
uint32_t sei_ebsp_size = nal_unit_size_ - 5; // copy the entire NAL unit
if (sei_rbsp_buf_) {
if (sei_ebsp_size > sei_rbsp_buf_size_) {
delete [] sei_rbsp_buf_;
Expand All @@ -729,7 +741,10 @@ ParserResult HevcVideoParser::ParsePictureData(const uint8_t* p_stream, uint32_t
sei_rbsp_buf_ = new uint8_t [sei_rbsp_buf_size_];
}
memcpy(sei_rbsp_buf_, (pic_data_buffer_ptr_ + curr_start_code_offset_ + 5), sei_ebsp_size);
rbsp_size_ = EbspToRbsp(sei_rbsp_buf_, 0, sei_ebsp_size);
if (EbspToRbsp(sei_rbsp_buf_, 0, sei_ebsp_size, &rbsp_size_) != PARSER_OK) {
ErrorLog(g_rocdec_logger, "This NAL unit is skipped.");
break;
}
ParseSeiMessage(sei_rbsp_buf_, rbsp_size_);
}
break;
Expand Down
68 changes: 55 additions & 13 deletions projects/rocdecode/src/parser/roc_video_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,10 @@ ParserResult RocVideoParser::GetNalUnit() {
nal_unit_size_ = 0;
curr_start_code_offset_ = next_start_code_offset_; // save the current start code offset

// Search for the next start code
while (curr_byte_offset_ < pic_data_size_ - 2) {
// Search for the next start code. Both operands are uint32_t, so the size needs testing
// separately: a one byte packet would otherwise make pic_data_size_ - 2 wrap to 0xFFFFFFFF
// and run the scan past the end of the buffer.
while (pic_data_size_ >= 3 && curr_byte_offset_ < pic_data_size_ - 2) {
if (pic_data_buffer_ptr_[curr_byte_offset_] == 0 && pic_data_buffer_ptr_[curr_byte_offset_ + 1] == 0 && pic_data_buffer_ptr_[curr_byte_offset_ + 2] == 0x01) {
curr_start_code_offset_ = next_start_code_offset_; // save the current start code offset

Expand All @@ -156,6 +158,15 @@ ParserResult RocVideoParser::GetNalUnit() {
// No NAL unit in the frame data
return PARSER_NOT_FOUND;
}
// The subtractions below are unsigned, so an end offset below the start would yield a huge
// size rather than a negative one, and the nal_unit_size_ floor the callers apply before
// copying out of the NAL unit would not catch it. Check the ordering here instead.
if (curr_start_code_offset_ > pic_data_size_ ||
(start_code_found && next_start_code_offset_ < curr_start_code_offset_)) {
ErrorLog(g_rocdec_logger, "Start code offsets are out of order for the current picture.");
nal_unit_size_ = 0;
return PARSER_NOT_FOUND;
}
if (start_code_found) {
nal_unit_size_ = next_start_code_offset_ - curr_start_code_offset_;
return PARSER_OK;
Expand All @@ -165,10 +176,12 @@ ParserResult RocVideoParser::GetNalUnit() {
}
}

size_t RocVideoParser::EbspToRbsp(uint8_t *streamBuffer,size_t begin_bytepos, size_t end_bytepos) {
ParserResult RocVideoParser::EbspToRbsp(uint8_t *streamBuffer,size_t begin_bytepos, size_t end_bytepos, size_t *p_rbsp_size) {
int count = 0;
*p_rbsp_size = 0;
if (end_bytepos < begin_bytepos) {
return end_bytepos;
*p_rbsp_size = end_bytepos;
return PARSER_OK;
}
uint8_t *streamBuffer_i = streamBuffer + begin_bytepos;
uint8_t *streamBuffer_end = streamBuffer + end_bytepos;
Expand All @@ -181,10 +194,12 @@ size_t RocVideoParser::EbspToRbsp(uint8_t *streamBuffer,size_t begin_bytepos, si
if (tmp == 0x03) {
//check the 4th uint8_t after 0x000003, except when cabac_zero_word is used, in which case the last three bytes of this NAL unit must be 0x000003
if ((streamBuffer_i + 1 != streamBuffer_end) && (streamBuffer_i[1] > 0x03)) {
return static_cast<size_t>(-1);
ErrorLog(g_rocdec_logger, "Malformed emulation prevention sequence in the NAL unit.");
return PARSER_INVALID_ARG;
}
//if cabac_zero_word is used, the final uint8_t of this NAL unit(0x03) is discarded, and the last two bytes of RBSP must be 0x0000
if (streamBuffer_i + 1 == streamBuffer_end) {
reduce_count++; // discarded as well, so it is not part of the RBSP
break;
}
memmove(streamBuffer_i, streamBuffer_i + 1, streamBuffer_end-streamBuffer_i - 1);
Expand All @@ -202,31 +217,54 @@ size_t RocVideoParser::EbspToRbsp(uint8_t *streamBuffer,size_t begin_bytepos, si
}
streamBuffer_i++;
}
return end_bytepos - begin_bytepos + reduce_count;
// Every discarded byte shortens the data, so the RBSP is the EBSP less reduce_count. Adding
// it instead reported more than the EBSP ever held: 00 00 03 01 is 3 bytes of RBSP but was
// reported as 5. For a full rbsp_buf_ that told the parse functions the buffer was larger
// than it is, which is exactly the kind of bound the rest of this change relies on.
*p_rbsp_size = end_bytepos - (begin_bytepos + reduce_count);
return PARSER_OK;
}

void RocVideoParser::ParseSeiMessage(uint8_t *nalu, size_t size) {
int offset = 0; // byte offset
int payload_type;
int payload_size;
ParserResult RocVideoParser::ParseSeiMessage(uint8_t *nalu, size_t size) {
size_t offset = 0; // byte offset
// Accumulated in size_t so that a long run of ff_bytes cannot wrap the running total before
// it is range checked below.
size_t payload_type;
size_t payload_size;

// SEI is supplemental and does not affect the decode, so a message running past the end of
// the NAL unit stops the parse here and keeps whatever was read cleanly. The result is
// reported for the record; the callers log it and carry on with the picture.
do {
payload_type = 0;
while (nalu[offset] == 0xFF) {
while (offset < size && nalu[offset] == 0xFF) {
payload_type += 255; // ff_byte
offset++;
}
if (offset >= size) {
ErrorLog(g_rocdec_logger, "SEI payload type extends past the end of the NAL unit.");
return PARSER_OUT_OF_RANGE;
}
payload_type += nalu[offset]; // last_payload_type_byte
offset++;

payload_size = 0;
while (nalu[offset] == 0xFF) {
while (offset < size && nalu[offset] == 0xFF) {
payload_size += 255; // ff_byte
offset++;
}
if (offset >= size) {
ErrorLog(g_rocdec_logger, "SEI payload size extends past the end of the NAL unit.");
return PARSER_OUT_OF_RANGE;
}
payload_size += nalu[offset]; // last_payload_size_byte
offset++;

if (payload_size > size - offset) {
ErrorLog(g_rocdec_logger, "SEI payload size (" + ROCDEC_TOSTR(payload_size) + ") exceeds the " + ROCDEC_TOSTR(size - offset) + " bytes left in the NAL unit.");
return PARSER_OUT_OF_RANGE;
}

// We start with INIT_SEI_MESSAGE_COUNT. Should be enough for normal use cases. If not, resize.
if((sei_message_count_ + 1) > sei_message_list_.size()) {
Comment thread
jeffqjiangNew marked this conversation as resolved.
Resolved
sei_message_list_.resize((sei_message_count_ + 1));
Expand All @@ -236,7 +274,10 @@ void RocVideoParser::ParseSeiMessage(uint8_t *nalu, size_t size) {

if (sei_payload_buf_) {
if ((payload_size + sei_payload_size_) > sei_payload_buf_size_) {
uint8_t *tmp_ptr = new uint8_t [payload_size + sei_payload_size_];
// Record the new size: leaving sei_payload_buf_size_ at the old value made every
// later message compare against a stale bound and reallocate again.
sei_payload_buf_size_ = sei_payload_size_ + payload_size;
uint8_t *tmp_ptr = new uint8_t [sei_payload_buf_size_];
Comment thread
jeffqjiangNew marked this conversation as resolved.
Resolved
memcpy(tmp_ptr, sei_payload_buf_, sei_payload_size_); // save the existing payload
delete [] sei_payload_buf_;
sei_payload_buf_ = tmp_ptr;
Expand All @@ -254,4 +295,5 @@ void RocVideoParser::ParseSeiMessage(uint8_t *nalu, size_t size) {

offset += payload_size;
} while (offset < size && nalu[offset] != 0x80);
return PARSER_OK;
}
32 changes: 21 additions & 11 deletions projects/rocdecode/src/parser/roc_video_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,14 @@ class RocVideoParser {

// NAL unit info
int start_code_num_; // number of start codes found so far
int curr_start_code_offset_;
int next_start_code_offset_;
int nal_unit_size_;

int rbsp_size_;
// Unsigned to match pic_data_size_ and curr_byte_offset_, so that none of the arithmetic
// between them converts signedness. GetNalUnit() range checks the two offsets before it
// derives nal_unit_size_ from them.
uint32_t curr_start_code_offset_;
uint32_t next_start_code_offset_;
uint32_t nal_unit_size_;
Comment thread
jeffqjiangNew marked this conversation as resolved.
Resolved

size_t rbsp_size_; // size_t to match the parse functions it is passed to
uint8_t rbsp_buf_[RBSP_BUF_SIZE]; // to store parameter set or slice header RBSP

int num_slices_;
Expand Down Expand Up @@ -220,20 +223,27 @@ class RocVideoParser {
ParserResult GetNalUnit();

/*! \brief Function to convert from Encapsulated Byte Sequence Packets to Raw Byte Sequence Payload
*
*
* \param [in,out] stream_buffer A pointer of <tt>uint8_t</tt> for the converted RBSP buffer.
* \param [in] begin_bytepos Start position in the EBSP buffer to convert
* \param [in] end_bytepos End position in the EBSP buffer to convert, generally it's size.
* \return Returns the size of the converted buffer in <tt>size_t</tt>
* \param [out] p_rbsp_size Size of the converted RBSP, which is the converted range less the
* emulation prevention bytes that were discarded. Set to 0 on failure, so it can
* never be mistaken for a length.
* \return <tt>ParserResult</tt>. PARSER_INVALID_ARG when an emulation prevention sequence is
* malformed, in which case the caller should skip the NAL unit.
*/
size_t EbspToRbsp(uint8_t *stream_buffer, size_t begin_bytepos, size_t end_bytepos);
ParserResult EbspToRbsp(uint8_t *stream_buffer, size_t begin_bytepos, size_t end_bytepos, size_t *p_rbsp_size);
Comment thread
jeffqjiangNew marked this conversation as resolved.
Resolved

/*! \brief Function to parse Sei Message Info
/*! \brief Function to parse Sei Message Info. Stops at the first message that runs past the
* end of the NAL unit, keeping the messages read before it. SEI does not affect the
* decode, so callers are expected to note the result and carry on with the picture
* rather than treat it as a picture level failure.
* \param [in] nalu A pointer of <tt>uint8_t</tt> for the input stream to be parsed
* \param [in] size Size of the input stream
* \return No return value
* \return <tt>ParserResult</tt>
*/
void ParseSeiMessage(uint8_t *nalu, size_t size);
ParserResult ParseSeiMessage(uint8_t *nalu, size_t size);

/*! \brief Function to initialize the decoded buffer pool
*/
Expand Down
Loading