Merge "Added WriterFuzzerBase class"
diff --git a/media/libstagefright/writer_fuzzers/Android.bp b/media/libstagefright/writer_fuzzers/Android.bp
new file mode 100644
index 0000000..224aeb3
--- /dev/null
+++ b/media/libstagefright/writer_fuzzers/Android.bp
@@ -0,0 +1,60 @@
+/******************************************************************************
+ *
+ * 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
+ */
+cc_defaults {
+ name: "writer-fuzzerbase-defaults",
+ local_include_dirs: [
+ "include",
+ ],
+ export_include_dirs: [
+ "include",
+ ],
+ static_libs: [
+ "liblog",
+ "libstagefright_foundation",
+ "libstagefright",
+ ],
+ shared_libs: [
+ "libbinder",
+ "libcutils",
+ "libutils",
+ ],
+}
+
+cc_defaults {
+ name: "writer-fuzzer-defaults",
+ defaults: ["writer-fuzzerbase-defaults"],
+ static_libs: [
+ "libwriterfuzzerbase",
+ ],
+ fuzz_config: {
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 155276,
+ },
+}
+
+cc_library_static {
+ name: "libwriterfuzzerbase",
+ defaults: ["writer-fuzzerbase-defaults"],
+ srcs: [
+ "WriterFuzzerBase.cpp",
+ ],
+}
diff --git a/media/libstagefright/writer_fuzzers/README.md b/media/libstagefright/writer_fuzzers/README.md
new file mode 100644
index 0000000..037236a
--- /dev/null
+++ b/media/libstagefright/writer_fuzzers/README.md
@@ -0,0 +1,46 @@
+# Fuzzer for writers
+
+## Table of contents
+ [libwriterfuzzerbase](#WriterFuzzerBase)
+
+# <a name="WriterFuzzerBase"></a> Fuzzer for libwriterfuzzerbase
+All the writers have a common API - creating a writer, adding a source for
+all the tracks, etc. These common APIs have been abstracted in a base class
+called `WriterFuzzerBase` to ensure code is reused between fuzzer plugins.
+
+## Plugin Design Considerations
+The fuzzer plugin for writers is designed based on the understanding of the
+writer and tries to achieve the following:
+
+##### Maximize code coverage
+The configuration parameters are not hardcoded, but instead selected based on
+incoming data. This ensures more code paths are reached by the fuzzer.
+
+Fuzzer for writers supports the following parameters:
+1. Track Mime (parameter name: `mime`)
+2. Channel Count (parameter name: `channel-count`)
+3. Sample Rate (parameter name: `sample-rate`)
+4. Frame Height (parameter name: `height`)
+5. Frame Width (parameter name: `width`)
+
+| Parameter| Valid Values| Configured Value|
+|------------- |-------------| ----- |
+| `mime` | 0. `audio/3gpp` 1. `audio/amr-wb` 2. `audio/vorbis` 3. `audio/opus` 4. `audio/mp4a-latm` 5. `video/avc` 6. `video/hevc` 7. `video/mp4v-es` 8. `video/3gpp` 9. `video/x-vnd.on2.vp8` 10. `video/x-vnd.on2.vp9` | All the bits of 2nd byte of data for first track and 11th byte of data for second track (if present) modulus 10 |
+| `channel-count` | In the range `0 to INT32_MAX` | All the bits of 3rd byte to 6th bytes of data if first track is audio and 12th to 15th bytes of data if second track is audio |
+| `sample-rate` | In the range `1 to INT32_MAX` | All the bits of 7th byte to 10th bytes of data if first track is audio and 16th to 19th bytes of data if second track is audio |
+| `height` | In the range `0 to INT32_MAX` | All the bits of 3rd byte to 6th bytes of data if first track is video and 12th to 15th bytes of data if second track is video |
+| `width` | In the range `0 to INT32_MAX` | All the bits of 7th byte to 10th bytes of data if first track is video and 16th to 19th bytes of data if second track is video |
+
+This also ensures that the plugin is always deterministic for any given input.
+
+##### Maximize utilization of input data
+The plugin divides the entire input data into frames based on frame markers.
+If no frame marker is found then the entire input data is treated as single frame.
+
+This ensures that the plugin tolerates any kind of input (huge,
+malformed, etc) and thereby increasing the chance of identifying vulnerabilities.
+
+
+## References:
+ * http://llvm.org/docs/LibFuzzer.html
+ * https://github.com/google/oss-fuzz
diff --git a/media/libstagefright/writer_fuzzers/WriterFuzzerBase.cpp b/media/libstagefright/writer_fuzzers/WriterFuzzerBase.cpp
new file mode 100644
index 0000000..65593e7
--- /dev/null
+++ b/media/libstagefright/writer_fuzzers/WriterFuzzerBase.cpp
@@ -0,0 +1,260 @@
+/*
+ * 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 <utils/Log.h>
+
+#include "WriterFuzzerBase.h"
+
+using namespace android;
+
+/**
+ * Buffer source implementations to parse input file
+ */
+
+uint32_t WriterFuzzerBase::BufferSource::getNumTracks() {
+ uint32_t numTracks = 0;
+ if (mSize > sizeof(uint8_t)) {
+ numTracks = min(mData[0], kMaxTrackCount);
+ mReadIndex += sizeof(uint8_t);
+ }
+ return numTracks;
+}
+
+bool WriterFuzzerBase::BufferSource::searchForMarker(size_t startIndex) {
+ while (true) {
+ if (isMarker()) {
+ return true;
+ }
+ --mReadIndex;
+ if (mReadIndex < startIndex) {
+ break;
+ }
+ }
+ return false;
+}
+
+ConfigFormat WriterFuzzerBase::BufferSource::getConfigFormat(int32_t trackIndex) {
+ return mParams[trackIndex];
+}
+
+int32_t WriterFuzzerBase::BufferSource::getNumCsds(int32_t trackIndex) {
+ return mNumCsds[trackIndex];
+}
+
+vector<FrameData> WriterFuzzerBase::BufferSource::getFrameList(int32_t trackIndex) {
+ return mFrameList[trackIndex];
+}
+
+void WriterFuzzerBase::BufferSource::getFrameInfo() {
+ size_t readIndexStart = mReadIndex;
+ if (mSize - mReadIndex > kMarkerSize + kMarkerSuffixSize) {
+ bool isFrameAvailable = true;
+ size_t bytesRemaining = mSize;
+ mReadIndex = mSize - kMarkerSize;
+ while (isFrameAvailable) {
+ isFrameAvailable = searchForMarker(readIndexStart);
+ if (isFrameAvailable) {
+ size_t location = mReadIndex + kMarkerSize;
+ if (location + kMarkerSuffixSize >= bytesRemaining) {
+ break;
+ }
+ bool isCSD = isCSDMarker(location);
+ location += kMarkerSuffixSize;
+ uint8_t *framePtr = const_cast<uint8_t *>(&mData[location]);
+ size_t frameSize = bytesRemaining - location, bufferSize = 0;
+ uint8_t trackIndex = framePtr[0] % kMaxTrackCount;
+ ++framePtr;
+ uint8_t flags = 0;
+ int64_t pts = 0;
+ if (isCSD && frameSize > 1) {
+ flags |= kCodecConfigFlag;
+ pts = 0;
+ ++mNumCsds[trackIndex];
+ bufferSize = frameSize - 1;
+ } else if (frameSize > sizeof(uint8_t) + sizeof(int64_t) + 1) {
+ flags = flagTypes[framePtr[0] % size(flagTypes)];
+ ++framePtr;
+ copy(framePtr, framePtr + sizeof(int64_t), reinterpret_cast<uint8_t *>(&pts));
+ framePtr += sizeof(int64_t);
+ bufferSize = frameSize - (sizeof(uint8_t) + sizeof(int64_t)) - 1;
+ } else {
+ break;
+ }
+ mFrameList[trackIndex].insert(
+ mFrameList[trackIndex].begin(),
+ FrameData{static_cast<int32_t>(bufferSize), flags, pts, framePtr});
+ bytesRemaining -= (frameSize + kMarkerSize + kMarkerSuffixSize);
+ --mReadIndex;
+ }
+ }
+ }
+ if (mFrameList[0].empty() && mFrameList[1].empty()) {
+ /**
+ * Scenario where input data does not contain the custom frame markers.
+ * Hence feed the entire data as single frame.
+ */
+ mFrameList[0].emplace_back(
+ FrameData{static_cast<int32_t>(mSize - readIndexStart), 0, 0, mData + readIndexStart});
+ }
+}
+bool WriterFuzzerBase::BufferSource::getTrackInfo(int32_t trackIndex) {
+ if (mSize <= mReadIndex + 2 * sizeof(int) + sizeof(uint8_t)) {
+ return false;
+ }
+ size_t mimeTypeIdx = mData[mReadIndex] % kSupportedMimeTypes;
+ char *mime = (char *)supportedMimeTypes[mimeTypeIdx].c_str();
+ mParams[trackIndex].mime = mime;
+ ++mReadIndex;
+
+ if (!strncmp(mime, "audio/", 6)) {
+ copy(mData + mReadIndex, mData + mReadIndex + sizeof(int),
+ reinterpret_cast<char *>(&mParams[trackIndex].channelCount));
+ copy(mData + mReadIndex + sizeof(int), mData + mReadIndex + 2 * sizeof(int),
+ reinterpret_cast<char *>(&mParams[trackIndex].sampleRate));
+ } else {
+ copy(mData + mReadIndex, mData + mReadIndex + sizeof(int),
+ reinterpret_cast<char *>(&mParams[trackIndex].height));
+ copy(mData + mReadIndex + sizeof(int), mData + mReadIndex + 2 * sizeof(int),
+ reinterpret_cast<char *>(&mParams[trackIndex].width));
+ }
+ mReadIndex += 2 * sizeof(int);
+ return true;
+}
+
+void writeHeaderBuffers(vector<FrameData> &bufferInfo, sp<AMessage> &format, int32_t numCsds) {
+ char csdName[kMaxCSDStrlen];
+ for (int csdId = 0; csdId < numCsds; ++csdId) {
+ int32_t flags = bufferInfo[csdId].flags;
+ if (flags == kCodecConfigFlag) {
+ sp<ABuffer> csdBuffer =
+ ABuffer::CreateAsCopy((void *)bufferInfo[csdId].buf, bufferInfo[csdId].size);
+ if (csdBuffer.get() == nullptr || csdBuffer->base() == nullptr) {
+ return;
+ }
+ snprintf(csdName, sizeof(csdName), "csd-%d", csdId);
+ format->setBuffer(csdName, csdBuffer);
+ }
+ }
+}
+
+bool WriterFuzzerBase::createOutputFile() {
+ mFd = memfd_create(mOutputFileName.c_str(), MFD_ALLOW_SEALING);
+ if (mFd == -1) {
+ return false;
+ }
+ return true;
+}
+
+void WriterFuzzerBase::addWriterSource(int32_t trackIndex) {
+ ConfigFormat params = mBufferSource->getConfigFormat(trackIndex);
+ sp<AMessage> format = new AMessage;
+ format->setString("mime", params.mime);
+ if (!strncmp(params.mime, "audio/", 6)) {
+ if (!strncmp(params.mime, "audio/3gpp", 10)) {
+ params.channelCount = 1;
+ params.sampleRate = 8000;
+ } else if (!strncmp(params.mime, "audio/amr-wb", 12)) {
+ params.channelCount = 1;
+ params.sampleRate = 16000;
+ } else {
+ params.sampleRate = max(1, params.sampleRate);
+ }
+ format->setInt32("channel-count", params.channelCount);
+ format->setInt32("sample-rate", params.sampleRate);
+ } else {
+ format->setInt32("width", params.width);
+ format->setInt32("height", params.height);
+ }
+ int32_t numCsds = mBufferSource->getNumCsds(trackIndex);
+ if (numCsds) {
+ vector<FrameData> mFrames = mBufferSource->getFrameList(trackIndex);
+ writeHeaderBuffers(mFrames, format, numCsds);
+ }
+ sp<MetaData> trackMeta = new MetaData;
+ convertMessageToMetaData(format, trackMeta);
+ mCurrentTrack[trackIndex] = new MediaAdapter(trackMeta);
+ mWriter->addSource(mCurrentTrack[trackIndex]);
+}
+
+void WriterFuzzerBase::start() {
+ mFileMeta->setInt32(kKeyRealTimeRecording, false);
+ mWriter->start(mFileMeta.get());
+}
+
+void WriterFuzzerBase::sendBuffersToWriter(sp<MediaAdapter> ¤tTrack, int32_t trackIndex) {
+ int32_t numCsds = mBufferSource->getNumCsds(trackIndex);
+ vector<FrameData> bufferInfo = mBufferSource->getFrameList(trackIndex);
+ int32_t range = bufferInfo.size();
+ for (int idx = numCsds; idx < range; ++idx) {
+ sp<ABuffer> buffer = new ABuffer((void *)bufferInfo[idx].buf, bufferInfo[idx].size);
+ MediaBuffer *mediaBuffer = new MediaBuffer(buffer);
+
+ // Released in MediaAdapter::signalBufferReturned().
+ mediaBuffer->add_ref();
+ mediaBuffer->set_range(buffer->offset(), buffer->size());
+ MetaDataBase &sampleMetaData = mediaBuffer->meta_data();
+ sampleMetaData.setInt64(kKeyTime, bufferInfo[idx].timeUs);
+
+ // Just set the kKeyDecodingTime as the presentation time for now.
+ sampleMetaData.setInt64(kKeyDecodingTime, bufferInfo[idx].timeUs);
+ if (bufferInfo[idx].flags == 1) {
+ sampleMetaData.setInt32(kKeyIsSyncFrame, true);
+ }
+
+ // This pushBuffer will wait until the mediaBuffer is consumed.
+ currentTrack->pushBuffer(mediaBuffer);
+ }
+}
+
+void WriterFuzzerBase::processData(const uint8_t *data, size_t size) {
+ if (!createOutputFile()) {
+ return;
+ }
+ if (!createWriter()) {
+ return;
+ }
+ mBufferSource = new BufferSource(data, size);
+ if (!mBufferSource) {
+ return;
+ }
+ mNumTracks = mBufferSource->getNumTracks();
+ if (mNumTracks > 0) {
+ for (int32_t idx = 0; idx < mNumTracks; ++idx) {
+ if (!mBufferSource->getTrackInfo(idx)) {
+ if (idx == 0) {
+ delete mBufferSource;
+ return;
+ }
+ mNumTracks = idx;
+ break;
+ }
+ }
+ mBufferSource->getFrameInfo();
+ for (int32_t idx = 0; idx < mNumTracks; ++idx) {
+ addWriterSource(idx);
+ }
+ start();
+ for (int32_t idx = 0; idx < mNumTracks; ++idx) {
+ sendBuffersToWriter(mCurrentTrack[idx], idx);
+ }
+ for (int32_t idx = 0; idx < mNumTracks; ++idx) {
+ if (mCurrentTrack[idx]) {
+ mCurrentTrack[idx]->stop();
+ }
+ }
+ }
+ delete mBufferSource;
+ mWriter->stop();
+}
diff --git a/media/libstagefright/writer_fuzzers/include/WriterFuzzerBase.h b/media/libstagefright/writer_fuzzers/include/WriterFuzzerBase.h
new file mode 100644
index 0000000..d819d43
--- /dev/null
+++ b/media/libstagefright/writer_fuzzers/include/WriterFuzzerBase.h
@@ -0,0 +1,168 @@
+/******************************************************************************
+ *
+ * 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
+ */
+
+#ifndef __WRITER_FUZZER_BASE_H__
+#define __WRITER_FUZZER_BASE_H__
+
+#include <media/stagefright/MediaAdapter.h>
+#include <media/stagefright/MediaWriter.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/Utils.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <algorithm>
+#include <cstring>
+#include <vector>
+
+using namespace std;
+
+constexpr uint32_t kMimeSize = 128;
+constexpr uint8_t kMaxTrackCount = 2;
+constexpr uint32_t kMaxCSDStrlen = 16;
+constexpr uint32_t kCodecConfigFlag = 32;
+
+namespace android {
+
+struct ConfigFormat {
+ char* mime;
+ int32_t width;
+ int32_t height;
+ int32_t sampleRate;
+ int32_t channelCount;
+};
+
+struct FrameData {
+ int32_t size;
+ uint8_t flags;
+ int64_t timeUs;
+ const uint8_t* buf;
+};
+
+static string supportedMimeTypes[] = {
+ "audio/3gpp", "audio/amr-wb", "audio/vorbis", "audio/opus",
+ "audio/mp4a-latm", "video/avc", "video/hevc", "video/mp4v-es",
+ "video/3gpp", "video/x-vnd.on2.vp8", "video/x-vnd.on2.vp9",
+};
+
+enum {
+ DEFAULT_FLAG = 0,
+ SYNC_FLAG = 1,
+ ENCRYPTED_FLAG = 2,
+};
+
+static uint8_t flagTypes[] = {DEFAULT_FLAG, SYNC_FLAG, ENCRYPTED_FLAG};
+
+class WriterFuzzerBase {
+ public:
+ WriterFuzzerBase() = default;
+ virtual ~WriterFuzzerBase() {
+ if (mFileMeta) {
+ mFileMeta.clear();
+ mFileMeta = nullptr;
+ }
+ if (mWriter) {
+ mWriter.clear();
+ mWriter = nullptr;
+ }
+ for (int32_t idx = 0; idx < kMaxTrackCount; ++idx) {
+ if (mCurrentTrack[idx]) {
+ mCurrentTrack[idx]->stop();
+ mCurrentTrack[idx].clear();
+ mCurrentTrack[idx] = nullptr;
+ }
+ }
+ close(mFd);
+ };
+
+ /** Function to create the media writer component.
+ * To be implemented by the derived class.
+ */
+ virtual bool createWriter() = 0;
+
+ /** Parent class functions to be reused by derived class.
+ * These are common for all media writer components.
+ */
+ bool createOutputFile();
+
+ void addWriterSource(int32_t trackIndex);
+
+ void start();
+
+ void sendBuffersToWriter(sp<MediaAdapter>& currentTrack, int32_t trackIndex);
+
+ void processData(const uint8_t* data, size_t size);
+
+ protected:
+ class BufferSource {
+ public:
+ BufferSource(const uint8_t* data, size_t size) : mData(data), mSize(size), mReadIndex(0) {}
+ ~BufferSource() {
+ mData = nullptr;
+ mSize = 0;
+ mReadIndex = 0;
+ for (int32_t idx = 0; idx < kMaxTrackCount; ++idx) {
+ mFrameList[idx].clear();
+ }
+ }
+ uint32_t getNumTracks();
+ bool getTrackInfo(int32_t trackIndex);
+ void getFrameInfo();
+ ConfigFormat getConfigFormat(int32_t trackIndex);
+ int32_t getNumCsds(int32_t trackIndex);
+ vector<FrameData> getFrameList(int32_t trackIndex);
+
+ private:
+ bool isMarker() { return (memcmp(&mData[mReadIndex], kMarker, kMarkerSize) == 0); }
+
+ bool isCSDMarker(size_t position) {
+ return (memcmp(&mData[position], kCsdMarkerSuffix, kMarkerSuffixSize) == 0);
+ }
+
+ bool searchForMarker(size_t startIndex);
+
+ const uint8_t* mData = nullptr;
+ size_t mSize = 0;
+ size_t mReadIndex = 0;
+ ConfigFormat mParams[kMaxTrackCount] = {};
+ int32_t mNumCsds[kMaxTrackCount] = {0};
+ vector<FrameData> mFrameList[kMaxTrackCount];
+
+ static constexpr int kSupportedMimeTypes = size(supportedMimeTypes);
+ static constexpr uint8_t kMarker[] = "_MARK";
+ static constexpr uint8_t kCsdMarkerSuffix[] = "_H_";
+ static constexpr uint8_t kFrameMarkerSuffix[] = "_F_";
+ // All markers should be 5 bytes long ( sizeof '_MARK' which is 5)
+ static constexpr size_t kMarkerSize = (sizeof(kMarker) - 1);
+ // All marker types should be 3 bytes long ('_H_', '_F_')
+ static constexpr size_t kMarkerSuffixSize = 3;
+ };
+
+ BufferSource* mBufferSource = nullptr;
+ int32_t mFd = -1;
+ uint32_t mNumTracks = 0;
+ string mOutputFileName = "writer.out";
+ sp<MediaWriter> mWriter = nullptr;
+ sp<MetaData> mFileMeta = nullptr;
+ sp<MediaAdapter> mCurrentTrack[kMaxTrackCount] = {};
+};
+
+} // namespace android
+
+#endif // __WRITER_FUZZER_BASE_H__