Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4892544e authored by Kris Alder's avatar Kris Alder Committed by Automerger Merge Worker
Browse files

Merge "Added WriterFuzzerBase class" am: 4c51bbb3

Original change: https://android-review.googlesource.com/c/platform/frameworks/av/+/1533162

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I8eb5345ad7a40b4289a1522a562f73f7592388f4
parents 4ab211c4 4c51bbb3
Loading
Loading
Loading
Loading
+60 −0
Original line number Original line Diff line number Diff line
/******************************************************************************
 *
 * 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",
    ],
}
+46 −0
Original line number Original line Diff line number Diff line
# 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
+260 −0
Original line number Original line Diff line number Diff line
/*
 * 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> &currentTrack, 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();
}
+168 −0
Original line number Original line Diff line number Diff line
/******************************************************************************
 *
 * 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__