diff --git a/middleware/drm/DrmSession.cpp b/middleware/drm/DrmSession.cpp index 34087df3fb..35d10c9525 100644 --- a/middleware/drm/DrmSession.cpp +++ b/middleware/drm/DrmSession.cpp @@ -24,12 +24,17 @@ #include "DrmSession.h" #include "PlayerLogManager.h" +#include /** * @brief Constructor for DrmSession. */ DrmSession::DrmSession(const string &keySystem) : m_keySystem(keySystem),m_OutputProtectionEnabled(false) , mContentSecurityManagerSession() + , mLifecycleMutex() + , mLifecycleCV() + , mActiveOperations(0) + , mMarkedForDestruction(false) { } @@ -40,6 +45,55 @@ DrmSession::~DrmSession() { } +/** + * @brief DELIA-70726 fix: Acquire lifecycle guard before use in decrypt(). + */ +bool DrmSession::AcquireForUse() +{ + std::lock_guard lock(mLifecycleMutex); + if (mMarkedForDestruction) + { + return false; + } + mActiveOperations++; + return true; +} + +/** + * @brief DELIA-70726 fix: Release lifecycle guard after use in decrypt(). + */ +void DrmSession::ReleaseAfterUse() +{ + std::lock_guard lock(mLifecycleMutex); + if (mActiveOperations > 0) + { + mActiveOperations--; + } + if (mActiveOperations == 0) + { + mLifecycleCV.notify_all(); + } +} + +/** + * @brief DELIA-70726 fix: Block deletion of this session until any decrypt() + * call already in progress (having acquired the guard) has finished. + */ +void DrmSession::PrepareForDestruction(uint32_t timeoutMs) +{ + std::unique_lock lock(mLifecycleMutex); + mMarkedForDestruction = true; + if (mActiveOperations > 0) + { + MW_LOG_WARN("DrmSession::PrepareForDestruction : waiting for %d in-flight decrypt operation(s) to complete before delete", mActiveOperations); + mLifecycleCV.wait_for(lock, std::chrono::milliseconds(timeoutMs), [this]() { return mActiveOperations == 0; }); + if (mActiveOperations > 0) + { + MW_LOG_ERR("DrmSession::PrepareForDestruction : timed out waiting for in-flight decrypt operation(s); proceeding with destruction"); + } + } +} + /** * @brief Get the DRM System, ie, UUID for PlayReady WideVine etc.. */ @@ -65,3 +119,14 @@ int DrmSession::decrypt(const uint8_t *f_pbIV, uint32_t f_cbIV, const uint8_t *p MW_LOG_ERR("Standard decrypt method not implemented"); return -1; } + +/** + * @brief Get the list of usable key IDs from the DRM session + * @retval Reference to vector of usable key IDs + * @note Default implementation returns the reference to an empty vector + */ +const std::vector>& DrmSession::getUsableKeys() const +{ + static const std::vector> emptyVector; + return emptyVector; +} diff --git a/middleware/drm/DrmSession.h b/middleware/drm/DrmSession.h index 4ff9e78497..0072fdcf7a 100755 --- a/middleware/drm/DrmSession.h +++ b/middleware/drm/DrmSession.h @@ -29,6 +29,9 @@ #include #include #include +#include +#include +#include #include "DrmUtils.h" #include "ContentSecurityManagerSession.h" @@ -66,7 +69,53 @@ class DrmSession std::string m_keySystem; bool m_OutputProtectionEnabled; ContentSecurityManagerSession mContentSecurityManagerSession; + + /* DELIA-70726 fix: + * Lifecycle guard used to prevent the DrmSession object from being + * deleted (e.g. by DrmSessionManager during DRM session slot + * reuse/eviction on back-to-back channel changes) while a GStreamer + * pipeline thread (multiqueue/decryptor) is concurrently inside + * decrypt()/verifyOutputProtection(). Without this guard, deletion of + * a session that is still referenced by an old, not-yet-fully-torn-down + * pipeline results in a use-after-free SIGSEGV inside + * OCDMSessionAdapter::verifyOutputProtection(). + */ + std::mutex mLifecycleMutex; + std::condition_variable mLifecycleCV; + int mActiveOperations; + bool mMarkedForDestruction; + public: + /** + * @fn AcquireForUse + * @brief Must be called by any external caller (e.g. the GStreamer + * decryptor element) before invoking decrypt() on a DrmSession + * obtained via a raw/cached pointer. Returns false if the + * session is already being torn down, in which case decrypt() + * MUST NOT be called on this object. + * @retval true if it is safe to call decrypt(), false otherwise. + */ + bool AcquireForUse(); + + /** + * @fn ReleaseAfterUse + * @brief Must be called exactly once for every successful AcquireForUse(), + * after the decrypt() call completes. + */ + void ReleaseAfterUse(); + + /** + * @fn PrepareForDestruction + * @brief Must be called by the owner (DrmSessionManager) before deleting + * this DrmSession. Marks the session so that any new + * AcquireForUse() calls fail fast, and blocks (bounded) until all + * in-flight decrypt() operations that already acquired the guard + * have completed, making it safe to free the object. + * @param timeoutMs maximum time to wait for in-flight operations to drain. + */ + void PrepareForDestruction(uint32_t timeoutMs = 3000); + + /** * @brief Create drm session with given init data * @param f_pbInitData : pointer to initdata @@ -135,6 +184,13 @@ class DrmSession */ virtual void clearDecryptContext() = 0; + /** + * @brief Get the list of usable key IDs from the DRM session + * @retval Reference to vector of usable key IDs + * @note Default implementation returns the reference to an empty vector + */ + virtual const std::vector>& getUsableKeys() const; + /** * @fn DrmSession * @param keySystem : DRM key system uuid diff --git a/middleware/drm/DrmSessionManager.cpp b/middleware/drm/DrmSessionManager.cpp index 61c8f404be..305626aa51 100755 --- a/middleware/drm/DrmSessionManager.cpp +++ b/middleware/drm/DrmSessionManager.cpp @@ -37,7 +37,10 @@ #define INVALID_SESSION_SLOT -1 #define DEFAULT_CDM_WAIT_TIMEOUT_MS 2000 -KeyID::KeyID() : creationTime(0), isFailedKeyId(false), isPrimaryKeyId(false), data() +/** + * @brief KeyIdEntries constructor. + */ +KeyIdEntries::KeyIdEntries() : creationTime(0), isFailedKeyEntries(false), isPrimaryKeyId(false), data() { } @@ -60,7 +63,7 @@ DrmSessionManager::DrmSessionManager(int maxDrmSessions, void *player, std::func ,mPlayerSendWatermarkSessionUpdateEventCB(watermarkSessionUpdateCallback) { drmSessionContexts = new DrmSessionContext[mMaxDRMSessions]; - cachedKeyIDs = new KeyID[mMaxDRMSessions]; + cachedKeyIDs = new KeyIdEntries[mMaxDRMSessions]; m_drmConfigParam = new configs(); playerSecInstance = new PlayerSecInterface(); @@ -106,6 +109,10 @@ void DrmSessionManager::clearSessionData() { if (drmSessionContexts != NULL && drmSessionContexts[i].drmSession != NULL) { + /* DELIA-70726 fix: block until any in-flight decrypt() on this session + * (called from a GStreamer pipeline thread via a cached raw pointer) + * has completed, before freeing the object. */ + drmSessionContexts[i].drmSession->PrepareForDestruction(); MW_SAFE_DELETE(drmSessionContexts[i].drmSession); drmSessionContexts[i] = DrmSessionContext(); } @@ -114,7 +121,7 @@ void DrmSessionManager::clearSessionData() std::lock_guard guard(cachedKeyMutex); if (cachedKeyIDs != NULL) { - cachedKeyIDs[i] = KeyID(); + cachedKeyIDs[i] = KeyIdEntries(); } } } @@ -144,13 +151,13 @@ void DrmSessionManager::clearFailedKeyIds() std::lock_guard guard(cachedKeyMutex); for(int i = 0 ; i < mMaxDRMSessions; i++) { - if(cachedKeyIDs[i].isFailedKeyId) + if(cachedKeyIDs[i].isFailedKeyEntries) { if(!cachedKeyIDs[i].data.empty()) { cachedKeyIDs[i].data.clear(); } - cachedKeyIDs[i].isFailedKeyId = false; + cachedKeyIDs[i].isFailedKeyEntries = false; cachedKeyIDs[i].creationTime = 0; } cachedKeyIDs[i].isPrimaryKeyId = false; @@ -179,7 +186,7 @@ bool DrmSessionManager::getFailedKeyIdStatus(int sessionIndex) std::lock_guard guard(cachedKeyMutex); if (sessionIndex >= 0 && sessionIndex < mMaxDRMSessions && cachedKeyIDs) { - rc = cachedKeyIDs[sessionIndex].isFailedKeyId; + rc = cachedKeyIDs[sessionIndex].isFailedKeyEntries; } return rc; } @@ -192,12 +199,14 @@ void DrmSessionManager::clearDrmSession(bool forceClearSession) for(int i = 0 ; i < mMaxDRMSessions; i++) { // Clear the session data if license key acquisition failed or if forceClearSession is true in the case of LicenseCaching is false. - if((cachedKeyIDs[i].isFailedKeyId || forceClearSession) && drmSessionContexts != NULL) + if((cachedKeyIDs[i].isFailedKeyEntries || forceClearSession) && drmSessionContexts != NULL) { std::lock_guard guard(drmSessionContexts[i].sessionMutex); if (drmSessionContexts[i].drmSession != NULL) { MW_LOG_WARN("DrmSessionManager:: Clearing failed Session Data Slot : %d", i); + /* DELIA-70726 fix: see clearSessionData() for rationale. */ + drmSessionContexts[i].drmSession->PrepareForDestruction(); MW_SAFE_DELETE(drmSessionContexts[i].drmSession); } } @@ -337,11 +346,17 @@ bool DrmSessionManager::IsKeyIdProcessed(std::vector keyIdArray, bool & for (int sessionSlot = 0; sessionSlot < mMaxDRMSessions; sessionSlot++) { auto keyIDSlot = cachedKeyIDs[sessionSlot].data; - if (!keyIDSlot.empty() && keyIDSlot.end() != std::find(keyIDSlot.begin(), keyIDSlot.end(), keyIdArray)) + // Check if keyId is already cached or marked as failed + auto it = std::find_if(keyIDSlot.begin(), keyIDSlot.end(), + [&](const KeyIdEntry &entry) + { + return entry.keyId == keyIdArray; + }); + if (it != keyIDSlot.end()) { std::string debugStr = PlayerLogManager::getHexDebugStr(keyIdArray); - MW_LOG_INFO("Session created/in progress with same keyID %s at slot %d", debugStr.c_str(), sessionSlot); - status = !cachedKeyIDs[sessionSlot].isFailedKeyId; + status = !it->isFailedKeyId; + MW_LOG_INFO("Session created/in progress with same keyID %s at slot %d, status=%d", debugStr.c_str(), sessionSlot, status); ret = true; break; } @@ -470,7 +485,7 @@ DrmSession* DrmSessionManager::createDrmSession(int &responseCode, int &err, std std::vector keyId; drmHelper->getKey(keyId); /* callback to initiate content protection data update */ - mCustomData = ContentUpdateCb(drmHelper, streamType, std::move(keyId), isContentProcess); + mCustomData = ContentUpdateCb(drmHelper, streamType, keyId, isContentProcess); if (code == KEY_READY) { return drmSessionContexts[selectedSlot].drmSession; @@ -488,7 +503,7 @@ DrmSession* DrmSessionManager::createDrmSession(int &responseCode, int &err, std std::lock_guard guard(cachedKeyMutex); if (cachedKeyIDs) { - cachedKeyIDs[selectedSlot].isFailedKeyId = true; + cachedKeyIDs[selectedSlot].isFailedKeyEntries = true; } return nullptr; } @@ -499,7 +514,7 @@ DrmSession* DrmSessionManager::createDrmSession(int &responseCode, int &err, std std::lock_guard guard(cachedKeyMutex); if (cachedKeyIDs) { - cachedKeyIDs[selectedSlot].isFailedKeyId = true; + cachedKeyIDs[selectedSlot].isFailedKeyEntries = true; } return nullptr; } @@ -510,11 +525,29 @@ DrmSession* DrmSessionManager::createDrmSession(int &responseCode, int &err, std std::lock_guard guard(cachedKeyMutex); if (cachedKeyIDs) { - cachedKeyIDs[selectedSlot].isFailedKeyId = true; + cachedKeyIDs[selectedSlot].isFailedKeyEntries = true; } return nullptr; } + bool isMultiKeySession = false; + { + std::lock_guard guard(cachedKeyMutex); + isMultiKeySession = (cachedKeyIDs[selectedSlot].data.size() > 1); + } + if (code == KEY_READY && isMultiKeySession) + { + // Multiple KeyIDs present for this session, validate them + if(!ValidateMultiKeySlot(keyId, selectedSlot)) + { + // Validation failed as current keyId is marked as failed + std::lock_guard guard(cachedKeyMutex); + cachedKeyIDs[selectedSlot].isFailedKeyEntries = true; + MW_LOG_WARN("ValidateMultiKeySlot failed for slot %d", selectedSlot); + return nullptr; + } + } + // License acquisition was done, so mContentSecurityManagerSession will be populated now const auto &localSession = mContentSecurityManagerSession; //Remove potential isSessionValid(), getSessionID() race by using a local copy if (localSession.isSessionValid()) @@ -526,6 +559,122 @@ DrmSession* DrmSessionManager::createDrmSession(int &responseCode, int &err, std return drmSessionContexts[selectedSlot].drmSession; } +/** + * @fn Validate multiple key IDs for a given DRM session slot using the usable keys from the DrmSession + * + * @param[in] keyId The key ID to validate + * @param[in] selectedSlot The DRM session slot to validate + * + * @return true if validation is successful, false otherwise + */ +bool DrmSessionManager::ValidateMultiKeySlot(const std::vector &keyId, int selectedSlot) +{ + if (selectedSlot < 0 || selectedSlot >= mMaxDRMSessions) + { + MW_LOG_ERR("invalid slot %d", selectedSlot); + return false; + } + + MW_LOG_INFO("Multiple KeyIDs present for the session at slot %d, validating each", selectedSlot); + + // Acquire usable keys from the DrmSession (under its mutex) + std::vector> usableKeyIds; + { + std::lock_guard sessionGuard(drmSessionContexts[selectedSlot].sessionMutex); + if (drmSessionContexts[selectedSlot].drmSession) + { + usableKeyIds = drmSessionContexts[selectedSlot].drmSession->getUsableKeys(); + } + else + { + MW_LOG_ERR("No drmSession present at slot %d", selectedSlot); + return false; + } + } + + // Normalize key to binary format (handles binary, hex string with/without dashes) + auto normalizeKeyToBinary = [](const std::vector &key) -> std::vector + { + // Check if key is already binary or if it's a hex string + bool isHexString = true; + for (auto c : key) + { + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || (c == '-'))) + { + isHexString = false; + break; + } + } + + // If not hex string, assume it's already binary + if (!isHexString) + { + return key; + } + + // Remove dashes (for UUID format: "abcdef1-2345-6789-abcd-ef01234567") + std::string hexStr; + hexStr.reserve(key.size()); + for (auto c : key) + { + if (c != '-') + { + hexStr.push_back(static_cast(c)); + } + } + + // Convert hex string to binary + std::vector binary; + for (size_t i = 0; i + 1 < hexStr.length(); i += 2) + { + std::string byteStr = hexStr.substr(i, 2); + uint8_t byte = static_cast(std::stoul(byteStr, nullptr, 16)); + binary.push_back(byte); + } + return binary; + }; + + auto keysMatch = [&](const std::vector &cachedKey, const std::vector &usableKey) -> bool + { + MW_LOG_DEBUG("Comparing cachedKey: %s with usableKey: %s", + PlayerLogManager::getHexDebugStr(cachedKey).c_str(), + PlayerLogManager::getHexDebugStr(usableKey).c_str()); + + return normalizeKeyToBinary(cachedKey) == normalizeKeyToBinary(usableKey); + }; + + // Update cachedKeyIDs under its mutex + std::lock_guard cacheGuard(cachedKeyMutex); + for (auto &keyIdEntry : cachedKeyIDs[selectedSlot].data) + { + std::string debugStr = PlayerLogManager::getHexDebugStr(keyIdEntry.keyId); + + auto it = std::find_if( + usableKeyIds.begin(), + usableKeyIds.end(), + [&](const std::vector &usableKey) + { + return keysMatch(keyIdEntry.keyId, usableKey); + }); + + if (it == usableKeyIds.end()) + { + MW_LOG_WARN("KeyID %s not found in usable keys, marking as failed for session at slot %d", debugStr.c_str(), selectedSlot); + keyIdEntry.isFailedKeyId = true; + if (keysMatch(keyId, keyIdEntry.keyId)) + { + MW_LOG_ERR("KeyID %s matches primary key for session at slot %d, Exiting", debugStr.c_str(), selectedSlot); + return false; + } + } + else + { + MW_LOG_INFO("KeyID %s is usable for session at slot %d", debugStr.c_str(), selectedSlot); + } + } + return true; +} + /** * @brief Create a DRM Session using the Drm Helper * Determine a slot in the drmSession Contexts which can be used @@ -569,7 +718,13 @@ KeyState DrmSessionManager::getDrmSession(int &err, std::shared_ptr d for (; sessionSlot < mMaxDRMSessions; sessionSlot++) { auto keyIDSlot = cachedKeyIDs[sessionSlot].data; - if (!keyIDSlot.empty() && keyIDSlot.end() != std::find(keyIDSlot.begin(), keyIDSlot.end(), keyIdArray)) + // Check if keyId is already cached + auto it = std::find_if(keyIDSlot.begin(), keyIDSlot.end(), + [&](const KeyIdEntry &entry) + { + return entry.keyId == keyIdArray; + }); + if (it != keyIDSlot.end()) { MW_LOG_INFO("Session created/in progress with same keyID %s at slot %d", keyIdDebugStr.c_str(), sessionSlot); keySlotFound = true; @@ -614,7 +769,7 @@ KeyState DrmSessionManager::getDrmSession(int &err, std::shared_ptr d else { // Already same session Slot is marked failed , not to proceed again . - if(cachedKeyIDs[sessionSlot].isFailedKeyId) + if(cachedKeyIDs[sessionSlot].isFailedKeyEntries) { MW_LOG_WARN(" Found FailedKeyId at sesssionSlot :%d, return key error",sessionSlot); return KEY_ERROR; @@ -629,14 +784,16 @@ KeyState DrmSessionManager::getDrmSession(int &err, std::shared_ptr d cachedKeyIDs[sessionSlot].data.clear(); } - cachedKeyIDs[sessionSlot].isFailedKeyId = false; + cachedKeyIDs[sessionSlot].isFailedKeyEntries = false; - std::vector> data; + std::vector data; for(auto& keyId : keyIdArrays) { + KeyIdEntry keyIdEntry; + keyIdEntry.keyId = keyId.second; std::string debugStr = PlayerLogManager::getHexDebugStr(keyId.second); MW_LOG_INFO("Insert[%d] - slot:%d keyID %s", keyId.first, sessionSlot, debugStr.c_str()); - data.push_back(keyId.second); + data.push_back(std::move(keyIdEntry)); } cachedKeyIDs[sessionSlot].data = data; @@ -654,55 +811,82 @@ KeyState DrmSessionManager::getDrmSession(int &err, std::shared_ptr d { MW_LOG_WARN("changing DRM session for %s to %s", drmSessionContexts[sessionSlot].drmSession->getKeySystem().c_str(), drmHelper->ocdmSystemId().c_str()); } - else if (cachedKeyIDs[sessionSlot].data.end() != std::find(cachedKeyIDs[sessionSlot].data.begin(), cachedKeyIDs[sessionSlot].data.end() ,drmSessionContexts[sessionSlot].data)) + else { - KeyState existingState = drmSessionContexts[sessionSlot].drmSession->getState(); - if (existingState == KEY_READY) - { - MW_LOG_INFO("Found drm session READY with same keyID %s - Reusing drm session", keyIdDebugStr.c_str()); - auto slotSession = drmSessionContexts[sessionSlot].drmSession->getSecManagerSession(); - if (slotSession.isSessionValid() && (!mContentSecurityManagerSession.isSessionValid()) ) - { - // Set the drmSession's ID as mContentSecurityManagerSession so that this code will not be repeated for multiple calls for createDrmSession - mContentSecurityManagerSession = slotSession; - bool videoMuteState = mIsVideoOnMute.load(); - MW_LOG_WARN("Activating re-used DRM, sessionId[%" PRId64 "], with video mute = %d", slotSession.getSessionID(), videoMuteState); - ContentSecurityManager::GetInstance()->UpdateSessionState(slotSession.getSessionID(), true); - } - return KEY_READY; - } - if (existingState == KEY_INIT) + bool isKeyIdFound = false; { - MW_LOG_WARN("Found drm session in INIT state with same keyID %s - Reusing drm session", keyIdDebugStr.c_str()); - return KEY_INIT; + // Protect access to cachedKeyIDs + std::lock_guard guard(cachedKeyMutex); + auto &keyIDSlot = cachedKeyIDs[sessionSlot].data; + const auto &keyIdToFind = drmSessionContexts[sessionSlot].data; + + auto it = std::find_if( + keyIDSlot.begin(), + keyIDSlot.end(), + [&](const KeyIdEntry &entry) + { + return entry.keyId == keyIdToFind; + }); + isKeyIdFound = (it != keyIDSlot.end()); } - else if (existingState <= KEY_READY) + + if (isKeyIdFound) { - if (drmSessionContexts[sessionSlot].drmSession->waitForState(KEY_READY, drmHelper->keyProcessTimeout())) + KeyState existingState = drmSessionContexts[sessionSlot].drmSession->getState(); + if (existingState == KEY_READY) { - MW_LOG_WARN("Waited for drm session READY with same keyID %s - Reusing drm session", keyIdDebugStr.c_str()); + MW_LOG_INFO("Found drm session READY with same keyID %s - Reusing drm session", keyIdDebugStr.c_str()); + auto slotSession = drmSessionContexts[sessionSlot].drmSession->getSecManagerSession(); + if (slotSession.isSessionValid() && (!mContentSecurityManagerSession.isSessionValid())) + { + // Set the drmSession's ID as mContentSecurityManagerSession so that this code will not be repeated for multiple calls for createDrmSession + mContentSecurityManagerSession = slotSession; + bool videoMuteState = mIsVideoOnMute.load(); + MW_LOG_WARN("Activating re-used DRM, sessionId[%" PRId64 "], with video mute = %d", slotSession.getSessionID(), videoMuteState); + ContentSecurityManager::GetInstance()->UpdateSessionState(slotSession.getSessionID(), true); + } return KEY_READY; } - MW_LOG_WARN("key was never ready for %s ", drmSessionContexts[sessionSlot].drmSession->getKeySystem().c_str()); - //CID-164094 : Added the mutex lock due to overriding the isFailedKeyId variable - std::lock_guard guard(cachedKeyMutex); - cachedKeyIDs[selectedSlot].isFailedKeyId = true; - return KEY_ERROR; + if (existingState == KEY_INIT) + { + MW_LOG_WARN("Found drm session in INIT state with same keyID %s - Reusing drm session", keyIdDebugStr.c_str()); + return KEY_INIT; + } + else if (existingState <= KEY_READY) + { + if (drmSessionContexts[sessionSlot].drmSession->waitForState(KEY_READY, drmHelper->keyProcessTimeout())) + { + MW_LOG_WARN("Waited for drm session READY with same keyID %s - Reusing drm session", keyIdDebugStr.c_str()); + return KEY_READY; + } + MW_LOG_WARN("key was never ready for %s ", drmSessionContexts[sessionSlot].drmSession->getKeySystem().c_str()); + // CID-164094 : Added the mutex lock due to overriding the isFailedKeyEntries variable + std::lock_guard guard(cachedKeyMutex); + cachedKeyIDs[selectedSlot].isFailedKeyEntries = true; + return KEY_ERROR; + } + else + { + MW_LOG_WARN("existing DRM session for %s has error state %d", drmSessionContexts[sessionSlot].drmSession->getKeySystem().c_str(), existingState); + // CID-164094 : Added the mutex lock due to overriding the isFailedKeyEntries variable + std::lock_guard guard(cachedKeyMutex); + cachedKeyIDs[selectedSlot].isFailedKeyEntries = true; + return KEY_ERROR; + } } else { - MW_LOG_WARN("existing DRM session for %s has error state %d", drmSessionContexts[sessionSlot].drmSession->getKeySystem().c_str(), existingState); - //CID-164094 : Added the mutex lock due to overriding the isFailedKeyId variable - std::lock_guard guard(cachedKeyMutex); - cachedKeyIDs[selectedSlot].isFailedKeyId = true; - return KEY_ERROR; + MW_LOG_WARN("existing DRM session for %s has different key in slot %d", drmSessionContexts[sessionSlot].drmSession->getKeySystem().c_str(), sessionSlot); } } - else - { - MW_LOG_WARN("existing DRM session for %s has different key in slot %d", drmSessionContexts[sessionSlot].drmSession->getKeySystem().c_str(), sessionSlot); - } MW_LOG_WARN("deleting existing DRM session for %s ", drmSessionContexts[sessionSlot].drmSession->getKeySystem().c_str()); + /* DELIA-70726 fix: this slot may still be referenced by a GStreamer + * decryptor element of a previous, not-yet-fully-torn-down pipeline + * (rapid/back-to-back channel change). Block here until any decrypt() + * call already in flight against this session finishes, so the delete + * below cannot race with OCDMSessionAdapter::verifyOutputProtection()/ + * decrypt() running on the old pipeline's multiqueue thread. */ + drmSessionContexts[sessionSlot].drmSession->PrepareForDestruction(); MW_SAFE_DELETE(drmSessionContexts[sessionSlot].drmSession); } this->ProfileUpdateCb(); @@ -800,7 +984,7 @@ void DrmSessionManager::UpdateMaxDRMSessions(int maxSessions) //Update to new session count mMaxDRMSessions = maxSessions; drmSessionContexts = new DrmSessionContext[mMaxDRMSessions]; - cachedKeyIDs = new KeyID[mMaxDRMSessions]; + cachedKeyIDs = new KeyIdEntries[mMaxDRMSessions]; MW_LOG_INFO("Updated DrmSessionManager MaxSession to:%d", mMaxDRMSessions); } } diff --git a/middleware/drm/DrmSessionManager.h b/middleware/drm/DrmSessionManager.h index 0bd8a71714..acf7027983 100644 --- a/middleware/drm/DrmSessionManager.h +++ b/middleware/drm/DrmSessionManager.h @@ -70,19 +70,24 @@ struct DrmSessionContext } }; -/** - * @struct KeyID - * @brief Structure to hold, keyId and session creation time for - * keyId - */ -struct KeyID +struct KeyIdEntry { - std::vector> data; - long long creationTime; + std::vector keyId; bool isFailedKeyId; + + KeyIdEntry() : keyId(), isFailedKeyId(false) + { + } +}; + +struct KeyIdEntries +{ + std::vector data; + long long creationTime; bool isPrimaryKeyId; + bool isFailedKeyEntries; - KeyID(); + KeyIdEntries(); }; /** @@ -133,7 +138,7 @@ class DrmSessionManager std::atomic mIsVideoOnMute; std::atomic mCurrentSpeed; private: - KeyID *cachedKeyIDs; + KeyIdEntries *cachedKeyIDs; char* accessToken; int accessTokenLen; SessionMgrState sessionMgrState; @@ -374,6 +379,14 @@ class DrmSessionManager * @return index to the session slot for selected drmSessionContext */ int getSlotIdForSession(DrmSession* session); + /** + * @fn ValidateMultiKeySlot + * @brief Validate multiple key IDs for a given DRM session slot + * @param[in] keyId The key ID to validate + * @param[in] selectedSlot The DRM session slot to validate + * @return true if validation is successful, false otherwise + */ + bool ValidateMultiKeySlot(const std::vector &keyId, int selectedSlot); /** * @fn releaseLicenseRenewalThreads */ diff --git a/middleware/gst-plugins/drm/gst/gstcdmidecryptor.cpp b/middleware/gst-plugins/drm/gst/gstcdmidecryptor.cpp index 10add97a62..829ba720fb 100755 --- a/middleware/gst-plugins/drm/gst/gstcdmidecryptor.cpp +++ b/middleware/gst-plugins/drm/gst/gstcdmidecryptor.cpp @@ -534,12 +534,19 @@ static GstFlowReturn gst_cdmidecryptor_transform_ip( { // call decrypt even for clear samples in order to copy it to a secure buffer. If secure buffers are not supported // decrypt() call will return without doing anything - if (cdmidecryptor->drmSession != NULL && cdmidecryptor->sinkCaps != NULL) - errorCode = cdmidecryptor->drmSession->decrypt(keyIDBuffer, ivBuffer, buffer, subSampleCount, subsamplesBuffer, cdmidecryptor->sinkCaps); + /* DELIA-70726 fix: guard against the DrmSession being concurrently torn down + * (e.g. DrmSessionManager reusing/evicting the slot during a back-to-back + * channel change) while this pipeline still has buffers in flight. */ + if (cdmidecryptor->drmSession != NULL && cdmidecryptor->sinkCaps != NULL + && cdmidecryptor->drmSession->AcquireForUse()) + { + errorCode = cdmidecryptor->drmSession->decrypt(keyIDBuffer, ivBuffer, buffer, subSampleCount, subsamplesBuffer, cdmidecryptor->sinkCaps); + cdmidecryptor->drmSession->ReleaseAfterUse(); + } else - { /* If drmSession creation failed, then the call will be aborted here */ + { /* If drmSession creation failed, or is being destroyed, the call will be aborted here */ result = GST_FLOW_NOT_SUPPORTED; - GST_ERROR_OBJECT(cdmidecryptor, "drmSession or sinkCaps is **** NULL ****, returning GST_FLOW_NOT_SUPPORTED"); + GST_ERROR_OBJECT(cdmidecryptor, "drmSession or sinkCaps is **** NULL **** (or session is being destroyed), returning GST_FLOW_NOT_SUPPORTED"); } } goto free_resources; @@ -656,7 +663,20 @@ static GstFlowReturn gst_cdmidecryptor_transform_ip( result = GST_FLOW_NOT_SUPPORTED; goto free_resources; } + /* DELIA-70726 fix: guard against the DrmSession being concurrently torn down + * (e.g. DrmSessionManager reusing/evicting the slot during a back-to-back + * channel change) while this pipeline still has buffers in flight. Without + * this guard, the multiqueue/decryptor thread can call decrypt() on a + * DrmSession that is being (or has already been) freed, causing a + * use-after-free SIGSEGV inside OCDMSessionAdapter::verifyOutputProtection(). */ + if (!cdmidecryptor->drmSession->AcquireForUse()) + { + GST_ERROR_OBJECT(cdmidecryptor, "drmSession is being destroyed, aborting decrypt"); + result = GST_FLOW_NOT_SUPPORTED; + goto free_resources; + } errorCode = cdmidecryptor->drmSession->decrypt(keyIDBuffer, ivBuffer, buffer, subSampleCount, subsamplesBuffer, cdmidecryptor->sinkCaps); + cdmidecryptor->drmSession->ReleaseAfterUse(); cdmidecryptor->streamEncrypted = true; if (errorCode != 0 || cdmidecryptor->hdcpOpProtectionFailCount)