| /* |
| * Copyright (C) 2022 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 <media/NdkMediaCrypto.h> |
| #include <media/NdkMediaDrm.h> |
| #include "fuzzer/FuzzedDataProvider.h" |
| |
| constexpr int32_t kMinBytes = 1; |
| constexpr int32_t kMaxBytes = 256; |
| constexpr int32_t kMinParamVal = 0; |
| constexpr int32_t kMaxParamVal = 3; |
| constexpr int32_t kMediaUUIdSize = sizeof(AMediaUUID); |
| constexpr int32_t kMinProvisionResponseSize = 0; |
| constexpr int32_t kMaxProvisionResponseSize = 16; |
| constexpr int32_t kMessageSize = 16; |
| constexpr int32_t kMinAPIcase = 0; |
| constexpr int32_t kMaxdecryptEncryptAPIs = 10; |
| constexpr int32_t kMaxpropertyAPIs = 3; |
| constexpr int32_t kMaxsetListenerAPIs = 2; |
| constexpr int32_t kMaxndkDrmAPIs = 3; |
| uint8_t signature[kMessageSize]; |
| |
| enum MediaUUID { INVALID_UUID = 0, PSSH_BOX_UUID, CLEARKEY_UUID, kMaxValue = CLEARKEY_UUID }; |
| |
| constexpr uint8_t kCommonPsshBoxUUID[] = {0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, |
| 0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B}; |
| |
| constexpr uint8_t kClearKeyUUID[] = {0xE2, 0x71, 0x9D, 0x58, 0xA9, 0x85, 0xB3, 0xC9, |
| 0x78, 0x1A, 0xB0, 0x30, 0xAF, 0x78, 0xD3, 0x0E}; |
| |
| constexpr uint8_t kInvalidUUID[] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, |
| 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80}; |
| |
| uint8_t kClearkeyPssh[] = { |
| // BMFF box header (4 bytes size + 'pssh') |
| 0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68, |
| // full box header (version = 1 flags = 0) |
| 0x01, 0x00, 0x00, 0x00, |
| // system id |
| 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, |
| 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, |
| // number of key ids |
| 0x00, 0x00, 0x00, 0x01, |
| // key id |
| 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, |
| 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, |
| // size of data, must be zero |
| 0x00, 0x00, 0x00, 0x00}; |
| |
| std::string kPropertyName = "clientId"; |
| std::string kMimeType[] = {"video/mp4", "audio/mp4"}; |
| std::string kCipherAlgorithm[] = {"AES/CBC/NoPadding", ""}; |
| std::string kMacAlgorithm[] = {"HmacSHA256", ""}; |
| |
| class NdkMediaDrmFuzzer { |
| public: |
| NdkMediaDrmFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; |
| void invokeNdkDrm(); |
| static void KeysChangeListener(AMediaDrm* drm, const AMediaDrmSessionId* sessionId, |
| const AMediaDrmKeyStatus* keysStatus, size_t numKeys, |
| bool hasNewUsableKey) { |
| (void)drm; |
| (void)sessionId; |
| (void)keysStatus; |
| (void)numKeys; |
| (void)hasNewUsableKey; |
| }; |
| |
| static void ExpirationUpdateListener(AMediaDrm* drm, const AMediaDrmSessionId* sessionId, |
| int64_t expiryTimeInMS) { |
| (void)drm; |
| (void)sessionId; |
| (void)expiryTimeInMS; |
| }; |
| |
| static void listener(AMediaDrm* drm, const AMediaDrmSessionId* sessionId, |
| AMediaDrmEventType eventType, int extra, const uint8_t* data, |
| size_t dataSize) { |
| (void)drm; |
| (void)sessionId; |
| (void)eventType; |
| (void)extra; |
| (void)data; |
| (void)dataSize; |
| } |
| |
| private: |
| FuzzedDataProvider mFdp; |
| void invokeDrmCreatePlugin(); |
| void invokeDrmSetListener(); |
| void invokeDrmPropertyAPI(); |
| void invokeDrmDecryptEncryptAPI(); |
| void invokeDrmSecureStopAPI(); |
| AMediaDrmSessionId mSessionId = {}; |
| AMediaDrm* mDrm = nullptr; |
| }; |
| |
| void NdkMediaDrmFuzzer::invokeDrmCreatePlugin() { |
| const uint8_t* mediaUUID = nullptr; |
| uint32_t uuidEnum = mFdp.ConsumeEnum<MediaUUID>(); |
| switch (uuidEnum) { |
| case INVALID_UUID: { |
| mediaUUID = kInvalidUUID; |
| break; |
| } |
| case PSSH_BOX_UUID: { |
| mediaUUID = kCommonPsshBoxUUID; |
| break; |
| } |
| case CLEARKEY_UUID: |
| default: { |
| mediaUUID = kClearKeyUUID; |
| break; |
| } |
| } |
| mDrm = AMediaDrm_createByUUID(mediaUUID); |
| } |
| |
| void NdkMediaDrmFuzzer::invokeDrmSecureStopAPI() { |
| // get maximum number of secure stops |
| AMediaDrmSecureStop secureStops; |
| size_t numSecureStops = kMaxParamVal; |
| // The API behavior could change based on the drm object (clearkey or |
| // psshbox) This API detects secure stops msg and release them. |
| AMediaDrm_getSecureStops(mDrm, &secureStops, &numSecureStops); |
| AMediaDrm_releaseSecureStops(mDrm, &secureStops); |
| } |
| |
| void NdkMediaDrmFuzzer::invokeDrmSetListener() { |
| int32_t setListenerAPI = mFdp.ConsumeIntegralInRange<size_t>(kMinAPIcase, kMaxsetListenerAPIs); |
| switch (setListenerAPI) { |
| case 0: { // set on key change listener |
| AMediaDrm_setOnKeysChangeListener(mDrm, KeysChangeListener); |
| break; |
| } |
| case 1: { // set on expiration on update listener |
| AMediaDrm_setOnExpirationUpdateListener(mDrm, ExpirationUpdateListener); |
| break; |
| } |
| case 2: |
| default: { // set on event listener |
| AMediaDrm_setOnEventListener(mDrm, listener); |
| break; |
| } |
| } |
| } |
| |
| void NdkMediaDrmFuzzer::invokeDrmPropertyAPI() { |
| int32_t propertyAPI = mFdp.ConsumeIntegralInRange<size_t>(kMinAPIcase, kMaxpropertyAPIs); |
| switch (propertyAPI) { |
| case 0: { // set property byte array |
| uint8_t value[kMediaUUIdSize]; |
| std::string name = |
| mFdp.ConsumeBool() ? kPropertyName : mFdp.ConsumeRandomLengthString(kMaxBytes); |
| const char* propertyName = name.c_str(); |
| AMediaDrm_setPropertyByteArray(mDrm, propertyName, value, sizeof(value)); |
| break; |
| } |
| case 1: { // get property in byte array |
| AMediaDrmByteArray array; |
| std::string name = |
| mFdp.ConsumeBool() ? kPropertyName : mFdp.ConsumeRandomLengthString(kMaxBytes); |
| const char* propertyName = name.c_str(); |
| AMediaDrm_getPropertyByteArray(mDrm, propertyName, &array); |
| break; |
| } |
| case 2: { // set string type property |
| std::string propertyName = mFdp.ConsumeRandomLengthString(kMaxBytes); |
| std::string value = mFdp.ConsumeRandomLengthString(kMaxBytes); |
| AMediaDrm_setPropertyString(mDrm, propertyName.c_str(), value.c_str()); |
| break; |
| } |
| case 3: |
| default: { // get property in string |
| const char* stringValue = nullptr; |
| std::string propertyName = mFdp.ConsumeRandomLengthString(kMaxBytes); |
| AMediaDrm_getPropertyString(mDrm, propertyName.c_str(), &stringValue); |
| break; |
| } |
| } |
| } |
| |
| void NdkMediaDrmFuzzer::invokeDrmDecryptEncryptAPI() { |
| int32_t decryptEncryptAPI = |
| mFdp.ConsumeIntegralInRange<size_t>(kMinAPIcase, kMaxdecryptEncryptAPIs); |
| switch (decryptEncryptAPI) { |
| case 0: { // Check if crypto scheme is supported |
| std::string mimeType = mFdp.ConsumeBool() ? mFdp.PickValueInArray(kMimeType) |
| : mFdp.ConsumeRandomLengthString(kMaxBytes); |
| AMediaDrm_isCryptoSchemeSupported(kClearKeyUUID, mimeType.c_str()); |
| break; |
| } |
| case 1: { // get a provision request byte array |
| const uint8_t* legacyRequest; |
| size_t legacyRequestSize = 1; |
| const char* legacyDefaultUrl; |
| AMediaDrm_getProvisionRequest(mDrm, &legacyRequest, &legacyRequestSize, |
| &legacyDefaultUrl); |
| break; |
| } |
| case 2: { // provide a response to the DRM engine plugin |
| const int32_t provisionresponseSize = mFdp.ConsumeIntegralInRange<size_t>( |
| kMinProvisionResponseSize, kMaxProvisionResponseSize); |
| uint8_t provisionResponse[provisionresponseSize]; |
| AMediaDrm_provideProvisionResponse(mDrm, provisionResponse, sizeof(provisionResponse)); |
| break; |
| } |
| case 3: { // get key request |
| const uint8_t* keyRequest = nullptr; |
| size_t keyRequestSize = 0; |
| std::string mimeType = mFdp.ConsumeBool() ? mFdp.PickValueInArray(kMimeType) |
| : mFdp.ConsumeRandomLengthString(kMaxBytes); |
| size_t numOptionalParameters = |
| mFdp.ConsumeIntegralInRange<size_t>(kMinParamVal, kMaxParamVal); |
| AMediaDrmKeyValue optionalParameters[numOptionalParameters]; |
| std::string keys[numOptionalParameters]; |
| std::string values[numOptionalParameters]; |
| for (int i = 0; i < numOptionalParameters; ++i) { |
| keys[i] = mFdp.ConsumeRandomLengthString(kMaxBytes); |
| values[i] = mFdp.ConsumeRandomLengthString(kMaxBytes); |
| optionalParameters[i].mKey = keys[i].c_str(); |
| optionalParameters[i].mValue = values[i].c_str(); |
| } |
| AMediaDrmKeyType keyType = (AMediaDrmKeyType)mFdp.ConsumeIntegralInRange<int>( |
| KEY_TYPE_STREAMING, KEY_TYPE_RELEASE); |
| AMediaDrm_getKeyRequest(mDrm, &mSessionId, kClearkeyPssh, sizeof(kClearkeyPssh), |
| mimeType.c_str(), keyType, optionalParameters, |
| numOptionalParameters, &keyRequest, &keyRequestSize); |
| break; |
| } |
| case 4: { // query key status |
| size_t numPairs = mFdp.ConsumeIntegralInRange<size_t>(kMinParamVal, kMaxParamVal); |
| AMediaDrmKeyValue keyStatus[numPairs]; |
| AMediaDrm_queryKeyStatus(mDrm, &mSessionId, keyStatus, &numPairs); |
| break; |
| } |
| case 5: { // provide key response |
| std::string key = mFdp.ConsumeRandomLengthString(kMaxBytes); |
| const char* keyResponse = key.c_str(); |
| AMediaDrmKeySetId keySetId; |
| AMediaDrm_provideKeyResponse(mDrm, &mSessionId, |
| reinterpret_cast<const uint8_t*>(keyResponse), |
| sizeof(keyResponse), &keySetId); |
| break; |
| } |
| case 6: { // restore key |
| AMediaDrmKeySetId keySetId; |
| AMediaDrm_restoreKeys(mDrm, &mSessionId, &keySetId); |
| break; |
| } |
| |
| case 7: { // Check signature verification using the specified Algorithm |
| std::string algorithm = kMacAlgorithm[mFdp.ConsumeBool()]; |
| std::vector<uint8_t> keyId = mFdp.ConsumeBytes<uint8_t>( |
| mFdp.ConsumeIntegralInRange<size_t>(kMinBytes, kMaxBytes)); |
| std::vector<uint8_t> message = mFdp.ConsumeBytes<uint8_t>( |
| mFdp.ConsumeIntegralInRange<size_t>(kMinBytes, kMaxBytes)); |
| AMediaDrm_verify(mDrm, &mSessionId, algorithm.c_str(), keyId.data(), message.data(), |
| message.size(), signature, sizeof(signature)); |
| break; |
| } |
| case 8: { // Generate a signature using the specified Algorithm |
| std::string algorithm = kMacAlgorithm[mFdp.ConsumeBool()]; |
| std::vector<uint8_t> keyId = mFdp.ConsumeBytes<uint8_t>( |
| mFdp.ConsumeIntegralInRange<size_t>(kMinBytes, kMaxBytes)); |
| std::vector<uint8_t> message = mFdp.ConsumeBytes<uint8_t>( |
| mFdp.ConsumeIntegralInRange<size_t>(kMinBytes, kMaxBytes)); |
| size_t signatureSize = sizeof(signature); |
| AMediaDrm_sign(mDrm, &mSessionId, algorithm.c_str(), keyId.data(), message.data(), |
| message.size(), signature, &signatureSize); |
| break; |
| } |
| case 9: { // Decrypt the data using algorithm |
| std::string algorithm = kCipherAlgorithm[mFdp.ConsumeBool()]; |
| std::vector<uint8_t> keyId = mFdp.ConsumeBytes<uint8_t>( |
| mFdp.ConsumeIntegralInRange<size_t>(kMinBytes, kMaxBytes)); |
| std::vector<uint8_t> iv = mFdp.ConsumeBytes<uint8_t>( |
| mFdp.ConsumeIntegralInRange<size_t>(kMinBytes, kMaxBytes)); |
| std::vector<uint8_t> input = mFdp.ConsumeBytes<uint8_t>( |
| mFdp.ConsumeIntegralInRange<size_t>(kMinBytes, kMaxBytes)); |
| uint8_t output[kMessageSize]; |
| AMediaDrm_decrypt(mDrm, &mSessionId, algorithm.c_str(), keyId.data(), iv.data(), |
| input.data(), output, input.size()); |
| break; |
| } |
| case 10: |
| default: { // Encrypt the data using algorithm |
| std::string algorithm = kCipherAlgorithm[mFdp.ConsumeBool()]; |
| std::vector<uint8_t> keyId = mFdp.ConsumeBytes<uint8_t>( |
| mFdp.ConsumeIntegralInRange<size_t>(kMinBytes, kMaxBytes)); |
| std::vector<uint8_t> iv = mFdp.ConsumeBytes<uint8_t>( |
| mFdp.ConsumeIntegralInRange<size_t>(kMinBytes, kMaxBytes)); |
| std::vector<uint8_t> input = mFdp.ConsumeBytes<uint8_t>( |
| mFdp.ConsumeIntegralInRange<size_t>(kMinBytes, kMaxBytes)); |
| uint8_t output[kMessageSize]; |
| AMediaDrm_encrypt(mDrm, &mSessionId, algorithm.c_str(), keyId.data(), iv.data(), |
| input.data(), output, input.size()); |
| break; |
| } |
| } |
| AMediaDrm_removeKeys(mDrm, &mSessionId); |
| } |
| |
| void NdkMediaDrmFuzzer::invokeNdkDrm() { |
| while (mFdp.remaining_bytes() > 0) { |
| // The API is called at start as it creates a AMediaDrm Object. |
| // mDrm AMediaDrm object is used in the below APIs. |
| invokeDrmCreatePlugin(); |
| if (mDrm) { |
| // The API opens session and returns "mSessionId" session Id. |
| // "mSessionId" is required in the below APIs. |
| AMediaDrm_openSession(mDrm, &mSessionId); |
| int32_t ndkDrmAPI = mFdp.ConsumeIntegralInRange<size_t>(kMinAPIcase, kMaxndkDrmAPIs); |
| switch (ndkDrmAPI) { |
| case 0: { |
| invokeDrmDecryptEncryptAPI(); |
| break; |
| } |
| case 1: { |
| invokeDrmPropertyAPI(); |
| break; |
| } |
| case 2: { |
| invokeDrmSetListener(); |
| break; |
| } |
| case 3: |
| default: { |
| invokeDrmSecureStopAPI(); |
| break; |
| } |
| } |
| AMediaDrm_closeSession(mDrm, &mSessionId); |
| AMediaDrm_release(mDrm); |
| } |
| } |
| } |
| |
| extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
| NdkMediaDrmFuzzer ndkMediaDrmFuzzer(data, size); |
| ndkMediaDrmFuzzer.invokeNdkDrm(); |
| return 0; |
| } |