From c6bfb45b7a4298cf643edbfed2910d7623469ee4 Mon Sep 17 00:00:00 2001 From: azerom960 Date: Tue, 29 Sep 2026 16:30:39 -0400 Subject: [PATCH 1/4] RDKEMW-26106: enforce CENC metadata bounds Centralize subsample validation across OCDM adapter entry points and add executable boundary coverage. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- Source/ocdm/CMakeLists.txt | 9 ++ Source/ocdm/adapter/SubSampleParser.h | 42 +++++++++ .../adapter/gstreamer/open_cdm_adapter.cpp | 90 +++++++------------ Source/ocdm/adapter/rdk/open_cdm_adapter.cpp | 72 ++++++--------- Source/ocdm/test_ocdm_cenc_bounds.cpp | 42 +++++++++ 5 files changed, 150 insertions(+), 105 deletions(-) create mode 100644 Source/ocdm/adapter/SubSampleParser.h create mode 100644 Source/ocdm/test_ocdm_cenc_bounds.cpp diff --git a/Source/ocdm/CMakeLists.txt b/Source/ocdm/CMakeLists.txt index 502e544f..fb57e428 100644 --- a/Source/ocdm/CMakeLists.txt +++ b/Source/ocdm/CMakeLists.txt @@ -174,3 +174,12 @@ InstallCMakeConfig( InstallPackageConfig( TARGETS ${TARGET} DESCRIPTION "OCDM library") + +option(BUILD_OCDM_TESTS "Build OCDM tests" OFF) +if(BUILD_OCDM_TESTS) + enable_testing() + add_executable(ocdm_cenc_bounds_test test_ocdm_cenc_bounds.cpp) + target_include_directories(ocdm_cenc_bounds_test PRIVATE ${CMAKE_CURRENT_LIST_DIR}) + set_target_properties(ocdm_cenc_bounds_test PROPERTIES CXX_STANDARD ${CXX_STD} CXX_STANDARD_REQUIRED YES) + add_test(NAME ocdm_cenc_bounds_test COMMAND ocdm_cenc_bounds_test) +endif() diff --git a/Source/ocdm/adapter/SubSampleParser.h b/Source/ocdm/adapter/SubSampleParser.h new file mode 100644 index 00000000..b45748ef --- /dev/null +++ b/Source/ocdm/adapter/SubSampleParser.h @@ -0,0 +1,42 @@ +#pragma once + +#include "../open_cdm.h" + +#include +#include +#include + +namespace Thunder { +namespace OCDM { + +inline bool ParseSubSamples(const uint8_t data[], const uint32_t length, const uint32_t count, const uint32_t sampleLength, std::vector& entries, uint32_t& encryptedLength) +{ + constexpr uint32_t entryLength = sizeof(uint16_t) + sizeof(uint32_t); + if ((count > std::numeric_limits::max()) || (count > (std::numeric_limits::max() / entryLength)) || (length != count * entryLength) || ((count != 0) && (data == nullptr))) { + return false; + } + + entries.clear(); + entries.reserve(count); + uint64_t sampleOffset = 0; + uint64_t encryptedOffset = 0; + + for (uint32_t index = 0; index < count; ++index) { + const uint8_t* entry = data + (index * entryLength); + const uint16_t clear = static_cast((static_cast(entry[0]) << 8) | entry[1]); + const uint32_t encrypted = (static_cast(entry[2]) << 24) | (static_cast(entry[3]) << 16) | (static_cast(entry[4]) << 8) | entry[5]; + sampleOffset += static_cast(clear) + encrypted; + encryptedOffset += encrypted; + if ((sampleOffset > sampleLength) || (encryptedOffset > std::numeric_limits::max())) { + entries.clear(); + return false; + } + entries.push_back({ clear, encrypted }); + } + + encryptedLength = static_cast(encryptedOffset); + return true; +} + +} +} diff --git a/Source/ocdm/adapter/gstreamer/open_cdm_adapter.cpp b/Source/ocdm/adapter/gstreamer/open_cdm_adapter.cpp index 4ce42525..96fce4cd 100644 --- a/Source/ocdm/adapter/gstreamer/open_cdm_adapter.cpp +++ b/Source/ocdm/adapter/gstreamer/open_cdm_adapter.cpp @@ -18,11 +18,11 @@ */ #include -#include #include "Module.h" #include "CapsParser.h" #include "open_cdm_adapter.h" +#include "../SubSampleParser.h" inline bool mappedBuffer(GstBuffer *buffer, bool writable, uint8_t **data, uint32_t *size) { @@ -109,49 +109,35 @@ OpenCDMError opencdm_gstreamer_session_decrypt(struct OpenCDMSession* session, G } uint8_t *mappedSubSample = reinterpret_cast(sampleMap.data); uint32_t mappedSubSampleSize = static_cast(sampleMap.size); - GstByteReader* reader = gst_byte_reader_new(mappedSubSample, mappedSubSampleSize); - uint16_t inClear = 0; - uint32_t inEncrypted = 0; + std::vector subSamples; uint32_t totalEncrypted = 0; - for (unsigned int position = 0; position < subSampleCount; position++) { - - gst_byte_reader_get_uint16_be(reader, &inClear); - gst_byte_reader_get_uint32_be(reader, &inEncrypted); - totalEncrypted += inEncrypted; + if (Thunder::OCDM::ParseSubSamples(mappedSubSample, mappedSubSampleSize, subSampleCount, mappedDataSize, subSamples, totalEncrypted) == false) { + gst_buffer_unmap(subSampleBuffer, &sampleMap); + if (keyID != nullptr) { + gst_buffer_unmap(keyID, &keyIDMap); + } + gst_buffer_unmap(IV, &ivMap); + gst_buffer_unmap(buffer, &dataMap); + return (ERROR_INVALID_DECRYPT_BUFFER); } - gst_byte_reader_set_pos(reader, 0); - - uint8_t* encryptedData = reinterpret_cast(malloc(totalEncrypted)); - uint8_t* encryptedDataIter = encryptedData; - - uint32_t index = 0; - for (unsigned int position = 0; position < subSampleCount; position++) { - - gst_byte_reader_get_uint16_be(reader, &inClear); - gst_byte_reader_get_uint32_be(reader, &inEncrypted); - memcpy(encryptedDataIter, mappedData + index + inClear, inEncrypted); - index += inClear + inEncrypted; - encryptedDataIter += inEncrypted; + std::vector encryptedData(totalEncrypted); + uint32_t sampleOffset = 0; + uint32_t encryptedOffset = 0; + for (const auto& entry : subSamples) { + memcpy(encryptedData.data() + encryptedOffset, mappedData + sampleOffset + entry.clear_bytes, entry.encrypted_bytes); + sampleOffset += entry.clear_bytes + entry.encrypted_bytes; + encryptedOffset += entry.encrypted_bytes; } - gst_byte_reader_set_pos(reader, 0); - - result = opencdm_session_decrypt(session, encryptedData, totalEncrypted, encScheme, pattern, mappedIV, mappedIVSize, mappedKeyID, mappedKeyIDSize, initWithLast15); - // Re-build sub-sample data. - index = 0; - unsigned total = 0; - for (uint32_t position = 0; position < subSampleCount; position++) { - gst_byte_reader_get_uint16_be(reader, &inClear); - gst_byte_reader_get_uint32_be(reader, &inEncrypted); - - memcpy(mappedData + total + inClear, encryptedData + index, inEncrypted); - index += inEncrypted; - total += inClear + inEncrypted; + result = opencdm_session_decrypt(session, encryptedData.data(), totalEncrypted, encScheme, pattern, mappedIV, mappedIVSize, mappedKeyID, mappedKeyIDSize, initWithLast15); + sampleOffset = 0; + encryptedOffset = 0; + for (const auto& entry : subSamples) { + memcpy(mappedData + sampleOffset + entry.clear_bytes, encryptedData.data() + encryptedOffset, entry.encrypted_bytes); + sampleOffset += entry.clear_bytes + entry.encrypted_bytes; + encryptedOffset += entry.encrypted_bytes; } - - gst_byte_reader_free(reader); - free(encryptedData); gst_buffer_unmap(subSampleBuffer, &sampleMap); } else { result = opencdm_session_decrypt(session, mappedData, mappedDataSize, encScheme, pattern, mappedIV, mappedIVSize, mappedKeyID, mappedKeyIDSize, initWithLast15); @@ -206,12 +192,11 @@ OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* ses goto exit; } subSample = gst_value_get_buffer(value); - if (subSample != nullptr && mappedBuffer(subSample, false, &mappedSubSample, &mappedSubSampleSize) == false) { + if ((subSample == nullptr) || (subSampleCount > UINT8_MAX) || (mappedBuffer(subSample, false, &mappedSubSample, &mappedSubSampleSize) == false)) { TRACE_L1("opencdm_gstreamer_session_decrypt_buffer: Invalid subsample buffer."); result = ERROR_INVALID_DECRYPT_BUFFER; goto exit; } - ASSERT(mappedSubSampleSize==subSampleCount); } //Get IV @@ -224,7 +209,7 @@ OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* ses GstBuffer* IV = gst_value_get_buffer(value); uint8_t *mappedIV = nullptr; //Set the Encryption Scheme and Pattern to defaults. uint32_t mappedIVSize = 0; - if (mappedBuffer(IV, false, &mappedIV, &mappedIVSize) == false) { + if ((IV == nullptr) || (mappedBuffer(IV, false, &mappedIV, &mappedIVSize) == false)) { TRACE_L1("opencdm_gstreamer_session_decrypt_buffer: Invalid IV buffer."); result = ERROR_INVALID_DECRYPT_BUFFER; goto exit; @@ -258,18 +243,11 @@ OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* ses gst_structure_get_uint(protectionMeta->info, "crypt_byte_block", &pattern.encrypted_blocks); gst_structure_get_uint(protectionMeta->info, "skip_byte_block", &pattern.clear_blocks); - //Create a SubSampleInfo Array with mapping - SubSampleInfo * subSampleInfoPtr = nullptr; - if (subSample != nullptr) { - GstByteReader* reader = gst_byte_reader_new(mappedSubSample, mappedSubSampleSize); - subSampleInfoPtr = reinterpret_cast(malloc(subSampleCount * sizeof(SubSampleInfo))); - for (unsigned int position = 0; position < subSampleCount; position++) { - - gst_byte_reader_get_uint16_be(reader, &subSampleInfoPtr[position].clear_bytes); - gst_byte_reader_get_uint32_be(reader, &subSampleInfoPtr[position].encrypted_bytes); - } - gst_byte_reader_set_pos(reader, 0); - gst_byte_reader_free(reader); + std::vector subSamples; + uint32_t totalEncrypted = mappedDataSize; + if ((subSampleCount > 0) && (Thunder::OCDM::ParseSubSamples(mappedSubSample, mappedSubSampleSize, subSampleCount, mappedDataSize, subSamples, totalEncrypted) == false)) { + result = ERROR_INVALID_DECRYPT_BUFFER; + goto exit; } //Get Stream Properties from GstCaps @@ -307,7 +285,7 @@ OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* ses } SampleInfo sampleInfo; - sampleInfo.subSample = subSampleInfoPtr; + sampleInfo.subSample = subSamples.empty() ? nullptr : subSamples.data(); sampleInfo.subSampleCount = subSampleCount; sampleInfo.scheme = encScheme; sampleInfo.pattern.clear_blocks = pattern.clear_blocks; @@ -323,10 +301,6 @@ OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* ses &sampleInfo, spPtr); - //Clean up - if(subSampleInfoPtr != nullptr) { - free(subSampleInfoPtr); - } } else { TRACE_L1("opencdm_gstreamer_session_decrypt_buffer: Missing Protection Metadata."); result = ERROR_INVALID_DECRYPT_BUFFER; diff --git a/Source/ocdm/adapter/rdk/open_cdm_adapter.cpp b/Source/ocdm/adapter/rdk/open_cdm_adapter.cpp index 81e1627a..8c9fb324 100644 --- a/Source/ocdm/adapter/rdk/open_cdm_adapter.cpp +++ b/Source/ocdm/adapter/rdk/open_cdm_adapter.cpp @@ -19,10 +19,10 @@ #include "open_cdm_adapter.h" #include "open_cdm_impl.h" +#include "../SubSampleParser.h" #include "Module.h" #include -#include #include #include "../CapsParser.h" @@ -141,18 +141,15 @@ OpenCDMError opencdm_gstreamer_session_decrypt(struct OpenCDMSession* session, G uint8_t *mappedSubSample = reinterpret_cast(sampleMap.data); uint32_t mappedSubSampleSize = static_cast(sampleMap.size); - GstByteReader* reader = gst_byte_reader_new(mappedSubSample, mappedSubSampleSize); - uint16_t inClear = 0; - uint32_t inEncrypted = 0; + std::vector subSamples; uint32_t totalEncrypted = 0; - uint32_t nCount = 0; - for (unsigned int position = 0; position < subSampleCount; position++) { - - gst_byte_reader_get_uint16_be(reader, &inClear); - gst_byte_reader_get_uint32_be(reader, &inEncrypted); - totalEncrypted += inEncrypted; + if (Thunder::OCDM::ParseSubSamples(mappedSubSample, mappedSubSampleSize, subSampleCount, mappedDataSize, subSamples, totalEncrypted) == false) { + gst_buffer_unmap(subSample, &sampleMap); + gst_buffer_unmap(keyID, &keyIDMap); + gst_buffer_unmap(IV, &ivMap); + gst_buffer_unmap(buffer, &dataMap); + return (ERROR_INVALID_DECRYPT_BUFFER); } - gst_byte_reader_set_pos(reader, 0); if(totalEncrypted > 0) { @@ -160,19 +157,12 @@ OpenCDMError opencdm_gstreamer_session_decrypt(struct OpenCDMSession* session, G gsize dataBlockSize = gst_svp_allocate_data_block(session->SessionPrivateData(), (void**) &svpData, totalEncrypted, totalEncrypted); uint8_t* encryptedDataIter = reinterpret_cast(gst_svp_header_get_start_of_data(session->SessionPrivateData(), svpData)); - - uint32_t index = 0; - for (unsigned int position = 0; position < subSampleCount; position++) { - - gst_byte_reader_get_uint16_be(reader, &inClear); - - gst_byte_reader_get_uint32_be(reader, &inEncrypted); - - memcpy(encryptedDataIter, mappedData + index + inClear, inEncrypted); - index += inClear + inEncrypted; - encryptedDataIter += inEncrypted; + uint32_t sampleOffset = 0; + for (const auto& entry : subSamples) { + memcpy(encryptedDataIter, mappedData + sampleOffset + entry.clear_bytes, entry.encrypted_bytes); + sampleOffset += entry.clear_bytes + entry.encrypted_bytes; + encryptedDataIter += entry.encrypted_bytes; } - gst_byte_reader_set_pos(reader, 0); GstPerf* ocdm_perf = new GstPerf("opencdm_session_decrypt_subsample"); result = opencdm_session_decrypt(session, svpData, dataBlockSize, encScheme, pattern, mappedIV, mappedIVSize, @@ -191,7 +181,6 @@ OpenCDMError opencdm_gstreamer_session_decrypt(struct OpenCDMSession* session, G gst_buffer_svp_transform_from_cleardata(session->SessionPrivateData(), buffer, mediaType); result = ERROR_NONE; } - gst_byte_reader_free(reader); gst_buffer_unmap(subSample, &sampleMap); } else { uint8_t* encryptedData = NULL; @@ -270,7 +259,7 @@ OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* ses } subSample = gst_value_get_buffer(value); - if (subSample != nullptr && gst_buffer_map(subSample, &sampleMap, GST_MAP_READ) == false) { + if ((subSample == nullptr) || (subSampleCount > UINT8_MAX) || (gst_buffer_map(subSample, &sampleMap, GST_MAP_READ) == false)) { TRACE_L1("opencdm_gstreamer_session_decrypt_buffer: Invalid subsample buffer."); gst_buffer_unmap(buffer, &dataMap); result = ERROR_INVALID_DECRYPT_BUFFER; @@ -291,7 +280,7 @@ OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* ses } GstBuffer* IV = gst_value_get_buffer(value); GstMapInfo ivMap; - if (IV != nullptr && gst_buffer_map(IV, &ivMap, (GstMapFlags) GST_MAP_READ) == false) { + if ((IV == nullptr) || (gst_buffer_map(IV, &ivMap, (GstMapFlags) GST_MAP_READ) == false)) { TRACE_L1("opencdm_gstreamer_session_decrypt_buffer: Invalid IV buffer."); gst_buffer_unmap(buffer, &dataMap); gst_buffer_unmap(subSample, &sampleMap); @@ -400,26 +389,19 @@ OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* ses gst_structure_get_uint(protectionMeta->info, "crypt_byte_block", &pattern.encrypted_blocks); gst_structure_get_uint(protectionMeta->info, "skip_byte_block", &pattern.clear_blocks); - //Create a SubSampleInfo Array with mapping - SubSampleInfo * subSampleInfoPtr = nullptr; - uint32_t total_encrypted_bytes = 0; - if (subSample != nullptr) { - GstByteReader* reader = gst_byte_reader_new(mappedSubSample, mappedSubSampleSize); - subSampleInfoPtr = reinterpret_cast(malloc(subSampleCount * sizeof(SubSampleInfo))); - for (unsigned int position = 0; position < subSampleCount; position++) { - - gst_byte_reader_get_uint16_be(reader, &subSampleInfoPtr[position].clear_bytes); - gst_byte_reader_get_uint32_be(reader, &subSampleInfoPtr[position].encrypted_bytes); - total_encrypted_bytes += subSampleInfoPtr[position].encrypted_bytes; - } - gst_byte_reader_set_pos(reader, 0); - gst_byte_reader_free(reader); - } else { - total_encrypted_bytes = mappedDataSize; + std::vector subSamples; + uint32_t total_encrypted_bytes = mappedDataSize; + if ((subSampleCount > 0) && (Thunder::OCDM::ParseSubSamples(mappedSubSample, mappedSubSampleSize, subSampleCount, mappedDataSize, subSamples, total_encrypted_bytes) == false)) { + gst_buffer_unmap(buffer, &dataMap); + gst_buffer_unmap(subSample, &sampleMap); + gst_buffer_unmap(IV, &ivMap); + gst_buffer_unmap(keyID, &keyIDMap); + result = ERROR_INVALID_DECRYPT_BUFFER; + goto exit; } SampleInfo sampleInfo; - sampleInfo.subSample = subSampleInfoPtr; + sampleInfo.subSample = subSamples.empty() ? nullptr : subSamples.data(); sampleInfo.subSampleCount = subSampleCount; sampleInfo.scheme = encScheme; sampleInfo.pattern.clear_blocks = pattern.clear_blocks; @@ -458,10 +440,6 @@ OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* ses result = ERROR_NONE; } - //Clean up - if(subSampleInfoPtr != nullptr) { - free(subSampleInfoPtr); - } gst_buffer_unmap(buffer, &dataMap); diff --git a/Source/ocdm/test_ocdm_cenc_bounds.cpp b/Source/ocdm/test_ocdm_cenc_bounds.cpp new file mode 100644 index 00000000..85089738 --- /dev/null +++ b/Source/ocdm/test_ocdm_cenc_bounds.cpp @@ -0,0 +1,42 @@ +#include "adapter/SubSampleParser.h" + +#include +#include +#include + +int main() +{ + std::vector entries; + uint32_t encryptedLength = 0; + + const std::array valid { 0, 2, 0, 0, 0, 4, 0, 1, 0, 0, 0, 3 }; + if (!Thunder::OCDM::ParseSubSamples(valid.data(), valid.size(), 2, 10, entries, encryptedLength) || entries.size() != 2 || encryptedLength != 7) { + return 1; + } + + const std::array truncated { 0, 0, 0, 0, 1 }; + if (Thunder::OCDM::ParseSubSamples(truncated.data(), truncated.size(), 1, 1, entries, encryptedLength)) { + return 2; + } + + const std::array oversizedClear { 0, 2, 0, 0, 0, 1 }; + if (Thunder::OCDM::ParseSubSamples(oversizedClear.data(), oversizedClear.size(), 1, 2, entries, encryptedLength)) { + return 3; + } + + const std::array overflowingEncrypted { 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 1 }; + if (Thunder::OCDM::ParseSubSamples(overflowingEncrypted.data(), overflowingEncrypted.size(), 2, UINT32_MAX, entries, encryptedLength)) { + return 4; + } + + if (Thunder::OCDM::ParseSubSamples(valid.data(), valid.size(), 1, 10, entries, encryptedLength)) { + return 5; + } + + const std::vector excessiveCount(256 * 6); + if (Thunder::OCDM::ParseSubSamples(excessiveCount.data(), excessiveCount.size(), 256, 0, entries, encryptedLength)) { + return 6; + } + + return 0; +} From f84c5c53fba60f00ab954b2a03397224f626838e Mon Sep 17 00:00:00 2001 From: azerom960 Date: Tue, 29 Sep 2026 16:40:44 -0400 Subject: [PATCH 2/4] RDKEMW-26106: cover empty metadata boundaries Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- Source/ocdm/test_ocdm_cenc_bounds.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/ocdm/test_ocdm_cenc_bounds.cpp b/Source/ocdm/test_ocdm_cenc_bounds.cpp index 85089738..26c270bb 100644 --- a/Source/ocdm/test_ocdm_cenc_bounds.cpp +++ b/Source/ocdm/test_ocdm_cenc_bounds.cpp @@ -38,5 +38,13 @@ int main() return 6; } + if (!Thunder::OCDM::ParseSubSamples(nullptr, 0, 0, 0, entries, encryptedLength) || !entries.empty() || encryptedLength != 0) { + return 7; + } + + if (Thunder::OCDM::ParseSubSamples(nullptr, 6, 1, 1, entries, encryptedLength)) { + return 8; + } + return 0; } From 9d5db64bef8731a56e5da18791f165008679a86d Mon Sep 17 00:00:00 2001 From: azerom960 Date: Tue, 29 Sep 2026 16:52:24 -0400 Subject: [PATCH 3/4] RDKEMW-26106: reject inconsistent adapter metadata Fail closed on mismatched subsample state and missing required key buffers before adapter sinks. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- Source/ocdm/adapter/gstreamer/open_cdm_adapter.cpp | 5 ++++- Source/ocdm/adapter/rdk/open_cdm_adapter.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/ocdm/adapter/gstreamer/open_cdm_adapter.cpp b/Source/ocdm/adapter/gstreamer/open_cdm_adapter.cpp index 96fce4cd..abfe0088 100644 --- a/Source/ocdm/adapter/gstreamer/open_cdm_adapter.cpp +++ b/Source/ocdm/adapter/gstreamer/open_cdm_adapter.cpp @@ -43,6 +43,9 @@ OpenCDMError opencdm_gstreamer_session_decrypt(struct OpenCDMSession* session, G GstBuffer* IV, GstBuffer* keyID, uint32_t initWithLast15) { OpenCDMError result (ERROR_INVALID_SESSION); + if (((subSampleCount == 0) != (subSampleBuffer == nullptr)) || (subSampleCount > UINT8_MAX)) { + return (ERROR_INVALID_DECRYPT_BUFFER); + } if (session != nullptr) { GstMapInfo dataMap; @@ -225,7 +228,7 @@ OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* ses GstBuffer* keyID = gst_value_get_buffer(value); uint8_t *mappedKeyID = nullptr; uint32_t mappedKeyIDSize = 0; - if (keyID != nullptr && mappedBuffer(keyID, false, &mappedKeyID, &mappedKeyIDSize) == false) { + if ((keyID == nullptr) || (mappedBuffer(keyID, false, &mappedKeyID, &mappedKeyIDSize) == false)) { TRACE_L1("Invalid keyID buffer."); result = ERROR_INVALID_DECRYPT_BUFFER; goto exit; diff --git a/Source/ocdm/adapter/rdk/open_cdm_adapter.cpp b/Source/ocdm/adapter/rdk/open_cdm_adapter.cpp index 8c9fb324..76aa27b6 100644 --- a/Source/ocdm/adapter/rdk/open_cdm_adapter.cpp +++ b/Source/ocdm/adapter/rdk/open_cdm_adapter.cpp @@ -73,6 +73,9 @@ OpenCDMError opencdm_gstreamer_session_decrypt(struct OpenCDMSession* session, G GstBuffer* IV, GstBuffer* keyID, uint32_t initWithLast15) { OpenCDMError result (ERROR_INVALID_SESSION); + if (((subSampleCount == 0) != (subSample == nullptr)) || (subSampleCount > UINT8_MAX)) { + return (ERROR_INVALID_DECRYPT_BUFFER); + } if (session != nullptr) { GstMapInfo dataMap; if (gst_buffer_map(buffer, &dataMap, (GstMapFlags) GST_MAP_READWRITE) == false) { @@ -313,7 +316,7 @@ OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* ses uint8_t *mappedKeyID = nullptr; uint32_t mappedKeyIDSize = 0; GstMapInfo keyIDMap; - if (keyID != nullptr && gst_buffer_map(keyID, &keyIDMap, (GstMapFlags) GST_MAP_READ) == false) { + if ((keyID == nullptr) || (gst_buffer_map(keyID, &keyIDMap, (GstMapFlags) GST_MAP_READ) == false)) { TRACE_L1("Invalid keyID buffer."); gst_buffer_unmap(buffer, &dataMap); gst_buffer_unmap(subSample, &sampleMap); From 82f0519f14fda7888f417e353f87c37c38d0e305 Mon Sep 17 00:00:00 2001 From: azerom960 Date: Wed, 30 Sep 2026 00:50:20 -0400 Subject: [PATCH 4/4] RDKEMW-26106: Harden malformed metadata cleanup Only unmap successfully mapped subsample buffers and cover the maximum supported count boundary. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- Source/ocdm/adapter/rdk/open_cdm_adapter.cpp | 20 +++++++++++++++----- Source/ocdm/test_ocdm_cenc_bounds.cpp | 7 ++++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Source/ocdm/adapter/rdk/open_cdm_adapter.cpp b/Source/ocdm/adapter/rdk/open_cdm_adapter.cpp index 76aa27b6..a2897bf3 100644 --- a/Source/ocdm/adapter/rdk/open_cdm_adapter.cpp +++ b/Source/ocdm/adapter/rdk/open_cdm_adapter.cpp @@ -277,7 +277,9 @@ OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* ses if (!value) { TRACE_L1("opencdm_gstreamer_session_decrypt_buffer: Missing IV buffer."); gst_buffer_unmap(buffer, &dataMap); - gst_buffer_unmap(subSample, &sampleMap); + if (subSample != nullptr) { + gst_buffer_unmap(subSample, &sampleMap); + } result = ERROR_INVALID_DECRYPT_BUFFER; goto exit; } @@ -286,7 +288,9 @@ OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* ses if ((IV == nullptr) || (gst_buffer_map(IV, &ivMap, (GstMapFlags) GST_MAP_READ) == false)) { TRACE_L1("opencdm_gstreamer_session_decrypt_buffer: Invalid IV buffer."); gst_buffer_unmap(buffer, &dataMap); - gst_buffer_unmap(subSample, &sampleMap); + if (subSample != nullptr) { + gst_buffer_unmap(subSample, &sampleMap); + } result = ERROR_INVALID_DECRYPT_BUFFER; goto exit; } @@ -306,7 +310,9 @@ OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* ses if (!value) { TRACE_L1("opencdm_gstreamer_session_decrypt_buffer: Missing KeyId buffer."); gst_buffer_unmap(buffer, &dataMap); - gst_buffer_unmap(subSample, &sampleMap); + if (subSample != nullptr) { + gst_buffer_unmap(subSample, &sampleMap); + } gst_buffer_unmap(IV, &ivMap); result = ERROR_INVALID_DECRYPT_BUFFER; goto exit; @@ -319,7 +325,9 @@ OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* ses if ((keyID == nullptr) || (gst_buffer_map(keyID, &keyIDMap, (GstMapFlags) GST_MAP_READ) == false)) { TRACE_L1("Invalid keyID buffer."); gst_buffer_unmap(buffer, &dataMap); - gst_buffer_unmap(subSample, &sampleMap); + if (subSample != nullptr) { + gst_buffer_unmap(subSample, &sampleMap); + } gst_buffer_unmap(IV, &ivMap); result = ERROR_INVALID_DECRYPT_BUFFER; goto exit; @@ -396,7 +404,9 @@ OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* ses uint32_t total_encrypted_bytes = mappedDataSize; if ((subSampleCount > 0) && (Thunder::OCDM::ParseSubSamples(mappedSubSample, mappedSubSampleSize, subSampleCount, mappedDataSize, subSamples, total_encrypted_bytes) == false)) { gst_buffer_unmap(buffer, &dataMap); - gst_buffer_unmap(subSample, &sampleMap); + if (subSample != nullptr) { + gst_buffer_unmap(subSample, &sampleMap); + } gst_buffer_unmap(IV, &ivMap); gst_buffer_unmap(keyID, &keyIDMap); result = ERROR_INVALID_DECRYPT_BUFFER; diff --git a/Source/ocdm/test_ocdm_cenc_bounds.cpp b/Source/ocdm/test_ocdm_cenc_bounds.cpp index 26c270bb..b9cfa380 100644 --- a/Source/ocdm/test_ocdm_cenc_bounds.cpp +++ b/Source/ocdm/test_ocdm_cenc_bounds.cpp @@ -33,9 +33,14 @@ int main() return 5; } + const std::vector maximumCount(255 * 6); + if (!Thunder::OCDM::ParseSubSamples(maximumCount.data(), maximumCount.size(), 255, 0, entries, encryptedLength) || entries.size() != 255) { + return 6; + } + const std::vector excessiveCount(256 * 6); if (Thunder::OCDM::ParseSubSamples(excessiveCount.data(), excessiveCount.size(), 256, 0, entries, encryptedLength)) { - return 6; + return 7; } if (!Thunder::OCDM::ParseSubSamples(nullptr, 0, 0, 0, entries, encryptedLength) || !entries.empty() || encryptedLength != 0) {