This is based on a patch submitted by Amlogic. When we're doing decrypt fallback, either in the CDM or the OEMCrypto tests, we sometimes fall back to a point where we're synthesizing new samples and/or subsamples for the content being decrypted. When this happens and the output buffer is clear, we should limit the size of the output buffer to only the space needed to hold the output. Previously, we've been passing the entire output buffer to every call. This can create a problem if the reason for the fallback is a lack of enough memory to communicate the buffers to the TA, since the output buffer will remain the same size as the total output. Restricting the buffer passed to each call to only the space needed by that call will reduce the memory requirement. Bug: 354834629 Test: x86-64 Merged from https://widevine-internal-review.googlesource.com/204810 Merged from https://widevine-internal-review.googlesource.com/204953 Change-Id: I412f43d8f88c72072ef1dd5293436bdb58e500b3
298 lines
11 KiB
C++
298 lines
11 KiB
C++
// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary
|
|
// source code may only be used and distributed under the Widevine
|
|
// License Agreement.
|
|
|
|
#include "oec_decrypt_fallback_chain.h"
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "oemcrypto_corpus_generator_helper.h"
|
|
#include "oemcrypto_types.h"
|
|
#include "string_conversions.h"
|
|
|
|
namespace {
|
|
|
|
void advance_dest_buffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) {
|
|
switch (dest_buffer->type) {
|
|
case OEMCrypto_BufferType_Clear:
|
|
dest_buffer->buffer.clear.clear_buffer += bytes;
|
|
break;
|
|
|
|
case OEMCrypto_BufferType_Secure:
|
|
dest_buffer->buffer.secure.offset += bytes;
|
|
break;
|
|
|
|
case OEMCrypto_BufferType_Direct:
|
|
// Nothing to do for this buffer type.
|
|
break;
|
|
}
|
|
}
|
|
|
|
void advance_iv_ctr(uint8_t (*subsample_iv)[wvoec::KEY_IV_SIZE], size_t bytes) {
|
|
uint64_t counter;
|
|
constexpr size_t half_iv_size = wvoec::KEY_IV_SIZE / 2;
|
|
memcpy(&counter, &(*subsample_iv)[half_iv_size], half_iv_size);
|
|
|
|
const size_t increment =
|
|
bytes / wvoec::AES_128_BLOCK_SIZE; // The truncation here is intentional
|
|
counter = wvutil::htonll64(wvutil::ntohll64(counter) + increment);
|
|
|
|
memcpy(&(*subsample_iv)[half_iv_size], &counter, half_iv_size);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace wvoec {
|
|
|
|
// Decrypts the given array of samples. Handles fallback behavior correctly if
|
|
// the OEMCrypto implementation does not accept multiple samples.
|
|
OEMCryptoResult DecryptFallbackChain::Decrypt(
|
|
const uint8_t* key_handle, size_t key_handle_length,
|
|
const OEMCrypto_SampleDescription* samples, size_t samples_length,
|
|
OEMCryptoCipherMode cipher_mode,
|
|
const OEMCrypto_CENCEncryptPatternDesc* pattern) {
|
|
OEMCryptoResult sts = OEMCrypto_DecryptCENC(key_handle, key_handle_length,
|
|
samples, samples_length, pattern);
|
|
|
|
// No need for a fallback. Abort early.
|
|
if (sts != OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
|
|
if (ShouldGenerateCorpus()) {
|
|
WriteDecryptCencCorpus(cipher_mode, samples, pattern, samples_length);
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
// Fall back to decrypting individual samples.
|
|
for (size_t i = 0; i < samples_length; ++i) {
|
|
sts = DecryptSample(key_handle, key_handle_length, samples[i], cipher_mode,
|
|
pattern);
|
|
if (sts != OEMCrypto_SUCCESS) return sts;
|
|
}
|
|
|
|
return sts;
|
|
}
|
|
|
|
// Decrypts the given sample. Handles fallback behavior correctly if the
|
|
// OEMCrypto implementation does not accept full samples.
|
|
OEMCryptoResult DecryptFallbackChain::DecryptSample(
|
|
const uint8_t* key_handle, size_t key_handle_length,
|
|
const OEMCrypto_SampleDescription& sample, OEMCryptoCipherMode cipher_mode,
|
|
const OEMCrypto_CENCEncryptPatternDesc* pattern) {
|
|
OEMCryptoResult sts =
|
|
OEMCrypto_DecryptCENC(key_handle, key_handle_length, &sample, 1, pattern);
|
|
|
|
// No need for a fallback. Abort early.
|
|
if (sts != OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
|
|
if (ShouldGenerateCorpus()) {
|
|
WriteDecryptCencCorpus(cipher_mode, &sample, pattern, 1);
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
// Fall back to decrypting individual subsamples.
|
|
OEMCrypto_SampleDescription fake_sample = sample;
|
|
for (size_t i = 0; i < sample.subsamples_length; ++i) {
|
|
const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[i];
|
|
|
|
const size_t length =
|
|
subsample.num_bytes_clear + subsample.num_bytes_encrypted;
|
|
fake_sample.buffers.input_data_length = length;
|
|
if (fake_sample.buffers.output_descriptor.type ==
|
|
OEMCrypto_BufferType_Clear) {
|
|
fake_sample.buffers.output_descriptor.buffer.clear.clear_buffer_length =
|
|
length;
|
|
}
|
|
fake_sample.subsamples = &subsample;
|
|
fake_sample.subsamples_length = 1;
|
|
|
|
sts = DecryptSubsample(key_handle, key_handle_length, fake_sample, pattern,
|
|
cipher_mode);
|
|
if (sts != OEMCrypto_SUCCESS) return sts;
|
|
|
|
fake_sample.buffers.input_data += length;
|
|
advance_dest_buffer(&fake_sample.buffers.output_descriptor, length);
|
|
if (cipher_mode == OEMCrypto_CipherMode_CENC) {
|
|
advance_iv_ctr(&fake_sample.iv,
|
|
subsample.block_offset + subsample.num_bytes_encrypted);
|
|
}
|
|
}
|
|
|
|
return sts;
|
|
}
|
|
|
|
// Decrypts the given subsample. Handles fallback behavior correctly if the
|
|
// OEMCrypto implementation does not accept full subsamples.
|
|
OEMCryptoResult DecryptFallbackChain::DecryptSubsample(
|
|
const uint8_t* key_handle, size_t key_handle_length,
|
|
const OEMCrypto_SampleDescription& sample,
|
|
const OEMCrypto_CENCEncryptPatternDesc* pattern,
|
|
OEMCryptoCipherMode cipher_mode) {
|
|
OEMCryptoResult sts =
|
|
OEMCrypto_DecryptCENC(key_handle, key_handle_length, &sample, 1, pattern);
|
|
|
|
// No need for a fallback. Abort early.
|
|
if (sts != OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
|
|
if (ShouldGenerateCorpus()) {
|
|
WriteDecryptCencCorpus(cipher_mode, &sample, pattern, 1);
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
// Fall back to decrypting individual subsample halves.
|
|
const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0];
|
|
OEMCrypto_SampleDescription fake_sample = sample;
|
|
OEMCrypto_SubSampleDescription fake_subsample;
|
|
fake_sample.subsamples = &fake_subsample;
|
|
fake_sample.subsamples_length = 1;
|
|
|
|
if (subsample.num_bytes_clear > 0) {
|
|
fake_sample.buffers.input_data_length = subsample.num_bytes_clear;
|
|
if (fake_sample.buffers.output_descriptor.type ==
|
|
OEMCrypto_BufferType_Clear) {
|
|
fake_sample.buffers.output_descriptor.buffer.clear.clear_buffer_length =
|
|
subsample.num_bytes_clear;
|
|
}
|
|
fake_subsample.num_bytes_clear = subsample.num_bytes_clear;
|
|
fake_subsample.num_bytes_encrypted = 0;
|
|
fake_subsample.block_offset = 0;
|
|
|
|
fake_subsample.subsample_flags = 0;
|
|
if (subsample.subsample_flags & OEMCrypto_FirstSubsample)
|
|
fake_subsample.subsample_flags |= OEMCrypto_FirstSubsample;
|
|
if (subsample.subsample_flags & OEMCrypto_LastSubsample &&
|
|
subsample.num_bytes_encrypted == 0)
|
|
fake_subsample.subsample_flags |= OEMCrypto_LastSubsample;
|
|
|
|
sts = DecryptSubsampleHalf(key_handle, key_handle_length, fake_sample,
|
|
pattern, cipher_mode);
|
|
if (sts != OEMCrypto_SUCCESS) return sts;
|
|
|
|
// Advance the buffers for the other half, in case they're needed.
|
|
fake_sample.buffers.input_data += subsample.num_bytes_clear;
|
|
advance_dest_buffer(&fake_sample.buffers.output_descriptor,
|
|
subsample.num_bytes_clear);
|
|
}
|
|
|
|
if (subsample.num_bytes_encrypted > 0) {
|
|
fake_sample.buffers.input_data_length = subsample.num_bytes_encrypted;
|
|
if (fake_sample.buffers.output_descriptor.type ==
|
|
OEMCrypto_BufferType_Clear) {
|
|
fake_sample.buffers.output_descriptor.buffer.clear.clear_buffer_length =
|
|
subsample.num_bytes_encrypted;
|
|
}
|
|
fake_subsample.num_bytes_clear = 0;
|
|
fake_subsample.num_bytes_encrypted = subsample.num_bytes_encrypted;
|
|
fake_subsample.block_offset = subsample.block_offset;
|
|
|
|
fake_subsample.subsample_flags = 0;
|
|
if (subsample.subsample_flags & OEMCrypto_FirstSubsample &&
|
|
subsample.num_bytes_clear == 0)
|
|
fake_subsample.subsample_flags |= OEMCrypto_FirstSubsample;
|
|
if (subsample.subsample_flags & OEMCrypto_LastSubsample)
|
|
fake_subsample.subsample_flags |= OEMCrypto_LastSubsample;
|
|
|
|
sts = DecryptSubsampleHalf(key_handle, key_handle_length, fake_sample,
|
|
pattern, cipher_mode);
|
|
if (sts != OEMCrypto_SUCCESS) return sts;
|
|
}
|
|
|
|
return sts;
|
|
}
|
|
|
|
// Decrypts the given subsample half. There is no fallback behavior after this;
|
|
// an OEMCrypto_ERROR_BUFFER_TOO_LARGE produced here will be returned to the
|
|
// caller.
|
|
OEMCryptoResult DecryptFallbackChain::DecryptSubsampleHalf(
|
|
const uint8_t* key_handle, size_t key_handle_length,
|
|
const OEMCrypto_SampleDescription& sample,
|
|
const OEMCrypto_CENCEncryptPatternDesc* pattern,
|
|
OEMCryptoCipherMode cipher_mode) {
|
|
if (ShouldGenerateCorpus()) {
|
|
WriteDecryptCencCorpus(cipher_mode, &sample, pattern, 1);
|
|
}
|
|
return OEMCrypto_DecryptCENC(key_handle, key_handle_length, &sample, 1,
|
|
pattern);
|
|
// In a real CDM, you would want some fallback here to handle the case where
|
|
// the buffer is too big for the OEMCrypto implementation. But in the case of
|
|
// the tests, we won't be passing a buffer that's too big unless we are trying
|
|
// to test that failure condition, so there's no need to handle that case
|
|
// here.
|
|
}
|
|
|
|
// Used for OEMCrypto Fuzzing: Corpus format is as below, let | be separator.
|
|
// cipher_mode + pattern + sample_data for all samples |
|
|
// input_data for all samples | subsample_data for all samples
|
|
void WriteDecryptCencCorpus(
|
|
OEMCryptoCipherMode cipher_mode,
|
|
const OEMCrypto_SampleDescription* samples_description,
|
|
const OEMCrypto_CENCEncryptPatternDesc* pattern, size_t samples_length) {
|
|
const std::string file_name =
|
|
GetFileName("oemcrypto_decrypt_cenc_fuzz_seed_corpus");
|
|
|
|
// Cipher mode and Pattern.
|
|
OEMCrypto_Decrypt_Cenc_Fuzz decrypt_cenc_fuzz_struct;
|
|
decrypt_cenc_fuzz_struct.cipher_mode = cipher_mode;
|
|
decrypt_cenc_fuzz_struct.pattern = *pattern;
|
|
AppendToFile(file_name,
|
|
reinterpret_cast<const char*>(&decrypt_cenc_fuzz_struct),
|
|
sizeof(OEMCrypto_Decrypt_Cenc_Fuzz));
|
|
|
|
// Sample data for all samples.
|
|
for (size_t i = 0; i < samples_length; i++) {
|
|
OEMCrypto_SampleDescription_Fuzz sample_description_data;
|
|
sample_description_data.buffers.input_data_length =
|
|
samples_description[i].buffers.input_data_length;
|
|
sample_description_data.buffers.output_descriptor.type =
|
|
samples_description[i].buffers.output_descriptor.type;
|
|
switch (sample_description_data.buffers.output_descriptor.type) {
|
|
case OEMCrypto_BufferType_Clear:
|
|
sample_description_data.buffers.output_descriptor.buffer_config =
|
|
samples_description[i]
|
|
.buffers.output_descriptor.buffer.clear.clear_buffer_length;
|
|
break;
|
|
|
|
case OEMCrypto_BufferType_Secure:
|
|
sample_description_data.buffers.output_descriptor.buffer_config =
|
|
samples_description[i]
|
|
.buffers.output_descriptor.buffer.secure.secure_buffer_length;
|
|
break;
|
|
|
|
case OEMCrypto_BufferType_Direct:
|
|
sample_description_data.buffers.output_descriptor.buffer_config =
|
|
samples_description[i]
|
|
.buffers.output_descriptor.buffer.direct.is_video;
|
|
break;
|
|
}
|
|
memcpy(sample_description_data.iv, samples_description[i].iv,
|
|
sizeof(sample_description_data.iv));
|
|
sample_description_data.subsamples_length =
|
|
samples_description[i].subsamples_length;
|
|
AppendToFile(file_name,
|
|
reinterpret_cast<const char*>(&sample_description_data),
|
|
sizeof(OEMCrypto_SampleDescription_Fuzz));
|
|
}
|
|
AppendSeparator(file_name);
|
|
|
|
// Input data for all samples.
|
|
for (size_t i = 0; i < samples_length; i++) {
|
|
AppendToFile(file_name,
|
|
reinterpret_cast<const char*>(
|
|
samples_description[i].buffers.input_data),
|
|
samples_description[i].buffers.input_data_length);
|
|
}
|
|
AppendSeparator(file_name);
|
|
|
|
// Subsample data for all samples.
|
|
for (size_t i = 0; i < samples_length; i++) {
|
|
for (size_t j = 0; j < samples_description[i].subsamples_length; j++) {
|
|
AppendToFile(
|
|
file_name,
|
|
reinterpret_cast<const char*>(&samples_description[i].subsamples[j]),
|
|
sizeof(OEMCrypto_SubSampleDescription));
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace wvoec
|