/****************************************************************************** * * Copyright (C) 2020 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. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #include #include using namespace android; class LinearBuffer : public C2Buffer { public: explicit LinearBuffer(const std::shared_ptr& block) : C2Buffer({block->share(block->offset(), block->size(), ::C2Fence())}) {} explicit LinearBuffer(const std::shared_ptr& block, size_t size) : C2Buffer({block->share(block->offset(), size, ::C2Fence())}) {} }; /** * Handle Callback functions onWorkDone_nb(), onTripped_nb(), onError_nb() for C2 Components */ struct CodecListener : public C2Component::Listener { public: CodecListener(const std::function comp, std::list>& workItems)> fn = nullptr) : callBack(fn) {} virtual void onWorkDone_nb(const std::weak_ptr comp, std::list> workItems) { if (callBack) { callBack(comp, workItems); } } virtual void onTripped_nb(const std::weak_ptr comp, const std::vector> settingResults) { (void)comp; (void)settingResults; } virtual void onError_nb(const std::weak_ptr comp, uint32_t errorCode) { (void)comp; (void)errorCode; } std::function comp, std::list>& workItems)> callBack; }; /** * Buffer source implementations to identify a frame and its size */ bool Codec2Fuzzer::BufferSource::searchForMarker() { while (true) { if (isMarker()) { return true; } --mReadIndex; if (mReadIndex > mSize) { break; } } return false; } void Codec2Fuzzer::BufferSource::parse() { bool isFrameAvailable = true; size_t bytesRemaining = mSize; while (isFrameAvailable) { isFrameAvailable = searchForMarker(); if (isFrameAvailable) { size_t location = mReadIndex + kMarkerSize; bool isCSD = isCSDMarker(location); location += kMarkerSuffixSize; uint8_t* framePtr = const_cast(&mData[location]); size_t frameSize = bytesRemaining - location; uint32_t flags = 0; if (mFrameList.empty()) { flags |= C2FrameData::FLAG_END_OF_STREAM; } else if (isCSD) { flags |= C2FrameData::FLAG_CODEC_CONFIG; } mFrameList.emplace_back(std::make_tuple(framePtr, frameSize, flags)); bytesRemaining -= (frameSize + kMarkerSize + kMarkerSuffixSize); --mReadIndex; } } if (mFrameList.empty()) { /** * Scenario where input data does not contain the custom frame markers. * Hence feed the entire data as single frame. */ mFrameList.emplace_back( std::make_tuple(const_cast(mData), 0, C2FrameData::FLAG_END_OF_STREAM)); mFrameList.emplace_back( std::make_tuple(const_cast(mData), mSize, C2FrameData::FLAG_CODEC_CONFIG)); } } FrameData Codec2Fuzzer::BufferSource::getFrame() { FrameData frame = mFrameList.back(); mFrameList.pop_back(); return frame; } void Codec2Fuzzer::handleWorkDone(std::weak_ptr comp, std::list>& workItems) { (void)comp; for (std::unique_ptr& work : workItems) { if (!work->worklets.empty()) { if (work->worklets.front()->output.flags != C2FrameData::FLAG_INCOMPLETE) { mEos = (work->worklets.front()->output.flags & C2FrameData::FLAG_END_OF_STREAM) != 0; work->input.buffers.clear(); work->worklets.clear(); { std::unique_lock lock(mQueueLock); mWorkQueue.push_back(std::move(work)); mQueueCondition.notify_all(); } if (mEos) { { std::lock_guard waitForDecodeComplete(mDecodeCompleteMutex); } mConditionalVariable.notify_one(); } } } } } bool Codec2Fuzzer::initDecoder() { std::vector> codec2FactoryFunc; codec2FactoryFunc.emplace_back( std::make_tuple(C2COMPONENTNAME, &CreateCodec2Factory, &DestroyCodec2Factory)); std::shared_ptr componentStore = GetTestComponentStore(codec2FactoryFunc); if (!componentStore) { return false; } std::shared_ptr allocatorStore = GetCodec2PlatformAllocatorStore(); if (!allocatorStore) { return false; } c2_status_t status = allocatorStore->fetchAllocator(C2AllocatorStore::DEFAULT_LINEAR, &mLinearAllocator); if (status != C2_OK) { return false; } mLinearPool = std::make_shared(mLinearAllocator, ++mBlockPoolId); if (!mLinearPool) { return false; } for (int32_t i = 0; i < kNumberOfC2WorkItems; ++i) { mWorkQueue.emplace_back(new C2Work); } status = componentStore->createComponent(C2COMPONENTNAME, &mComponent); if (status != C2_OK) { return false; } status = componentStore->createInterface(C2COMPONENTNAME, &mInterface); if (status != C2_OK) { return false; } C2ComponentKindSetting kind; C2ComponentDomainSetting domain; status = mInterface->query_vb({&kind, &domain}, {}, C2_MAY_BLOCK, nullptr); if (status != C2_OK) { return false; } std::vector configParams; if (domain.value == DOMAIN_VIDEO) { C2StreamPictureSizeInfo::input inputSize(0u, kWidthOfVideo, kHeightOfVideo); configParams.push_back(&inputSize); } else if (domain.value == DOMAIN_AUDIO) { C2StreamSampleRateInfo::output sampleRateInfo(0u, kSamplingRateOfAudio); C2StreamChannelCountInfo::output channelCountInfo(0u, kChannelsOfAudio); configParams.push_back(&sampleRateInfo); configParams.push_back(&channelCountInfo); } mListener.reset(new CodecListener( [this](std::weak_ptr comp, std::list>& workItems) { handleWorkDone(comp, workItems); })); if (!mListener) { return false; } status = mComponent->setListener_vb(mListener, C2_DONT_BLOCK); if (status != C2_OK) { return false; } std::vector> failures; componentStore->config_sm(configParams, &failures); if (failures.size() != 0) { return false; } status = mComponent->start(); if (status != C2_OK) { return false; } return true; } void Codec2Fuzzer::deInitDecoder() { mComponent->stop(); mComponent->reset(); mComponent->release(); mComponent = nullptr; } void Codec2Fuzzer::decodeFrames(const uint8_t* data, size_t size) { mBufferSource = new BufferSource(data, size); if (!mBufferSource) { return; } mBufferSource->parse(); c2_status_t status = C2_OK; size_t numFrames = 0; while (!mBufferSource->isEos()) { uint8_t* frame = nullptr; size_t frameSize = 0; FrameData frameData = mBufferSource->getFrame(); frame = std::get<0>(frameData); frameSize = std::get<1>(frameData); std::unique_ptr work; { std::unique_lock lock(mQueueLock); if (mWorkQueue.empty()) mQueueCondition.wait_for(lock, kC2FuzzerTimeOut); if (!mWorkQueue.empty()) { work.swap(mWorkQueue.front()); mWorkQueue.pop_front(); } else { return; } } work->input.flags = (C2FrameData::flags_t)std::get<2>(frameData); work->input.ordinal.timestamp = 0; work->input.ordinal.frameIndex = ++numFrames; work->input.buffers.clear(); int32_t alignedSize = C2FUZZER_ALIGN(frameSize, PAGE_SIZE); std::shared_ptr block; status = mLinearPool->fetchLinearBlock( alignedSize, {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, &block); if (status != C2_OK || block == nullptr) { return; } C2WriteView view = block->map().get(); if (view.error() != C2_OK) { return; } memcpy(view.base(), frame, frameSize); work->input.buffers.emplace_back(new LinearBuffer(block, frameSize)); work->worklets.clear(); work->worklets.emplace_back(new C2Worklet); std::list> items; items.push_back(std::move(work)); status = mComponent->queue_nb(&items); if (status != C2_OK) { return; } } std::unique_lock waitForDecodeComplete(mDecodeCompleteMutex); mConditionalVariable.wait_for(waitForDecodeComplete, kC2FuzzerTimeOut, [this] { return mEos; }); std::list> c2flushedWorks; mComponent->flush_sm(C2Component::FLUSH_COMPONENT, &c2flushedWorks); delete mBufferSource; } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { if (size < 1) { return 0; } Codec2Fuzzer* codec = new Codec2Fuzzer(); if (!codec) { return 0; } if (codec->initDecoder()) { codec->decodeFrames(data, size); } delete codec; return 0; }