diff --git a/include/rtc/rtp.hpp b/include/rtc/rtp.hpp index 37bc6e2..0bdfba0 100644 --- a/include/rtc/rtp.hpp +++ b/include/rtc/rtp.hpp @@ -326,6 +326,9 @@ public: inline SSRC ssrc() const { return ntohl(_ssrc); } inline void setSSRC(SSRC ssrc) { _ssrc = htonl(ssrc); } + /// Get item at given index + /// @note All items with index < `num` must be valid, otherwise this function has undefined behaviour (use `safelyCountChunkSize` to check if chunk is valid) + /// @param num Index of item to return inline RTCP_SDES_ITEM *getItem(int num) { auto base = &_items; while (num-- > 0) { @@ -335,6 +338,39 @@ public: return reinterpret_cast(base); } + long safelyCountChunkSize(unsigned int maxChunkSize) { + if (maxChunkSize < RTCP_SDES_CHUNK::size({})) { + // chunk is truncated + return -1; + } else { + unsigned int size = sizeof(SSRC); + unsigned int i = 0; + // We can always access first 4 bytes of first item (in case of no items there will be 4 null bytes) + auto item = getItem(i); + std::vector textsLength{}; + while (item->type != 0) { + if (size + RTCP_SDES_ITEM::size(0) > maxChunkSize) { + // item is too short + return -1; + } + auto itemLength = item->length(); + if (size + RTCP_SDES_ITEM::size(itemLength) >= maxChunkSize) { + // item is too large (it can't be equal to chunk size because after item there must be 1-4 null bytes as padding) + return -1; + } + textsLength.push_back(itemLength); + // safely to access next item + item = getItem(++i); + } + auto realSize = RTCP_SDES_CHUNK::size(textsLength); + if (realSize > maxChunkSize) { + // Chunk is too large + return -1; + } + return realSize; + } + } + [[nodiscard]] static unsigned int size(std::vector textLengths) { unsigned int itemsSize = 0; for (auto length: textLengths) { @@ -345,6 +381,8 @@ public: return words * 4; } + /// Get size of chunk + /// @note All items must be valid, otherwise this function has undefined behaviour (use `safelyCountChunkSize` to check if chunk is valid) [[nodiscard]] unsigned int getSize() { std::vector textLengths{}; unsigned int i = 0; @@ -374,8 +412,37 @@ public: header.prepareHeader(202, chunkCount, length); } + bool isValid() { + auto chunksSize = header.lengthInBytes() - sizeof(header); + if (chunksSize == 0) { + return true; + } else { + // there is at least one chunk + unsigned int i = 0; + unsigned int size = 0; + while (size < chunksSize) { + if (chunksSize < size + RTCP_SDES_CHUNK::size({})) { + // chunk is truncated + return false; + } + auto chunk = getChunk(i); + auto chunkSize = chunk->safelyCountChunkSize(chunksSize - size); + if (chunksSize < 0) { + // chunk is invalid + return false; + } + size += chunkSize; + } + return size == chunksSize; + } + } + /// Returns number of chunks in this packet + /// @note Returns 0 if packet is invalid inline unsigned int chunksCount() { + if (!isValid()) { + return 0; + } uint16_t chunksSize = 4 * (header.length() + 1) - sizeof(header); unsigned int size = 0; unsigned int i = 0; @@ -385,6 +452,9 @@ public: return i; } + /// Get chunk at given index + /// @note All chunks (and their items) with index < `num` must be valid, otherwise this function has undefined behaviour (use `isValid` to check if chunk is valid) + /// @param num Index of chunk to return inline RTCP_SDES_CHUNK *getChunk(int num) { auto base = &_chunks; while (num-- > 0) { diff --git a/src/peerconnection.cpp b/src/peerconnection.cpp index da23601..3b64b46 100644 --- a/src/peerconnection.cpp +++ b/src/peerconnection.cpp @@ -694,6 +694,10 @@ void PeerConnection::forwardMedia(message_ptr message) { ssrcs.insert(rtcpsr->getReportBlock(i)->getSSRC()); } else if (header->payloadType() == 202) { auto sdes = reinterpret_cast(header); + if (!sdes->isValid()) { + PLOG_WARNING << "RTCP SDES packet is invalid"; + continue; + } for (unsigned int i = 0; i < sdes->chunksCount(); i++) { auto chunk = sdes->getChunk(i); ssrcs.insert(chunk->ssrc());