Bharath Chandra Elluru
2020-12-15 12:13:07 -08:00
parent e851d42eb1
commit 6b548748b2
5 changed files with 2391 additions and 139 deletions

View File

@@ -104,17 +104,13 @@
oemcrypto_fuzztests.gypi cflags_cc in order to generate additional debug
information locally.
* Build and test fuzz scripts locally using:
* Build and test fuzz scripts locally using following commands. The build
script builds fuzz binaries for both oemcrypto reference implementation
as well as odkitee implementation.
```shell
$ export CXX=clang++
$ export CC=clang
$ export GYP_DEFINES="clang=1"
$ cd /path/to/cdm/repo
$ export PATH_TO_CDM_DIR=.
$ gyp --format=ninja --depth=$(pwd) \
oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gyp
$ ninja -C out/Default/
$ cd PATH_TO_CDM_DIR
$ ./oemcrypto/test/fuzz_tests/build_oemcrypto_fuzztests
$ mkdir /tmp/new_interesting_corpus
$ ./out/Default/fuzzer_binary /tmp/new_interesting_corpus \
/path/to/fuzz/seed/corpus/folder
@@ -127,8 +123,9 @@
$ ./out/Default/fuzzer_binary crash_input_file
```
## Adding a new OEMCrypto fuzz script
* In order to fuzz a new OEMCrypto API in future, a fuzz script can be added to
oemcrypto/test/fuzz_tests folder which ends with _fuzz.cc.
* In order to fuzz a new OEMCrypto API in future, a fuzz script can be added
to oemcrypto/test/fuzz_tests folder which starts with oemcrypto and ends
with fuzz.cc(GCB build script for oemcrypto fuzzers expects the format).
* In the program, define the function LLVMFuzzerTestOneInput with the following signature:
```
@@ -164,50 +161,14 @@
### Adding a new fuzz script to the build script:
* In order to update build script such as adding a new fuzzer to build script,
we need to update the build script in docker image from cloud repository.
[Build script.](https://widevine-internal.googlesource.com/cloud/+/refs/heads/master/docker
/cloud_build/oemcrypto/release/ubuntu/fuzz/build.sh)
* As long as a new fuzz script is added which starts with oemcrypto and ends
with fuzz, the build command can be added to build_oemcrypto_fuzztests.
GCB script uses build_oemcrypto_fuzztests script to build fuzz binaries
and make them available for clusterfuzz to run continuously.
Add the new fuzz script name to fuzzers variable and follow steps in README
to upload new docker image. Make sure you update the tag to be higher than
latest version in GCR.
Run the following command from your machine to update the docker image tag
in the git trigger.
```shell
stubby call --rpc_creds_file=/tmp/mint.txt \
blade:alphasource-ci-proctor-metadata-service-prod \
ProctorMetadataService.UpdateTrigger --proto2 <<EOF
trigger {
cloud_project_number: 257246079067
name: "cdm-git-trigger"
id: "e8939c9a-d971-4c05-91b5-e0544abf872b"
state: LIVE
git_trigger {
url: "https://widevine-internal.googlesource.com/cdm"
branch_name: "master"
}
build_configs {
build {
steps {
name: "gcr.io/google.com/blockbuster-1154/
cloud-build-oemcrypto-release-ubuntu-fuzz:LATEST_TAG_VERSION"
}
}
}
result_config {
email_config {
notify_condition {
condition: ON_FAILURE
}
to_address: "wideving-engprod@google.com"
}
}
}
EOF
```
* If the new fuzzer cannot follow the naming convention OR GCB script needs
to be updated for any other reason, refer to [this section](https://docs.google.com/document/d/1mdSV2irJZz5Y9uYb5DmSIddBjrAIZU9q8G5Q_BGpA4I/edit#heading=h.bu9yfftdonkg)
section.
## Generate code coverage reports locally

View File

@@ -0,0 +1,16 @@
#!/bin/bash
set -ex
export CXX=clang++
export CC=clang
export GYP_DEFINES="$GYP_DEFINES clang=1"
export PATH_TO_CDM_DIR=.
gyp --format=ninja --depth=$(pwd) oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gyp
ninja -C out/Default
# oemcrypto_odkitee_fuzztests.gypi has flags to instrument all the gyp targets
# with fuzzer flags.
gyp --format=ninja --depth=$(pwd) \
--include=oemcrypto/test/fuzz_tests/oemcrypto_odkitee_fuzztests.gypi \
oemcrypto/test/fuzz_tests/oemcrypto_odkitee_fuzztests.gyp
ninja -C out/Default

View File

@@ -144,26 +144,29 @@ Test_PST_Report::Test_PST_Report(const std::string& pst_in,
template <class CoreRequest, PrepAndSignRequest_t PrepAndSignRequest,
class CoreResponse, class ResponseData>
void RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse,
ResponseData>::SignAndVerifyRequest() {
OEMCryptoResult
RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse, ResponseData>::
SignAndCreateRequestWithCustomBufferLengths(bool verify_request) {
// In the real world, a message should be signed by the client and
// verified by the server. This simulates that.
size_t gen_signature_length = 0;
size_t core_message_length = 0;
constexpr size_t small_size = 42; // arbitrary.
size_t message_size =
std::max(required_message_size_, core_message_length + small_size);
vector<uint8_t> data(message_size, 0);
for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF;
ASSERT_EQ(
PrepAndSignRequest(session()->session_id(), data.data(), data.size(),
&core_message_length, nullptr, &gen_signature_length),
OEMCrypto_ERROR_SHORT_BUFFER);
uint32_t session_id = session()->session_id();
GetDefaultRequestSignatureAndCoreMessageLengths<PrepAndSignRequest>(
session_id, required_message_size_, 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_);
// Used to test request APIs with varying lengths of signature.
gen_signature_length =
std::max(gen_signature_length, required_request_signature_size_);
// Make the message buffer a little bigger than the core message, or the
// required size, whichever is larger.
message_size =
size_t message_size =
std::max(required_message_size_, core_message_length + small_size);
data.resize(message_size);
vector<uint8_t> data(message_size);
for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF;
if (ShouldGenerateCorpus()) {
WriteRequestApiCorpus<CoreRequest>(gen_signature_length,
@@ -171,17 +174,33 @@ void RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse,
}
vector<uint8_t> gen_signature(gen_signature_length);
ASSERT_EQ(PrepAndSignRequest(session()->session_id(), data.data(),
data.size(), &core_message_length,
gen_signature.data(), &gen_signature_length),
OEMCrypto_SUCCESS);
OEMCryptoResult result = PrepAndSignRequest(
session()->session_id(), data.data(), data.size(), &core_message_length,
gen_signature.data(), &gen_signature_length);
// We need to fill in core request and verify signature only for calls other
// than OEMCryptoMemory buffer overflow test. Any test other than buffer
// overflow will pass true.
if (!verify_request || result != OEMCrypto_SUCCESS) return result;
if (global_features.api_version >= kCoreMessagesAPI) {
ASSERT_GT(data.size(), core_message_length);
std::string core_message(reinterpret_cast<char*>(data.data()),
core_message_length);
FillAndVerifyCoreRequest(core_message);
}
VerifyRequestSignature(data, gen_signature, core_message_length);
return result;
}
template <PrepAndSignRequest_t PrepAndSignRequest>
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) {
vector<uint8_t> data(small_size);
for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF;
ASSERT_EQ(
PrepAndSignRequest(session_id, data.data(), data.size(),
core_message_length, nullptr, gen_signature_length),
OEMCrypto_ERROR_SHORT_BUFFER);
}
template <class CoreRequest, PrepAndSignRequest_t PrepAndSignRequest,
@@ -306,10 +325,26 @@ void ProvisioningRoundTrip::EncryptAndSignResponse() {
&encrypted_response_data_);
core_response_.enc_private_key.length =
encrypted_response_data_.rsa_key_length;
SignResponse();
}
// We need this for provisioning response out of range tests where
// core response substring lengths are modified.
void ProvisioningRoundTrip::
EncryptAndSignResponseWithoutUpdatingEncPrivateKeyLength() {
encryptor_.PadAndEncryptProvisioningMessage(&response_data_,
&encrypted_response_data_);
SignResponse();
}
void ProvisioningRoundTrip::SignResponse() {
if (global_features.api_version >= kCoreMessagesAPI) {
ASSERT_TRUE(
oemcrypto_core_message::serialize::CreateCoreProvisioningResponse(
core_response_, core_request_, &serialized_core_message_));
// Resizing for huge core message length unit tests.
serialized_core_message_.resize(
std::max(required_core_message_size_, serialized_core_message_.size()));
}
// Make the message buffer a just big enough, or the
// required size, whichever is larger.
@@ -689,6 +724,10 @@ void LicenseRoundTrip::EncryptAndSignResponse() {
ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreLicenseResponse(
core_response_, core_request_, request_hash_string,
&serialized_core_message_));
// Resize serialize core message to be just big enough or required core
// message size, whichever is larger.
serialized_core_message_.resize(
std::max(required_core_message_size_, serialized_core_message_.size()));
}
// Make the message buffer a just big enough, or the
@@ -701,7 +740,6 @@ void LicenseRoundTrip::EncryptAndSignResponse() {
for (size_t i = 0; i < encrypted_response_.size(); i++) {
encrypted_response_[i] = i % 0x100;
}
ASSERT_GE(kMaxCoreMessage, serialized_core_message_.size());
ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size());
memcpy(encrypted_response_.data(), serialized_core_message_.data(),
serialized_core_message_.size());
@@ -716,6 +754,11 @@ void LicenseRoundTrip::EncryptAndSignResponse() {
}
OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session) {
return LoadResponse(session, true);
}
OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session,
bool verify_keys) {
EXPECT_NE(session, nullptr);
// Write corpus for oemcrypto_load_license_fuzz. Fuzz script expects
// unecnrypted response from license server as input corpus data.
@@ -762,7 +805,7 @@ OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session) {
encrypted_response_.size(), serialized_core_message_.size(),
response_signature_.data(), response_signature_.size());
}
if (result == OEMCrypto_SUCCESS) {
if (verify_keys && result == OEMCrypto_SUCCESS) {
// Give the session object a copy of the license truth data so that it can
// call SelectKey, use key control information, and so that it has key data
// to verify decrypt operations.
@@ -861,6 +904,18 @@ void EntitledMessage::MakeOneKey(size_t entitlement_key_index) {
key_data->content_key_data_iv, sizeof(key_data->content_key_data_iv));
}
OEMCrypto_EntitledContentKeyObject* EntitledMessage::entitled_key_array() {
return entitled_key_array_;
}
EntitledContentKeyData* EntitledMessage::entitled_key_data() {
return entitled_key_data_;
}
size_t EntitledMessage::entitled_key_data_size() {
return sizeof(entitled_key_data_);
}
void EntitledMessage::SetEntitlementKeyId(unsigned int index,
const std::string& key_id) {
ASSERT_LT(index, num_keys_);
@@ -884,6 +939,32 @@ OEMCrypto_Substring EntitledMessage::FindSubstring(const void* ptr,
}
void EntitledMessage::LoadKeys(OEMCryptoResult expected_sts) {
EncryptContentKey();
ASSERT_EQ(expected_sts,
OEMCrypto_LoadEntitledContentKeys(
license_messages_->session()->session_id(),
reinterpret_cast<const uint8_t*>(entitled_key_data_),
sizeof(entitled_key_data_), num_keys_, entitled_key_array_));
if (expected_sts != OEMCrypto_SUCCESS) {
return;
}
VerifyEntitlementTestKeys();
}
OEMCryptoResult EntitledMessage::LoadKeys(const vector<uint8_t>& message) {
return OEMCrypto_LoadEntitledContentKeys(
license_messages_->session()->session_id(), message.data(),
message.size(), num_keys_, entitled_key_array_);
}
OEMCryptoResult EntitledMessage::LoadKeys() {
return OEMCrypto_LoadEntitledContentKeys(
license_messages_->session()->session_id(),
reinterpret_cast<const uint8_t*>(entitled_key_data_),
sizeof(entitled_key_data_), num_keys_, entitled_key_array_);
}
void EntitledMessage::EncryptContentKey() {
for (size_t i = 0; i < num_keys_; ++i) {
EntitledContentKeyData* key_data = &entitled_key_data_[i];
const size_t entitlement_key_index = key_data->key_index;
@@ -912,15 +993,6 @@ void EntitledMessage::LoadKeys(OEMCryptoResult expected_sts) {
AppendToFile(file_name, reinterpret_cast<const char*>(entitled_key_array_),
num_keys_);
}
ASSERT_EQ(expected_sts,
OEMCrypto_LoadEntitledContentKeys(
license_messages_->session()->session_id(),
reinterpret_cast<const uint8_t*>(entitled_key_data_),
sizeof(entitled_key_data_), num_keys_, entitled_key_array_));
if (expected_sts != OEMCrypto_SUCCESS) {
return;
}
VerifyEntitlementTestKeys();
}
// This function verifies that the key control block reported by OEMCrypto agree
@@ -1035,6 +1107,10 @@ void RenewalRoundTrip::EncryptAndSignResponse() {
} else {
ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreRenewalResponse(
core_request_, renewal_duration_seconds_, &serialized_core_message_));
// Resize serialize core message to be just big enough or required core
// message size, whichever is larger.
serialized_core_message_.resize(
std::max(required_core_message_size_, serialized_core_message_.size()));
}
// Make the message buffer a just big enough, or the
// required size, whichever is larger.
@@ -1047,7 +1123,6 @@ void RenewalRoundTrip::EncryptAndSignResponse() {
encrypted_response_[i] = i % 0x100;
}
// Concatenate the core message and the response.
ASSERT_GE(kMaxCoreMessage, serialized_core_message_.size());
ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size());
memcpy(encrypted_response_.data(), serialized_core_message_.data(),
serialized_core_message_.size());

View File

@@ -155,12 +155,25 @@ class RoundTrip {
core_response_(),
response_data_(),
encrypted_response_data_(),
required_message_size_(0) {}
required_message_size_(0),
required_core_message_size_(0),
required_request_signature_size_(0) {}
virtual ~RoundTrip() {}
// Have OEMCrypto sign a request message and then verify the signature and the
// core message.
virtual void SignAndVerifyRequest();
virtual void SignAndVerifyRequest() {
// Boolean true generates core request and verifies the request.
// Custom message sizes are 0 by default, so the behavior of following
// functions will be sign and verify request without any custom buffers
// sizes.
ASSERT_EQ(SignAndCreateRequestWithCustomBufferLengths(true),
OEMCrypto_SUCCESS);
}
// Have OEMCrypto sign and call create request APIs. Buffer parameters in API
// can be set to custom values to test with varying lengths of buffers.
virtual OEMCryptoResult SignAndCreateRequestWithCustomBufferLengths(
bool verify_request = false);
// Used for OEMCrypto Fuzzing: Function to convert fuzzer data to valid
// License/Provisioning/Renwal request data that can be serialized.
virtual void InjectFuzzedRequestData(uint8_t* data, size_t size);
@@ -189,6 +202,16 @@ class RoundTrip {
// Set the size of the buffer used the encrypted license.
void set_message_size(size_t size) { required_message_size_ = size; }
// Set core message size to test OEMCrypto request APIs for varying core
// message lengths.
void set_core_message_size(size_t size) {
required_core_message_size_ = size;
}
// Set signature size to test OEMCrypto request APIs for varying signature
// lengths.
void set_request_signature_size(size_t size) {
required_request_signature_size_ = size;
}
std::vector<uint8_t>& response_signature() { return response_signature_; }
const std::string& serialized_core_message() const {
return serialized_core_message_;
@@ -217,6 +240,8 @@ class RoundTrip {
// Message buffers will be at least this big. Tests for loading and signing
// messages will increase all buffers to this size.
size_t required_message_size_;
size_t required_core_message_size_;
size_t required_request_signature_size_;
std::vector<uint8_t> response_signature_;
std::string serialized_core_message_;
std::vector<uint8_t> encrypted_response_;
@@ -239,6 +264,8 @@ class ProvisioningRoundTrip
virtual void PrepareSession(const wvoec::WidevineKeybox& keybox);
void CreateDefaultResponse() override;
void EncryptAndSignResponse() override;
void EncryptAndSignResponseWithoutUpdatingEncPrivateKeyLength();
void SignResponse();
OEMCryptoResult LoadResponse() override { return LoadResponse(session_); }
OEMCryptoResult LoadResponse(Session* session) override;
void VerifyLoadFailed();
@@ -318,6 +345,7 @@ class LicenseRoundTrip
void EncryptAndSignResponse() override;
OEMCryptoResult LoadResponse() override { return LoadResponse(session_); }
OEMCryptoResult LoadResponse(Session* session) override;
OEMCryptoResult LoadResponse(Session* session, bool verify_keys);
// Reload an offline license into a different session. This derives new mac
// keys and then calls LoadResponse.
OEMCryptoResult ReloadResponse(Session* session);
@@ -442,11 +470,19 @@ class EntitledMessage {
void FillKeyArray();
void MakeOneKey(size_t entitlement_key_index);
void LoadKeys(OEMCryptoResult expected_sts);
OEMCryptoResult LoadKeys(const vector<uint8_t>& message);
OEMCryptoResult LoadKeys();
void EncryptContentKey();
void set_num_keys(uint32_t num_keys) { num_keys_ = num_keys; }
uint32_t num_keys() const { return num_keys_; }
void SetEntitlementKeyId(unsigned int index, const std::string& key_id);
// Verify that key control blocks of the loaded keys.
void VerifyEntitlementTestKeys();
OEMCrypto_EntitledContentKeyObject* entitled_key_array();
// Returns entitled_key_data_ which is used as input message buffer to
// load entitled content keys API.
EntitledContentKeyData* entitled_key_data();
size_t entitled_key_data_size();
private:
// Find the offset of the give pointer, relative to |entitled_key_data_|.
@@ -631,6 +667,11 @@ bool ConvertByteToValidBoolean(const bool* in);
template <class CoreRequest>
void WriteRequestApiCorpus(size_t signature_length, size_t core_message_length,
vector<uint8_t>& data);
template <PrepAndSignRequest_t PrepAndSignRequest>
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);
} // namespace wvoec
#endif // CDM_OEC_SESSION_UTIL_H_

File diff suppressed because it is too large Load Diff