diff --git a/libwvdrmengine/oemcrypto/test/README.md b/libwvdrmengine/oemcrypto/test/README.md new file mode 100644 index 00000000..a85b663d --- /dev/null +++ b/libwvdrmengine/oemcrypto/test/README.md @@ -0,0 +1,125 @@ +# OEMCrypto Unit Tests + +## Basic Functionality Tests + +Most unit tests in this category verify that the basic functionality of opening +sessions, initializing and terminating the system, and reporting status work +correctly. + +## Decrypt Tests + +The decrypt tests verify that encrypted data is correctly decrypted with the +desired key. These tests cover a large variety of patterns, sample sizes, and +subsample sizes. + +## Secure Buffers + +If OEMCrypto implements the function `OEMCrypto_AllocateSecureBuffer`, then all +of the decrypt tests will also run with the output buffer being a secure +buffer. If the function `OEMCrypto_SupportsDecryptHash` returns +`OEMCrypto_CRC_Clear_Buffer`, then the secure buffer decryption will be verified +with the CRC32 hash of the input data. + +## Usage Table Tests + +Usage table tests verify that the usage table is correctly procesed. The usage +table is used to control reloading keys for offline playback, and for reporting +secure stops for online playback. + +## Duration Tests + +Duration tests verify that license durations are enforced correctly. Most of +this functionality can be met by keeping an accurate system time, and calling +the ODK functions as described in the document "License Duration and Renewal". + +## OEMCrypto Memory Unit Tests + +### Objective + +* Add OEMCrypto buffer overflow unit tests (indirect way of fuzzing) to verify + OEMCrypto API behavior when the parameters passed to the API are out of + range or not reasonable. The API can return an error code, but shouldn't + crash. + +* A lot of OEMCrypto APIs take buffers and their length as inputs to the APIs + and we have added unit tests with buffers of varying lengths (small + to huge) to verify API behavior which is an indirect and simplest way of + fuzz testing to detect buffer overflows. + +* Add the tests for OEMCrypto APIs with prefix `OEMCryptoMemory` in the + following format. Huge length is set at 100 MB as of now. + + ```cpp + for (size_t length=small_length; length subsamples.size()) return 0; } // Sample loop. // Allocate input/output buffers for each sample description. vector input_buffer(total_input_data_length); - RAND_bytes(input_buffer.data(), total_input_data_length); size_t input_buffer_index = 0; for (size_t i = 0; i < samples_length; i++) { sample_descriptions[i].buffers.input_data = diff --git a/libwvdrmengine/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.h b/libwvdrmengine/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.h index 7dc707ee..c524196b 100644 --- a/libwvdrmengine/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.h +++ b/libwvdrmengine/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.h @@ -36,9 +36,7 @@ class OEMCryptoLicenseAPIFuzz : public InitializeFuzz { session_.GenerateNonce(); } - ~OEMCryptoLicenseAPIFuzz() { - session_.close(); - } + ~OEMCryptoLicenseAPIFuzz() { session_.close(); } LicenseRoundTrip& license_messages() { return license_messages_; } diff --git a/libwvdrmengine/oemcrypto/test/fuzz_tests/oemcrypto_odkitee_dispatcher_fuzz.cc b/libwvdrmengine/oemcrypto/test/fuzz_tests/oemcrypto_odkitee_dispatcher_fuzz.cc new file mode 100644 index 00000000..1a3c770b --- /dev/null +++ b/libwvdrmengine/oemcrypto/test/fuzz_tests/oemcrypto_odkitee_dispatcher_fuzz.cc @@ -0,0 +1,77 @@ +#include + +#include "dispatcher.h" +#include "marshaller_base.h" +#include "transport_interface.h" + +namespace wvoec { + +void InitializeODKMessage(ODK_Message* message, uint8_t* data, size_t size) { + ODK_Message_Impl* impl = (ODK_Message_Impl*)message; + impl->base = data; + impl->size = size; + impl->capacity = size; + impl->read_offset = 0; + impl->status = MESSAGE_STATUS_OK; +} + +void OpenOEMCryptoTASession() { + ODK_Message request; + ODK_Message* response = NULL; + uint8_t response_buffer[0x1000]; + uint8_t request_body[] = { + 0x06, // TAG_UINT32 + 0x09, 0x00, 0x00, 0x00, // API value (0x09) + 0x01, // TAG_BOOL + 0x00, // value (false) + 0x0a // TAG_EOM + }; + + InitializeODKMessage(&request, request_body, sizeof(request_body)); + + ODK_DispatchMessage(&request, &response); + if (response != NULL) ODK_Transport_DeallocateMessage(response); +} + +void InitializeOEMCryptoTA() { + ODK_Message init_request; + ODK_Message* init_response = NULL; + uint8_t response_buffer[0x1000]; + uint8_t init_request_body[] = { + 0x06, // TAG_UINT32 + 0x01, 0x00, 0x00, 0x00, // API value(0x01) + 0x0a // TAG_EOM + }; + + InitializeODKMessage(&init_request, init_request_body, + sizeof(init_request_body)); + + ODK_DispatchMessage(&init_request, &init_response); + if (init_response != NULL) ODK_Transport_DeallocateMessage(init_response); +} + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { + ODK_InitializeDispatcher(); + InitializeOEMCryptoTA(); + OpenOEMCryptoTASession(); + return 0; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + ODK_Message request; + ODK_Message* response = NULL; + unsigned char response_buffer[0x1000]; + + uint8_t* input = new uint8_t[size]; + memcpy(input, data, size); + + InitializeODKMessage(&request, input, size); + + ODK_DispatchMessage(&request, &response); + if (response != NULL) ODK_Transport_DeallocateMessage(response); + + delete[] input; + return 0; +} + +} // namespace wvoec diff --git a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp index b4988edd..9649ce66 100644 --- a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp +++ b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp @@ -129,6 +129,7 @@ std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { if (!generic_crypto) FilterOut(&filter, "*GenericCrypto*"); if (!cast_receiver) FilterOut(&filter, "*CastReceiver*"); if (!usage_table) FilterOut(&filter, "*UsageTable*"); + if (!usage_table) FilterOut(&filter, "*BadRange_pst*"); if (derive_key_method == NO_METHOD) FilterOut(&filter, "*SessionTest*"); if (provisioning_method != OEMCrypto_OEMCertificate) FilterOut(&filter, "*Prov30*"); diff --git a/libwvdrmengine/oemcrypto/test/oec_session_util.cpp b/libwvdrmengine/oemcrypto/test/oec_session_util.cpp index f5d45775..0fc0f909 100644 --- a/libwvdrmengine/oemcrypto/test/oec_session_util.cpp +++ b/libwvdrmengine/oemcrypto/test/oec_session_util.cpp @@ -154,8 +154,7 @@ RoundTrip:: constexpr size_t small_size = 42; // arbitrary. uint32_t session_id = session()->session_id(); GetDefaultRequestSignatureAndCoreMessageLengths( - session_id, required_message_size_, small_size, &gen_signature_length, - &core_message_length); + session_id, small_size, &gen_signature_length, &core_message_length); // Used to test request APIs with varying lengths of core message. core_message_length = std::max(core_message_length, required_core_message_size_); @@ -192,9 +191,8 @@ RoundTrip:: template void GetDefaultRequestSignatureAndCoreMessageLengths( - uint32_t& session_id, size_t& required_message_size, - const size_t& small_size, size_t* gen_signature_length, - size_t* core_message_length) { + uint32_t& session_id, const size_t& small_size, + size_t* gen_signature_length, size_t* core_message_length) { vector data(small_size); for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF; ASSERT_EQ( diff --git a/libwvdrmengine/oemcrypto/test/oec_session_util.h b/libwvdrmengine/oemcrypto/test/oec_session_util.h index 264a8ae9..3ede5dc0 100644 --- a/libwvdrmengine/oemcrypto/test/oec_session_util.h +++ b/libwvdrmengine/oemcrypto/test/oec_session_util.h @@ -669,9 +669,8 @@ void WriteRequestApiCorpus(size_t signature_length, size_t core_message_length, vector& data); template void GetDefaultRequestSignatureAndCoreMessageLengths( - uint32_t& session_id, size_t& required_message_size, - const size_t& small_size, size_t* gen_signature_length, - size_t* core_message_length); + uint32_t& session_id, const size_t& small_size, + size_t* gen_signature_length, size_t* core_message_length); } // namespace wvoec #endif // CDM_OEC_SESSION_UTIL_H_ diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index 264d63f3..b03c2161 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -137,6 +137,11 @@ void TestHugeLengthDoesNotCrashAPI(oemcrypto_function f, !check_status); buffer_length *= 2) { sts = f(buffer_length); + if (check_status && sts != OEMCrypto_SUCCESS && + sts != OEMCrypto_ERROR_SHORT_BUFFER) { + LOGI("Test exits huge buffer loop for length:%zu, status:%d", + buffer_length, sts); + } } } @@ -170,6 +175,17 @@ const size_t kLargeMessageSize[] = { 8*KiB, 8*KiB, 16*KiB, 32*KiB}; // const size_t kAV1NumberSubsamples[] = { 72, 144, 288, 576}; // clang-format on +// Return a printable string from data. If all the characters are printable, +// then just use the string. Otherwise, convert to hex. +std::string MaybeHex(const uint8_t* data, size_t length) { + for (size_t i = 0; i < length; i++) { + if (!isprint(data[i])) return "0x" + wvcdm::HexEncode(data, length); + } + return std::string(reinterpret_cast(data), length); +} +std::string MaybeHex(const std::vector& data) { + return MaybeHex(data.data(), data.size()); +} } // namespace class OEMCryptoClientTest : public ::testing::Test, public SessionUtil { @@ -202,6 +218,17 @@ class OEMCryptoClientTest : public ::testing::Test, public SessionUtil { } }; +TEST_F(OEMCryptoClientTest, FreeUnallocatedSecureBufferNoFailure) { + Session s; + s.open(); + OEMCrypto_DestBufferDesc output_descriptor; + int secure_fd = kHugeRandomNumber; + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_FreeSecureBuffer(s.session_id(), &output_descriptor, + secure_fd)); + s.close(); +} + /// @addtogroup basic /// @{ @@ -212,15 +239,20 @@ class OEMCryptoClientTest : public ::testing::Test, public SessionUtil { */ TEST_F(OEMCryptoClientTest, VersionNumber) { const std::string log_message = - "OEMCrypto unit tests for API 16.4. Tests last updated 2020-10-07"; + "OEMCrypto unit tests for API 16.3 or 4. Tests last updated 2021-02-22"; cout << " " << log_message << "\n"; + cout << " " + << "These tests are part of Android S." + << "\n"; LOGI("%s", log_message.c_str()); // If any of the following fail, then it is time to update the log message // above. EXPECT_EQ(ODK_MAJOR_VERSION, 16); // Note on minor versions. Widevine requires version 16.3 or greater for CE // CDM and Android devices. For CE CDM devices that do not support usage - // tables, we strongly recommend 16.4. + // tables, we strongly recommend 16.4. Note: This is the version of the ODK + // library built into the tests, which might be different from the version + // that is pre-compiled into liboemcrypto.so. EXPECT_GE(ODK_MINOR_VERSION, 3); EXPECT_LE(ODK_MINOR_VERSION, 4); EXPECT_EQ(kCurrentAPI, 16u); @@ -229,7 +261,9 @@ TEST_F(OEMCryptoClientTest, VersionNumber) { ASSERT_EQ('L', level[0]); cout << " OEMCrypto Security Level is " << level << endl; uint32_t version = OEMCrypto_APIVersion(); - cout << " OEMCrypto API version is " << version << endl; + uint32_t minor_version = OEMCrypto_MinorAPIVersion(); + cout << " OEMCrypto API version is " << version << "." + << minor_version << endl; if (OEMCrypto_SupportsUsageTable()) { cout << " OEMCrypto supports usage tables" << endl; } else { @@ -661,8 +695,8 @@ TEST_F(OEMCryptoClientTest, ClearCopyTestAPI10) { OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); dest_buffer_descriptor.buffer.clear.address = output_buffer.data(); dest_buffer_descriptor.buffer.clear.address_length = output_buffer.size() - 1; - ASSERT_EQ( - OEMCrypto_ERROR_SHORT_BUFFER, + ASSERT_NE( + OEMCrypto_SUCCESS, OEMCrypto_CopyBuffer(s.session_id(), input_buffer.data(), input_buffer.size(), &dest_buffer_descriptor, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); @@ -896,8 +930,8 @@ TEST_F(OEMCryptoKeyboxTest, NormalGetDeviceId) { uint8_t dev_id[128] = {0}; size_t dev_id_len = 128; sts = OEMCrypto_GetDeviceID(dev_id, &dev_id_len); - cout << " NormalGetDeviceId: dev_id = " << dev_id - << " len = " << dev_id_len << endl; + cout << " NormalGetDeviceId: dev_id = " + << MaybeHex(dev_id, dev_id_len) << " len = " << dev_id_len << endl; ASSERT_EQ(OEMCrypto_SUCCESS, sts); } @@ -1039,7 +1073,7 @@ TEST_F(OEMCryptoProv30Test, GetDeviceId) { } ASSERT_EQ(OEMCrypto_SUCCESS, sts); dev_id.resize(dev_id_len); - cout << " NormalGetDeviceId: dev_id = " << dev_id.data() + cout << " NormalGetDeviceId: dev_id = " << MaybeHex(dev_id) << " len = " << dev_id_len << endl; ASSERT_EQ(OEMCrypto_SUCCESS, sts); } @@ -2355,6 +2389,39 @@ TEST_P(OEMCryptoLicenseTest, !kCheckStatus); } +TEST_P(OEMCryptoLicenseTest, + DecryptCENCForNumBytesClearPlusEncryptedOverflowsSize) { + LoadLicense(); + OEMCrypto_SelectKey(session_.session_id(), session_.license().keys[0].key_id, + session_.license().keys[0].key_id_length, + OEMCrypto_CipherMode_CTR); + + size_t input_buffer_size = 1; + vector in_buffer(input_buffer_size); + vector out_buffer(in_buffer.size()); + + OEMCrypto_SampleDescription sample_description; + OEMCrypto_SubSampleDescription subsample_description; + GenerateSimpleSampleDescription(in_buffer, out_buffer, &sample_description, + &subsample_description); + + OEMCrypto_SubSampleDescription* sub_samples = + const_cast( + sample_description.subsamples); + // If Decrypt cenc API does not check for overflow on clear + encrypted + // addition operation. This will result in 1 which will match with input data + // length, which causes validation to pass. + sub_samples[0].num_bytes_clear = 2; + sub_samples[0].num_bytes_encrypted = 0xFFFFFFFFFFFFFFFF; + + // Create the pattern description (always 0,0 for CTR) + OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0}; + // Try to decrypt the data + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_DecryptCENC(session_.session_id(), &sample_description, 1, + &pattern)); +} + TEST_P(OEMCryptoLicenseTest, OEMCryptoMemoryDecryptCENCForHugeNumBytesEncryptedAndBuffers) { TestDecryptCENCForHugeBufferLengths( @@ -3850,50 +3917,17 @@ class OEMCryptoSessionTestsDecryptTests OEMCrypto_SUCCESS); } - void TestDecryptCENC() { - OEMCryptoResult sts; + void TestDecryptCENC() { ASSERT_EQ(DecryptCENC(), OEMCrypto_SUCCESS); } - // OEMCrypto only supports providing a decrypt hash for one sample. - if (samples_.size() > 1) verify_crc_ = false; - - // If supported, check the decrypt hashes. - if (verify_crc_) { - const TestSample& sample = samples_[0]; - - uint32_t hash = - wvcrc32(sample.truth_buffer.data(), sample.truth_buffer.size()); - ASSERT_EQ(OEMCrypto_SetDecryptHash( - session_.session_id(), 1, - reinterpret_cast(&hash), sizeof(hash)), - OEMCrypto_SUCCESS); - } - - // Build an array of just the sample descriptions. - std::vector sample_descriptions; - sample_descriptions.reserve(samples_.size()); - for (TestSample& sample : samples_) { - // This must be deferred until this point in case the test modifies the - // buffer before testing decrypt. - sample.description.buffers.input_data = sample.encrypted_buffer.data(); - // Append to the description array. - sample_descriptions.push_back(sample.description); - } - - // Perform decryption using the test data that was previously set up. - sts = DecryptFallbackChain::Decrypt( - session_.session_id(), sample_descriptions.data(), - sample_descriptions.size(), cipher_mode_, &pattern_); - ASSERT_EQ(sts, OEMCrypto_SUCCESS); - - // Validate the decrypted data. + void ValidateDecryptedData() { for (TestSample& sample : samples_) { if (sample.description.buffers.output_descriptor.type == OEMCrypto_BufferType_Clear) { const size_t total_size = sample.description.buffers.input_data_length; // To verify there is no buffer overrun after decrypting, look at the - // padded bytes just after the data buffer that was written. It should - // not have changed from the original 0xaa that we set in MakeBuffer - // function. + // padded bytes just after the data buffer that was written. It + // should not have changed from the original 0xaa that we set in + // MakeBuffer function. if (decrypt_inplace_) { EXPECT_EQ(std::count(sample.encrypted_buffer.begin() + total_size, sample.encrypted_buffer.end(), 0xaa), @@ -3919,6 +3953,41 @@ class OEMCryptoSessionTestsDecryptTests } } + OEMCryptoResult DecryptCENC() { + // OEMCrypto only supports providing a decrypt hash for one sample. + if (samples_.size() > 1) verify_crc_ = false; + + // If supported, check the decrypt hashes. + if (verify_crc_) { + const TestSample& sample = samples_[0]; + + uint32_t hash = + wvcrc32(sample.truth_buffer.data(), sample.truth_buffer.size()); + OEMCrypto_SetDecryptHash(session_.session_id(), 1, + reinterpret_cast(&hash), + sizeof(hash)); + } + + // Build an array of just the sample descriptions. + std::vector sample_descriptions; + sample_descriptions.reserve(samples_.size()); + for (TestSample& sample : samples_) { + // This must be deferred until this point in case the test modifies the + // buffer before testing decrypt. + sample.description.buffers.input_data = sample.encrypted_buffer.data(); + // Append to the description array. + sample_descriptions.push_back(sample.description); + } + + // Perform decryption using the test data that was previously set up. + OEMCryptoResult result = DecryptFallbackChain::Decrypt( + session_.session_id(), sample_descriptions.data(), + sample_descriptions.size(), cipher_mode_, &pattern_); + if (result != OEMCrypto_SUCCESS) return result; + ValidateDecryptedData(); + return result; + } + // Parameters of test case OEMCrypto_CENCEncryptPatternDesc pattern_; OEMCryptoCipherMode cipher_mode_; @@ -4094,7 +4163,6 @@ TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSampleAPI16) { // max_sample_size. const size_t subsample_size = std::min(max_sample_size / max_num_subsamples + 1, max_subsample_size); - size_t bytes_remaining = max_sample_size; std::vector subsample_sizes; while (bytes_remaining > 0 && subsample_sizes.size() < max_num_subsamples) { @@ -4113,6 +4181,103 @@ TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSampleAPI16) { ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); } +TEST_P(OEMCryptoSessionTestsDecryptTests, + OEMCryptoMemoryDecryptCENCForHugeNumberOfSubSamples) { + auto oemcrypto_function = [&](size_t number_of_subsamples) { + std::vector subsample_sizes; + while (number_of_subsamples-- > 0) { + subsample_sizes.push_back({1, 1}); + } + SetSubsampleSizes(subsample_sizes); + LoadLicense(); + MakeBuffers(); + EncryptData(); + OEMCryptoResult result = DecryptCENC(); + // Closing the session and opening it for next iteration. + // If it is last iteration, session will be closed in teardown method of + // class. + session_.close(); + session_.open(); + InstallTestRSAKey(&session_); + return result; + }; + TestHugeLengthDoesNotCrashAPI(oemcrypto_function, 1, 2 * MiB, kCheckStatus); +} + +TEST_P(OEMCryptoSessionTestsDecryptTests, + OEMCryptoMemoryCheckDecryptCENCStatusForHugeNumberOfSubSamples) { + size_t number_of_subsamples = 10000; + std::vector subsample_sizes; + while (number_of_subsamples-- > 0) { + subsample_sizes.push_back({100, 100}); + } + SetSubsampleSizes(subsample_sizes); + LoadLicense(); + MakeBuffers(); + EncryptData(); + // Build an array of just the sample descriptions. + std::vector sample_descriptions; + sample_descriptions.reserve(samples_.size()); + for (TestSample& sample : samples_) { + // This must be deferred until this point in case the test modifies the + // buffer before testing decrypt. + sample.description.buffers.input_data = sample.encrypted_buffer.data(); + // Append to the description array. + sample_descriptions.push_back(sample.description); + } + OEMCryptoResult result = OEMCrypto_DecryptCENC( + session_.session_id(), sample_descriptions.data(), 1, &pattern_); + LOGD("Large number of subsamples test has return code %d", result); +} + +TEST_P(OEMCryptoSessionTestsDecryptTests, + OEMCryptoMemoryCheckDecryptCENCStatusForHugeSubSample) { + std::vector subsample_sizes; + subsample_sizes.push_back({100000, 100000}); + SetSubsampleSizes(subsample_sizes); + LoadLicense(); + MakeBuffers(); + EncryptData(); + // Build an array of just the sample descriptions. + std::vector sample_descriptions; + sample_descriptions.reserve(samples_.size()); + for (TestSample& sample : samples_) { + // This must be deferred until this point in case the test modifies the + // buffer before testing decrypt. + sample.description.buffers.input_data = sample.encrypted_buffer.data(); + // Append to the description array. + sample_descriptions.push_back(sample.description); + } + OEMCryptoResult result = OEMCrypto_DecryptCENC( + session_.session_id(), sample_descriptions.data(), 1, &pattern_); + LOGD("Large subsample test has return code %d", result); +} + +TEST_P(OEMCryptoSessionTestsDecryptTests, + OEMCryptoMemoryDecryptCENCForHugeNumberOfSamples) { + auto oemcrypto_function = [&](size_t number_of_samples) { + std::vector> samples; + std::vector subsample_sizes; + subsample_sizes.push_back({1, 1}); + while (number_of_samples-- > 0) { + samples.push_back(subsample_sizes); + } + SetSampleSizes(samples); + LoadLicense(); + MakeBuffers(); + EncryptData(); + OEMCryptoResult result = DecryptCENC(); + // Closing the session and opening it for next iteration. + // If it is last iteration, session will be closed in teardown method of + // class. + session_.close(); + session_.open(); + InstallTestRSAKey(&session_); + return result; + }; + TestHugeLengthDoesNotCrashAPI(oemcrypto_function, 1, 2 * MiB, kCheckStatus); +} + // Based on the resource rating, OEMCrypto should be able to handle the maximum // subsample size. TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSubsample) {