Recover when stored information is corrupted
[ Merge of http://go/wvgerrit/52040 ] Information stored in files are serialized and protected by an MD5 hash. When files cannot be read because the MD5 hash computed over it fails verification, the file is deleted. This allows for recovery. However if the protobuf deserialization fails we return an error but do not delete the file. When errors of this sort occur with usage information files, the CDM cannot recover. removeAllSecureStops() will fail as well and new licenses with PSTs cannot be processed. In order to recover the file will be deleted when a protobuf deserialization error occurs. Bug: 109765590 Test: WV unit, integration tests. GTS tests. Netflix and Play Movies playback Change-Id: I408914924e644d5c22b2ba7865d3a7d598788ee6
This commit is contained in:
@@ -1203,6 +1203,9 @@ bool DeviceFiles::RetrieveHashedFile(
|
||||
|
||||
if (bytes != static_cast<ssize_t>(serialized_hash_file.size())) {
|
||||
LOGW("DeviceFiles::RetrieveHashedFile: read failed");
|
||||
// Remove the corrupted file so the caller will not get the same error
|
||||
// when trying to access the file repeatedly, causing the system to stall.
|
||||
file_system_->Remove(path);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1212,6 +1215,9 @@ bool DeviceFiles::RetrieveHashedFile(
|
||||
HashedFile hash_file;
|
||||
if (!hash_file.ParseFromString(serialized_hash_file)) {
|
||||
LOGW("DeviceFiles::RetrieveHashedFile: Unable to parse hash file");
|
||||
// Remove the corrupted file so the caller will not get the same error
|
||||
// when trying to access the file repeatedly, causing the system to stall.
|
||||
file_system_->Remove(path);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1231,6 +1237,9 @@ bool DeviceFiles::RetrieveHashedFile(
|
||||
|
||||
if (!deserialized_file->ParseFromString(hash_file.file())) {
|
||||
LOGW("DeviceFiles::RetrieveHashedFile: Unable to parse file");
|
||||
// Remove the corrupted file so the caller will not get the same error
|
||||
// when trying to access the file repeatedly, causing the system to stall.
|
||||
file_system_->Remove(path);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "cdm_identifier.h"
|
||||
#include "config_test_env.h"
|
||||
#include "device_files.h"
|
||||
#include "device_files.pb.h"
|
||||
#include "file_store.h"
|
||||
#include "file_utils.h"
|
||||
#include "license_protocol.pb.h"
|
||||
@@ -3419,6 +3420,170 @@ TEST_F(WvCdmRequestLicenseTest, RemoveCorruptedUsageInfoTest) {
|
||||
EXPECT_TRUE(usage_info.empty());
|
||||
}
|
||||
|
||||
TEST_F(WvCdmRequestLicenseTest, RemoveCorruptedUsageInfoTest2) {
|
||||
Unprovision();
|
||||
|
||||
std::string app_id_empty = "";
|
||||
std::string app_id_not_empty = "not empty";
|
||||
|
||||
TestWvCdmClientPropertySet property_set;
|
||||
Provision(kLevelDefault);
|
||||
|
||||
CdmSecurityLevel security_level = GetDefaultSecurityLevel();
|
||||
FileSystem file_system;
|
||||
DeviceFiles handle(&file_system);
|
||||
EXPECT_TRUE(handle.Init(security_level));
|
||||
std::vector<std::string> psts;
|
||||
EXPECT_TRUE(handle.DeleteAllUsageInfoForApp(
|
||||
DeviceFiles::GetUsageInfoFileName(""), &psts));
|
||||
|
||||
for (size_t i = 0; i < N_ELEM(usage_info_sub_samples_icp); ++i) {
|
||||
SubSampleInfo* data = usage_info_sub_samples_icp + i;
|
||||
property_set.set_app_id(i % 2 == 0 ? app_id_empty : app_id_not_empty);
|
||||
decryptor_.OpenSession(g_key_system, &property_set, kDefaultCdmIdentifier,
|
||||
NULL, &session_id_);
|
||||
std::string key_id = a2bs_hex(
|
||||
"000000427073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id
|
||||
"08011a0d7769646576696e655f74657374220f73" // pssh data
|
||||
"747265616d696e675f636c6970");
|
||||
|
||||
char ch = 0x33 + i;
|
||||
key_id.append(1, ch);
|
||||
|
||||
GenerateKeyRequest(key_id, kLicenseTypeStreaming, &property_set);
|
||||
|
||||
// TODO(rfrias): streaming_clip6 is a streaming license without a pst
|
||||
if (ch == '6')
|
||||
VerifyKeyRequestResponse(g_license_server, g_client_auth, false);
|
||||
else
|
||||
VerifyUsageKeyRequestResponse(g_license_server, g_client_auth);
|
||||
|
||||
std::vector<uint8_t> decrypt_buffer(data->encrypt_data.size());
|
||||
CdmDecryptionParameters decryption_parameters(
|
||||
&data->key_id, &data->encrypt_data.front(), data->encrypt_data.size(),
|
||||
&data->iv, data->block_offset, &decrypt_buffer[0]);
|
||||
decryption_parameters.is_encrypted = data->is_encrypted;
|
||||
decryption_parameters.is_secure = data->is_secure;
|
||||
decryption_parameters.subsample_flags = data->subsample_flags;
|
||||
EXPECT_EQ(NO_ERROR, decryptor_.Decrypt(session_id_, data->validate_key_id,
|
||||
decryption_parameters));
|
||||
|
||||
EXPECT_TRUE(std::equal(data->decrypt_data.begin(), data->decrypt_data.end(),
|
||||
decrypt_buffer.begin()));
|
||||
decryptor_.CloseSession(session_id_);
|
||||
}
|
||||
|
||||
CdmUsageInfo usage_info;
|
||||
EXPECT_EQ(
|
||||
KEY_MESSAGE,
|
||||
decryptor_.GetUsageInfo(app_id_empty, kDefaultCdmIdentifier,
|
||||
&usage_info));
|
||||
EXPECT_TRUE(usage_info.size() > 0);
|
||||
EXPECT_EQ(
|
||||
KEY_MESSAGE,
|
||||
decryptor_.GetUsageInfo(app_id_not_empty, kDefaultCdmIdentifier,
|
||||
&usage_info));
|
||||
EXPECT_TRUE(usage_info.size() > 0);
|
||||
|
||||
// Read in usage info file
|
||||
std::string path;
|
||||
EXPECT_TRUE(Properties::GetDeviceFilesBasePath(security_level, &path));
|
||||
std::string usage_info_not_empty_app_id_file_name =
|
||||
path + DeviceFiles::GetUsageInfoFileName(app_id_not_empty);
|
||||
|
||||
ssize_t file_size =
|
||||
file_system.FileSize(usage_info_not_empty_app_id_file_name);
|
||||
EXPECT_LT(4, file_size);
|
||||
File* file =
|
||||
file_system.Open(usage_info_not_empty_app_id_file_name,
|
||||
FileSystem::kReadOnly);
|
||||
EXPECT_TRUE(NULL != file);
|
||||
std::string file_data;
|
||||
file_data.resize(file_size);
|
||||
ssize_t bytes = file->Read(&file_data[0], file_data.size());
|
||||
EXPECT_EQ(file_size, bytes);
|
||||
file->Close();
|
||||
|
||||
video_widevine_client::sdk::HashedFile hash_file;
|
||||
|
||||
EXPECT_TRUE(hash_file.ParseFromString(file_data));
|
||||
size_t pos = file_data.find(hash_file.hash());
|
||||
EXPECT_NE(std::string::npos, pos);
|
||||
EXPECT_NE(0u, pos);
|
||||
|
||||
// Corrupt the protobuf key field of the hash of the usage info file
|
||||
// with non-empty app id and write it back out
|
||||
file_data[pos-1] = 0xff;
|
||||
|
||||
file = file_system.Open(usage_info_not_empty_app_id_file_name,
|
||||
FileSystem::kCreate | FileSystem::kTruncate);
|
||||
EXPECT_TRUE(NULL != file);
|
||||
bytes = file->Write(file_data.data(), file_data.size());
|
||||
EXPECT_EQ(file_size, bytes);
|
||||
file->Close();
|
||||
|
||||
EXPECT_EQ(
|
||||
NO_ERROR,
|
||||
decryptor_.RemoveAllUsageInfo(app_id_not_empty, kDefaultCdmIdentifier));
|
||||
|
||||
EXPECT_EQ(
|
||||
NO_ERROR,
|
||||
decryptor_.GetUsageInfo(app_id_not_empty, kDefaultCdmIdentifier,
|
||||
&usage_info));
|
||||
EXPECT_TRUE(usage_info.empty());
|
||||
EXPECT_EQ(
|
||||
KEY_MESSAGE,
|
||||
decryptor_.GetUsageInfo(app_id_empty, kDefaultCdmIdentifier,
|
||||
&usage_info));
|
||||
EXPECT_TRUE(usage_info.size() > 0);
|
||||
|
||||
// Read in usage info file
|
||||
std::string usage_info_empty_app_id_file_name =
|
||||
path + DeviceFiles::GetUsageInfoFileName(app_id_empty);
|
||||
|
||||
file_size = file_system.FileSize(usage_info_empty_app_id_file_name);
|
||||
EXPECT_LT(4, file_size);
|
||||
file = file_system.Open(usage_info_empty_app_id_file_name,
|
||||
FileSystem::kReadOnly);
|
||||
EXPECT_TRUE(NULL != file);
|
||||
file_data.resize(file_size);
|
||||
bytes = file->Read(&file_data[0], file_data.size());
|
||||
EXPECT_EQ(file_size, bytes);
|
||||
file->Close();
|
||||
|
||||
EXPECT_TRUE(hash_file.ParseFromString(file_data));
|
||||
pos = file_data.find(hash_file.hash());
|
||||
EXPECT_NE(std::string::npos, pos);
|
||||
EXPECT_NE(0u, pos);
|
||||
|
||||
// Corrupt the protobuf key field of the hash of the usage info file
|
||||
// with empty app id and write it back out
|
||||
file_data[pos-1] = 0xff;
|
||||
|
||||
file = file_system.Open(usage_info_empty_app_id_file_name,
|
||||
FileSystem::kCreate | FileSystem::kTruncate);
|
||||
EXPECT_TRUE(NULL != file);
|
||||
bytes = file->Write(file_data.data(), file_data.size());
|
||||
EXPECT_EQ(file_size, bytes);
|
||||
file->Close();
|
||||
|
||||
EXPECT_EQ(
|
||||
NO_ERROR,
|
||||
decryptor_.RemoveAllUsageInfo(app_id_empty, kDefaultCdmIdentifier));
|
||||
|
||||
EXPECT_EQ(
|
||||
NO_ERROR,
|
||||
decryptor_.GetUsageInfo(app_id_not_empty, kDefaultCdmIdentifier,
|
||||
&usage_info));
|
||||
EXPECT_TRUE(usage_info.empty());
|
||||
EXPECT_EQ(
|
||||
NO_ERROR,
|
||||
decryptor_.GetUsageInfo(app_id_empty, kDefaultCdmIdentifier,
|
||||
&usage_info));
|
||||
EXPECT_TRUE(usage_info.empty());
|
||||
}
|
||||
|
||||
TEST_F(WvCdmRequestLicenseTest, GetSecureStopIdsTest) {
|
||||
Unprovision();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user