Check for open session when initializing usage table.

[ Merge of http://go/wvgerrit/122984 ]

There was an issue encountered by some vendors with how the usage
table was initialized on some devices.  Previously, the CDM would
open an OEMCrypto session first, then initialize the usage table
(loading existing or creating a new one).  On these devices,
OEMCrypto_CreateUsageTableHeader() and OEMCrypto_LoadUsageTableHeader()
would fail if there were any open sessions.

This CL changes the initialization process to create/load the usage
table before opening an OEMCrypto session.

This change also lays the ground work for another usage table fix
to address GTS tests failure.

In the process, several of the functions for the usage table have been
split up into smaller chunks of code.  This required additional changes
to the usage table unittest to keep them up to date.

Bug: 169195093
Bug: 180639135
Test: Linux unittests and MediaDrmTest
Change-Id: Ifbf35f5d8cff5b89fea9b16edb998c84803f4fbe
This commit is contained in:
Alex Dale
2021-04-22 17:08:33 -07:00
parent e233e68de1
commit 023b06eded
5 changed files with 672 additions and 481 deletions

View File

@@ -51,8 +51,9 @@
}
namespace wvcdm {
namespace {
using UsageTableLock = std::unique_lock<std::recursive_mutex>;
constexpr size_t KiB = 1024;
constexpr size_t MiB = 1024 * 1024;
@@ -166,20 +167,22 @@ size_t GenericEncryptionBlockSize(CdmEncryptionAlgorithm algorithm) {
}
} // namespace
// CryptoSession variables allocation.
shared_mutex CryptoSession::static_field_mutex_;
shared_mutex CryptoSession::oem_crypto_mutex_;
bool CryptoSession::initialized_ = false;
int CryptoSession::session_count_ = 0;
int CryptoSession::termination_counter_ = 0;
UsageTableHeader* CryptoSession::usage_table_header_l1_ = nullptr;
UsageTableHeader* CryptoSession::usage_table_header_l3_ = nullptr;
std::unique_ptr<UsageTableHeader> CryptoSession::usage_table_header_l1_;
std::unique_ptr<UsageTableHeader> CryptoSession::usage_table_header_l3_;
std::recursive_mutex CryptoSession::usage_table_mutex_;
std::atomic<uint64_t> CryptoSession::request_id_index_source_(0);
size_t GetOffset(std::string message, std::string field) {
size_t pos = message.find(field);
if (pos == std::string::npos) {
LOGE("Cannot find the |field| offset in message: field = %s",
field.c_str());
IdToString(field));
pos = 0;
}
return pos;
@@ -195,7 +198,7 @@ OEMCrypto_Substring GetSubstring(const std::string& message,
size_t pos = message.find(field);
if (pos == std::string::npos) {
LOGW("Cannot find the |field| substring in message: field = %s",
field.c_str());
IdToString(field));
substring.offset = 0;
substring.length = 0;
} else {
@@ -359,7 +362,7 @@ void CryptoSession::Init() {
bool CryptoSession::TryTerminate() {
LOGV("Terminating crypto session");
WithStaticFieldWriteLock("TryTerminate", [&] {
const bool terminated = WithStaticFieldWriteLock("TryTerminate", [&] {
LOGV(
"Terminating crypto session: initialized_ = %s, session_count_ = %d, "
"termination_counter_ = %d",
@@ -371,25 +374,20 @@ bool CryptoSession::TryTerminate() {
if (session_count_ > 0 || termination_counter_ > 0 || !initialized_)
return false;
OEMCryptoResult sts;
WithOecWriteLock("Terminate", [&] { sts = OEMCrypto_Terminate(); });
const OEMCryptoResult sts =
WithOecWriteLock("Terminate", [&] { return OEMCrypto_Terminate(); });
if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Terminate failed: status = %d", static_cast<int>(sts));
}
if (usage_table_header_l1_ != nullptr) {
delete usage_table_header_l1_;
usage_table_header_l1_ = nullptr;
}
if (usage_table_header_l3_ != nullptr) {
delete usage_table_header_l3_;
usage_table_header_l3_ = nullptr;
}
initialized_ = false;
return true;
});
return true;
if (terminated) {
UsageTableLock lock(usage_table_mutex_);
usage_table_header_l1_.reset();
usage_table_header_l3_.reset();
}
return terminated;
}
void CryptoSession::DisableDelayedTermination() {
@@ -398,7 +396,63 @@ void CryptoSession::DisableDelayedTermination() {
[&] { termination_counter_ = 0; });
}
bool CryptoSession::SetUpUsageTableHeader(
SecurityLevel requested_security_level) {
if (usage_table_header_ != nullptr) {
LOGE("Usage table is already set up for the current crypto session");
return false;
}
const CdmSecurityLevel security_level =
GetSecurityLevel(requested_security_level);
if (security_level != kSecurityLevelL1 &&
security_level != kSecurityLevelL3) {
LOGD("Unsupported security level for usage support: security_level = %d",
static_cast<int>(security_level));
return false;
}
// Check if usage support is available.
bool supports_usage_table = false;
if (!HasUsageInfoSupport(requested_security_level, &supports_usage_table)) {
metrics_->oemcrypto_usage_table_support_.SetError(
USAGE_INFORMATION_SUPPORT_FAILED);
return false;
}
metrics_->oemcrypto_usage_table_support_.Record(
supports_usage_table ? kUsageEntrySupport : kNonSecureUsageSupport);
if (!supports_usage_table) {
return false;
}
LOGV("Usage table lock: SetUpUsageTableHeader()");
UsageTableLock auto_lock(usage_table_mutex_);
// TODO(b/141350978): Prevent any recursive logic.
// Manipulate only the usage table for the requested security level.
std::unique_ptr<UsageTableHeader>& header = security_level == kSecurityLevelL1
? usage_table_header_l1_
: usage_table_header_l3_;
if (!header) {
// This may be called twice within the same thread when the table
// is initialized. On the second call |header| will not be null,
// causing this block to be skipped.
header.reset(new UsageTableHeader());
if (!header->Init(security_level, this)) {
LOGE("Failed to initialize and sync usage usage table");
// Must be cleared globally to prevent the next session to be
// opened from using the invalid UsageTableHeader.
header.reset();
return false;
}
}
usage_table_header_ = header.get();
metrics_->usage_table_header_initial_size_.Record(
usage_table_header_->size());
return true;
}
CdmResponseType CryptoSession::GetTokenFromKeybox(std::string* token) {
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(token, PARAMETER_NULL);
std::string temp_buffer(KEYBOX_KEY_DATA_SIZE, '\0');
@@ -422,6 +476,7 @@ CdmResponseType CryptoSession::GetTokenFromKeybox(std::string* token) {
}
CdmResponseType CryptoSession::GetTokenFromOemCert(std::string* token) {
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
RETURN_IF_NULL(token, PARAMETER_NULL);
OEMCryptoResult status;
@@ -752,9 +807,16 @@ uint8_t CryptoSession::GetSecurityPatchLevel() {
CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
LOGD("Opening crypto session: requested_security_level = %s",
SecurityLevelToString(requested_security_level));
RETURN_IF_UNINITIALIZED(UNKNOWN_ERROR);
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
if (open_) return NO_ERROR;
if (!SetUpUsageTableHeader(requested_security_level)) {
// Ignore errors since we do not know when a session is opened,
// if it is intended to be used for offline/usage session related
// or otherwise.
LOGW("Session opened without a usage table");
}
CdmResponseType result = GetProvisioningMethod(requested_security_level,
&pre_provision_token_type_);
if (result != NO_ERROR) return result;
@@ -817,70 +879,10 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
key_session_.reset(new ContentKeySession(oec_session_id_, metrics_));
});
// Set up Usage Table support
//
// This MUST be the last thing in the function because it contains the
// successful return paths of the function. It will attempt to initialize
// usage tables. However, whether they are employed is determined by
// information in the license, which will not be received until later. In case
// usage tables are not even needed, initialization errors here are ignored by
// returning successfully.
//
// TODO(b/141350978): Refactor this code so that it does not have this
// problem.
if (!GetApiVersion(&api_version_)) {
LOGE("Failed to get API version");
return USAGE_SUPPORT_GET_API_FAILED;
}
bool supports_usage_table = false;
if (HasUsageInfoSupport(&supports_usage_table)) {
metrics_->oemcrypto_usage_table_support_.Record(
supports_usage_table ? kUsageEntrySupport : kNonSecureUsageSupport);
if (supports_usage_table) {
CdmSecurityLevel security_level = GetSecurityLevel();
if (security_level == kSecurityLevelL1 ||
security_level == kSecurityLevelL3) {
// This block cannot use |WithStaticFieldWriteLock| because it needs
// to unlock the lock partway through.
LOGV("Static field write lock: Open() initializing usage table");
std::unique_lock<shared_mutex> auto_lock(static_field_mutex_);
UsageTableHeader** header = security_level == kSecurityLevelL1
? &usage_table_header_l1_
: &usage_table_header_l3_;
if (*header == nullptr) {
*header = new UsageTableHeader();
// Ignore errors since we do not know when a session is opened,
// if it is intended to be used for offline/usage session related
// or otherwise.
auto_lock.unlock();
bool is_usage_table_header_inited =
(*header)->Init(security_level, this);
auto_lock.lock();
if (!is_usage_table_header_inited) {
delete *header;
*header = nullptr;
usage_table_header_ = nullptr;
return NO_ERROR;
}
}
usage_table_header_ = *header;
metrics_->usage_table_header_initial_size_.Record((*header)->size());
} // End |static_field_mutex_| block.
}
} else {
metrics_->oemcrypto_usage_table_support_.SetError(
USAGE_INFORMATION_SUPPORT_FAILED);
}
// Do not add logic after this point as it may never get exercised. In the
// event of errors initializing the usage tables, the code will return early,
// never reaching this point. See the comment above the "Set up Usage Table
// support" section for details.
//
// TODO(b/139973602): Refactor this code.
return NO_ERROR;
}
@@ -889,13 +891,13 @@ void CryptoSession::Close() {
open_ ? "true" : "false");
if (!open_) return;
OEMCryptoResult close_sts;
WithOecWriteLock(
"Close", [&] { close_sts = OEMCrypto_CloseSession(oec_session_id_); });
const OEMCryptoResult close_sts = WithOecWriteLock(
"Close", [&] { return OEMCrypto_CloseSession(oec_session_id_); });
metrics_->oemcrypto_close_session_.Increment(close_sts);
// Clear cached values.
has_usage_info_support_ = kBooleanUnset;
oem_token_.clear();
if (close_sts != OEMCrypto_SUCCESS) {
LOGW("OEMCrypto_CloseSession failed: status = %d",
@@ -905,6 +907,7 @@ void CryptoSession::Close() {
case OEMCrypto_SUCCESS:
case OEMCrypto_ERROR_INVALID_SESSION:
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
usage_table_header_ = nullptr;
open_ = false;
break;
case OEMCrypto_ERROR_CLOSE_SESSION_FAILED:
@@ -2488,14 +2491,13 @@ CdmResponseType CryptoSession::LoadUsageTableHeader(
LOGV("Loading usage table header: requested_security_level = %s",
SecurityLevelToString(requested_security_level));
OEMCryptoResult result;
WithOecWriteLock("LoadUsageTableHeader", [&] {
result = OEMCrypto_LoadUsageTableHeader(
const OEMCryptoResult result = WithOecWriteLock("LoadUsageTableHeader", [&] {
return OEMCrypto_LoadUsageTableHeader(
requested_security_level,
reinterpret_cast<const uint8_t*>(usage_table_header.data()),
usage_table_header.size());
metrics_->oemcrypto_load_usage_table_header_.Increment(result);
});
metrics_->oemcrypto_load_usage_table_header_.Increment(result);
if (result != OEMCrypto_SUCCESS) {
if (result == OEMCrypto_WARNING_GENERATION_SKEW) {