diff --git a/fuzzer/Android.bp b/fuzzer/Android.bp new file mode 100644 index 00000000..67be8bec --- /dev/null +++ b/fuzzer/Android.bp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +cc_defaults { + name: "libcdm_fuzzer_defaults", + cflags: [ + "-DDYNAMIC_ADAPTER", + ], + static_libs: [ + "libcdm", + "libcdm_protos", + "libcdm_utils", + "libwvlevel3", + "libwv_odk", + "libjsmn", + ], + shared_libs: [ + "libcrypto", + "libprotobuf-cpp-lite", + "libbinder_ndk", + "liblog", + "libbase", + "libutils", + ], + include_dirs: [ + "vendor/widevine/libwvdrmengine/cdm/core/include", + "vendor/widevine/libwvdrmengine/cdm/metrics/include", + "vendor/widevine/libwvdrmengine/cdm/util/include", + "vendor/widevine/libwvdrmengine/cdm/include", + "vendor/widevine/libwvdrmengine/oemcrypto/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/include", + "external/jsmn", + "external/protobuf/src" + ], + header_libs: ["libutils_headers"], + fuzz_config: { + cc: [ + "android-media-fuzzing-reports@google.com", + ], + componentid: 59148, + untrusted_data: true, + critical: false, + hotlists: ["4593311"], + description: "The fuzzers target the APIs of all widevine modules", + vector: "local_no_privileges_required", + service_privilege: "privileged", + users: "single_user", + fuzzed_code_usage: "shipped" + }, + proprietary: true, + vendor: true, +} + +cc_fuzz { + name: "policy_engine_fuzzer", + srcs: ["policy_engine_fuzzer.cpp"], + defaults: ["libcdm_fuzzer_defaults"], +} diff --git a/fuzzer/README.md b/fuzzer/README.md new file mode 100644 index 00000000..fc910aa6 --- /dev/null +++ b/fuzzer/README.md @@ -0,0 +1,28 @@ +# Fuzzers for libcdm + +## Table of contents ++ [policy_engine_fuzzer](#PolicyEngine) + +# Fuzzer for PolicyEngine + +PolicyEngine supports the following parameters: +1. SigningKeyId (parameter name: "kSigningKeyId") +2. RenewalServerUrl (parameter name: "kRenewalServerUrl") +3. EntitlementKeyId (parameter name: "kEntitlementKeyId") + +| Parameter| Valid Values| Configured Value| +|------------- |-------------| ----- | +|`kSigningKeyId`| `String` |Value obtained from FuzzedDataProvider| +|`kRenewalServerUrl`| `String` |Value obtained from FuzzedDataProvider| +|`kEntitlementKeyId`| `String` |Value obtained from FuzzedDataProvider| + +#### Steps to run +1. Build the fuzzer +``` + $ mm -j$(nproc) policy_engine_fuzzer +``` +2. Run on device +``` + $ adb sync data + $ adb shell /data/fuzz/arm64/policy_engine_fuzzer/vendor/policy_engine_fuzzer +``` diff --git a/fuzzer/policy_engine_fuzzer.cpp b/fuzzer/policy_engine_fuzzer.cpp new file mode 100644 index 00000000..435b7f97 --- /dev/null +++ b/fuzzer/policy_engine_fuzzer.cpp @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "policy_engine.h" +#include "wv_cdm_event_listener.h" +#include + +using namespace wvcdm; +using namespace wvcdm::metrics; +using namespace video_widevine; + +static constexpr int32_t kLicenseIDVersion = 1; +static constexpr int32_t kMaxByte = 256; +static constexpr int32_t kMinResolution = 1; +static constexpr int32_t kMaxResolution = 1024; +static constexpr int32_t kMaxEntitledKey = 5; +const KeyId kSigningKeyId = "signing_key"; +const KeyId kEntitlementKeyId = "entitlementkeyid"; +const char *kRenewalServerUrl = + "https://test.google.com/license/GetCencLicense"; + +class FuzzWvCdmEventListener : public WvCdmEventListener { + void OnSessionRenewalNeeded(const CdmSessionId & /*session_id*/) {} + void OnSessionKeysChange(const CdmSessionId & /*session_id*/, + const CdmKeyStatusMap & /*keys_status*/, + bool /*has_new_usable_key*/) {} + void OnExpirationUpdate(const CdmSessionId & /*session_id*/, + int64_t /*new_expiry_time_seconds*/) {} +}; + +class PolicyEngineFuzzer { +public: + PolicyEngineFuzzer(const uint8_t *data, size_t size) : mFdp(data, size){}; + void process(); + +private: + FuzzedDataProvider mFdp; + KeyId mKeyId; + License mLicense; + void setUp(); +}; + +void policySetBool(std::function function, + FuzzedDataProvider *fdp) { + if (fdp->ConsumeBool()) { + function(fdp->ConsumeBool()); + } +} + +void policySetInt64(std::function function, + FuzzedDataProvider *fdp) { + if (fdp->ConsumeBool()) { + function(fdp->ConsumeIntegral()); + } +} + +void PolicyEngineFuzzer::setUp() { + + License::KeyContainer *key = mLicense.add_key(); + int32_t keyType = mFdp.ConsumeIntegralInRange( + License::KeyContainer::SIGNING, License::KeyContainer::ENTITLEMENT); + key->set_type((License_KeyContainer_KeyType)keyType); + mKeyId = mFdp.ConsumeRandomLengthString(kMaxByte); + + LicenseIdentification *id = mLicense.mutable_id(); + if (mFdp.ConsumeBool()) { + id->set_version(kLicenseIDVersion); + } + if (mFdp.ConsumeBool()) { + id->set_type(mFdp.ConsumeBool() ? STREAMING : OFFLINE); + } + if (mFdp.ConsumeBool()) { + id->set_provider_session_token(mFdp.ConsumeRandomLengthString(kMaxByte)); + } + + if (mFdp.ConsumeBool()) { + mLicense.set_license_start_time(mFdp.ConsumeIntegral()); + } + + if (mFdp.ConsumeBool()) { + License_KeyContainer_OutputProtection *outputProtection = + key->mutable_required_protection(); + License_KeyContainer_OutputProtection_HDCP hdcpLevel = + (License_KeyContainer_OutputProtection_HDCP)mFdp + .ConsumeIntegralInRange(HDCP_NONE, HDCP_NO_DIGITAL_OUTPUT); + outputProtection->set_hdcp(hdcpLevel); + } + + if (mFdp.ConsumeBool()) { + const std::string ivString = mFdp.ConsumeRandomLengthString(kMaxByte); + key->set_iv(ivString); + } + + switch (keyType) { + case License::KeyContainer::SIGNING: { + if (mFdp.ConsumeBool()) { + mKeyId = mFdp.ConsumeBool() ? kSigningKeyId : mKeyId; + key->set_id(mKeyId); + } + break; + } + case License::KeyContainer::CONTENT: { + if (mFdp.ConsumeBool()) { + key->set_id(mKeyId); + } + if (mFdp.ConsumeBool()) { + int32_t level = mFdp.ConsumeIntegralInRange( + License::KeyContainer::SW_SECURE_CRYPTO, + License::KeyContainer::HW_SECURE_ALL); + key->set_level((License_KeyContainer_SecurityLevel)level); + } + + License_Policy *policy = mLicense.mutable_policy(); + policySetBool( + std::bind(&License_Policy::set_can_play, policy, std::placeholders::_1), + &mFdp); + policySetBool(std::bind(&License_Policy::set_can_persist, policy, + std::placeholders::_1), &mFdp); + policySetBool(std::bind(&License_Policy::set_can_renew, policy, + std::placeholders::_1), &mFdp); + policySetBool(std::bind(&License_Policy::set_renew_with_usage, policy, + std::placeholders::_1), &mFdp); + policySetBool(std::bind(&License_Policy::set_soft_enforce_rental_duration, + policy, std::placeholders::_1), &mFdp); + policySetBool(std::bind(&License_Policy::set_soft_enforce_playback_duration, + policy, std::placeholders::_1), &mFdp); + + policySetInt64(std::bind(&License_Policy::set_rental_duration_seconds, + policy, std::placeholders::_1), &mFdp); + policySetInt64(std::bind(&License_Policy::set_playback_duration_seconds, + policy, std::placeholders::_1), &mFdp); + policySetInt64(std::bind(&License_Policy::set_license_duration_seconds, + policy, std::placeholders::_1), &mFdp); + policySetInt64( + std::bind(&License_Policy::set_renewal_recovery_duration_seconds, + policy, std::placeholders::_1), &mFdp); + policySetInt64( + std::bind(&License_Policy::set_renewal_retry_interval_seconds, policy, + std::placeholders::_1), &mFdp); + policySetInt64( + std::bind(&License_Policy::set_renewal_delay_seconds, policy, + std::placeholders::_1), &mFdp); + + if (mFdp.ConsumeBool()) { + video_widevine::License::Policy::TimerDelayBase timeDelayBase = + (video_widevine::License::Policy::TimerDelayBase)mFdp.ConsumeIntegralInRange( + License_Policy_TimerDelayBase_TIMER_DELAY_BASE_UNSPECIFIED , + License_Policy_TimerDelayBase_FIRST_DECRYPT); + policy->set_initial_renewal_delay_base(timeDelayBase); + } + + if (mFdp.ConsumeBool()) { + std::string serverUrl = mFdp.ConsumeBool() + ? kRenewalServerUrl + : mFdp.ConsumeRandomLengthString(kMaxByte); + policy->set_renewal_server_url(serverUrl); + } + break; + } + case License::KeyContainer::KEY_CONTROL: { + key->set_type(License::KeyContainer::KEY_CONTROL); + // No control bits for this type of key container used for license + break; + } + case License::KeyContainer::OPERATOR_SESSION: { + key->set_type(License::KeyContainer::OPERATOR_SESSION); + + if (mFdp.ConsumeBool()) { + key->set_id(mKeyId); + } + + if (mFdp.ConsumeBool()) { + int32_t level = mFdp.ConsumeIntegralInRange( + License::KeyContainer::SW_SECURE_CRYPTO, + License::KeyContainer::HW_SECURE_ALL); + key->set_level((License_KeyContainer_SecurityLevel)level); + } + + if (mFdp.ConsumeBool()) { + policySetBool(std::bind(&License_Policy::set_can_persist, mLicense.mutable_policy(), + std::placeholders::_1), &mFdp); + } + + License::KeyContainer::OperatorSessionKeyPermissions *permissions = + key->mutable_operator_session_key_permissions(); + policySetBool( + std::bind(&License::KeyContainer::OperatorSessionKeyPermissions:: + set_allow_encrypt, + permissions, std::placeholders::_1), &mFdp); + policySetBool( + std::bind(&License::KeyContainer::OperatorSessionKeyPermissions:: + set_allow_decrypt, + permissions, std::placeholders::_1), &mFdp); + policySetBool(std::bind(&License::KeyContainer:: + OperatorSessionKeyPermissions::set_allow_sign, + permissions, std::placeholders::_1), &mFdp); + policySetBool( + std::bind(&License::KeyContainer::OperatorSessionKeyPermissions:: + set_allow_signature_verify, + permissions, std::placeholders::_1), &mFdp); + break; + } + case License::KeyContainer::ENTITLEMENT: + default: { + if (mFdp.ConsumeBool()) { + mKeyId = mFdp.ConsumeBool() ? kEntitlementKeyId : mKeyId; + key->set_id(mKeyId); + } + if (mFdp.ConsumeBool()) { + int32_t level = mFdp.ConsumeIntegralInRange( + License::KeyContainer::SW_SECURE_CRYPTO, + License::KeyContainer::HW_SECURE_ALL); + key->set_level((License_KeyContainer_SecurityLevel)level); + } + + License_Policy *policy = mLicense.mutable_policy(); + policySetBool( + std::bind(&License_Policy::set_can_play, policy, std::placeholders::_1), + &mFdp); + policySetBool(std::bind(&License_Policy::set_can_persist, policy, + std::placeholders::_1), &mFdp); + policySetBool(std::bind(&License_Policy::set_can_renew, policy, + std::placeholders::_1), &mFdp); + policySetBool(std::bind(&License_Policy::set_renew_with_usage, policy, + std::placeholders::_1), &mFdp); + policySetBool(std::bind(&License_Policy::set_soft_enforce_rental_duration, + policy, std::placeholders::_1), &mFdp); + policySetBool(std::bind(&License_Policy::set_soft_enforce_playback_duration, + policy, std::placeholders::_1), &mFdp); + + policySetInt64(std::bind(&License_Policy::set_rental_duration_seconds, + policy, std::placeholders::_1), &mFdp); + policySetInt64(std::bind(&License_Policy::set_playback_duration_seconds, + policy, std::placeholders::_1), &mFdp); + policySetInt64(std::bind(&License_Policy::set_license_duration_seconds, + policy, std::placeholders::_1), &mFdp); + policySetInt64( + std::bind(&License_Policy::set_renewal_recovery_duration_seconds, + policy, std::placeholders::_1), &mFdp); + policySetInt64( + std::bind(&License_Policy::set_renewal_retry_interval_seconds, policy, + std::placeholders::_1), &mFdp); + policySetInt64( + std::bind(&License_Policy::set_renewal_delay_seconds, policy, + std::placeholders::_1), &mFdp); + + if (mFdp.ConsumeBool()) { + video_widevine::License::Policy::TimerDelayBase timeDelayBase = (video_widevine::License::Policy::TimerDelayBase)mFdp.ConsumeIntegralInRange( + License_Policy_TimerDelayBase_TIMER_DELAY_BASE_UNSPECIFIED , + License_Policy_TimerDelayBase_FIRST_DECRYPT); + policy->set_initial_renewal_delay_base(timeDelayBase); + } + break; + } + } +} + +void PolicyEngineFuzzer::process() { + setUp(); + FuzzWvCdmEventListener fuzzWvCdmEventListener; + CdmSessionId sessionId = mFdp.ConsumeRandomLengthString(kMaxByte); + metrics::CryptoMetrics crypto_metrics; + std::unique_ptr cryptoSession( + wvcdm::CryptoSession::MakeCryptoSession(&crypto_metrics)); + std::unique_ptr policyEngine(new PolicyEngine( + sessionId, &fuzzWvCdmEventListener, cryptoSession.get())); + + while (mFdp.remaining_bytes()) { + auto invokePolicyEngineAPI = + mFdp.PickValueInArray>({ + [&]() { policyEngine->CanDecryptContent(mKeyId); }, + [&]() { policyEngine->CanUseKeyForSecurityLevel(mKeyId); }, + [&]() { policyEngine->OnTimerEvent(); }, + [&]() { + policyEngine->SetLicense( + mLicense, mFdp.ConsumeBool() /*defer_license_state_update*/); + }, + [&]() { + int32_t maxEntitledKey = + mFdp.ConsumeIntegralInRange(1, kMaxEntitledKey); + std::vector entitled_keys( + maxEntitledKey); + for (int i = 0; i < maxEntitledKey; ++i) { + entitled_keys[i].set_entitlement_key_id( + mFdp.ConsumeRandomLengthString(kMaxByte)); + entitled_keys[i].set_key_id( + mFdp.ConsumeBool() + ? mFdp.ConsumeRandomLengthString(kMaxByte) + : mKeyId); + } + policyEngine->SetEntitledLicenseKeys(entitled_keys); + }, + [&]() { policyEngine->SetLicenseForRelease(mLicense); }, + [&]() { policyEngine->BeginDecryption(); }, + [&]() { policyEngine->DecryptionEvent(); }, + [&]() { + policyEngine->UpdateLicense( + mLicense, mFdp.ConsumeBool() /*defer_license_state_update*/); + }, + [&]() { + policyEngine->UpdateLicenseState( + mFdp.ConsumeIntegral() /*current_time*/); + }, + [&]() { + int32_t width = mFdp.ConsumeIntegralInRange( + kMinResolution, kMaxResolution); + int32_t height = mFdp.ConsumeIntegralInRange( + kMinResolution, kMaxResolution); + policyEngine->NotifyResolution(width, height); + }, + [&]() { policyEngine->NotifySessionExpiration(); }, + [&]() { + CdmQueryMap query_info; + policyEngine->Query(&query_info); + }, + [&]() { + CdmKeyAllowedUsage key_usage; + policyEngine->QueryKeyAllowedUsage( + mKeyId, (mFdp.ConsumeBool() ? nullptr : &key_usage)); + }, + [&]() { + policyEngine->RestorePlaybackTimes( + mFdp.ConsumeIntegral() /*playback_start_time*/, + mFdp.ConsumeIntegral() /*last_playback_time*/, + mFdp.ConsumeIntegral() /*grace_period_end_time*/); + }, + [&]() { + policyEngine->HasLicenseOrRentalOrPlaybackDurationExpired( + mFdp.ConsumeIntegral()); + }, + }); + invokePolicyEngineAPI(); + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + PolicyEngineFuzzer policyEngineFuzzer(data, size); + policyEngineFuzzer.process(); + return 0; +}