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

Commit b09a1d23 authored by Steven Moreland's avatar Steven Moreland
Browse files

binder_parcel_fuzzer: support owned Parcels

Some codepaths in Parcel are different when the Parcel
is owned. This allows fillRandomParcel to also sometimes
cause the returned Parcel be a view of a filled out
Parcel. This increases the possible impact and bug
finding abilities of all of our AIDL fuzzers as well.

Ignore-AOSP-First: fuzzing
Bug: 369404061
Test: binder_parcel_fuzzer
Change-Id: Ib19a0cbd74d48e18ba36cff56202541105ef9163
parent 750d69b2
Loading
Loading
Loading
Loading
+61 −0
Original line number Diff line number Diff line
@@ -2728,6 +2728,65 @@ size_t Parcel::ipcObjectsCount() const
    return 0;
}

static void do_nothing_release_func(const uint8_t* data, size_t dataSize,
                                    const binder_size_t* objects, size_t objectsCount) {
    (void)data;
    (void)dataSize;
    (void)objects;
    (void)objectsCount;
}
static void delete_data_release_func(const uint8_t* data, size_t dataSize,
                                     const binder_size_t* objects, size_t objectsCount) {
    delete[] data;
    (void)dataSize;
    (void)objects;
    (void)objectsCount;
}

void Parcel::makeDangerousViewOf(Parcel* p) {
    if (p->isForRpc()) {
        // warning: this must match the logic in rpcSetDataReference
        auto* rf = p->maybeRpcFields();
        LOG_ALWAYS_FATAL_IF(rf == nullptr);
        std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>> fds;
        if (rf->mFds) {
            fds.reserve(rf->mFds->size());
            for (const auto& fd : *rf->mFds) {
                fds.push_back(binder::borrowed_fd(toRawFd(fd)));
            }
        }
        status_t result =
                rpcSetDataReference(rf->mSession, p->mData, p->mDataSize,
                                    rf->mObjectPositions.data(), rf->mObjectPositions.size(),
                                    std::move(fds), do_nothing_release_func);
        LOG_ALWAYS_FATAL_IF(result != OK, "Failed: %s", statusToString(result).c_str());
    } else {
#ifdef BINDER_WITH_KERNEL_IPC
        // warning: this must match the logic in ipcSetDataReference
        auto* kf = p->maybeKernelFields();
        LOG_ALWAYS_FATAL_IF(kf == nullptr);

        // Ownership of FDs is passed to the Parcel from kernel binder. This should be refactored
        // to move this ownership out of Parcel and into release_func. However, today, Parcel
        // always assums it can own and close FDs today. So, for purposes of testing consistency,
        // , create new FDs it can own.

        uint8_t* newData = new uint8_t[p->mDataSize]; // deleted by delete_data_release_func
        memcpy(newData, p->mData, p->mDataSize);
        for (size_t i = 0; i < kf->mObjectsSize; i++) {
            flat_binder_object* flat =
                    reinterpret_cast<flat_binder_object*>(newData + kf->mObjects[i]);
            if (flat->hdr.type == BINDER_TYPE_FD) {
                flat->handle = fcntl(flat->handle, F_DUPFD_CLOEXEC, 0);
            }
        }

        ipcSetDataReference(newData, p->mDataSize, kf->mObjects, kf->mObjectsSize,
                            delete_data_release_func);
#endif // BINDER_WITH_KERNEL_IPC
    }
}

void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize, const binder_size_t* objects,
                                 size_t objectsCount, release_func relFunc) {
    // this code uses 'mOwner == nullptr' to understand whether it owns memory
@@ -2738,6 +2797,7 @@ void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize, const bin
    auto* kernelFields = maybeKernelFields();
    LOG_ALWAYS_FATAL_IF(kernelFields == nullptr); // guaranteed by freeData.

    // must match makeDangerousViewOf
    mData = const_cast<uint8_t*>(data);
    mDataSize = mDataCapacity = dataSize;
    kernelFields->mObjects = const_cast<binder_size_t*>(objects);
@@ -2816,6 +2876,7 @@ status_t Parcel::rpcSetDataReference(
    auto* rpcFields = maybeRpcFields();
    LOG_ALWAYS_FATAL_IF(rpcFields == nullptr); // guaranteed by markForRpc.

    // must match makeDangerousViewOf
    mData = const_cast<uint8_t*>(data);
    mDataSize = mDataCapacity = dataSize;
    mOwner = relFunc;
+6 −1
Original line number Diff line number Diff line
@@ -649,6 +649,11 @@ public:

    LIBBINDER_EXPORTED void print(std::ostream& to, uint32_t flags = 0) const;

    // This API is to quickly become a view of another Parcel, so that we can also
    // test 'owner' paths quickly. It's extremely dangerous to use this API in
    // practice, and you should never ever do it.
    LIBBINDER_EXPORTED void makeDangerousViewOf(Parcel* p);

private:
    // Close all file descriptors in the parcel at object positions >= newObjectsSize.
    void closeFileDescriptors(size_t newObjectsSize);
@@ -664,7 +669,7 @@ private:
    void ipcSetDataReference(const uint8_t* data, size_t dataSize, const binder_size_t* objects,
                             size_t objectsCount, release_func relFunc);
    // Takes ownership even when an error is returned.
    status_t rpcSetDataReference(
    [[nodiscard]] status_t rpcSetDataReference(
            const sp<RpcSession>& session, const uint8_t* data, size_t dataSize,
            const uint32_t* objectTable, size_t objectTableSize,
            std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>&& ancillaryFds,
+3 −0
Original line number Diff line number Diff line
@@ -28,6 +28,9 @@ struct RandomParcelOptions {
    std::function<void(Parcel* p, FuzzedDataProvider& provider)> writeHeader;
    std::vector<sp<IBinder>> extraBinders;
    std::vector<binder::unique_fd> extraFds;

    // internal state owned by fillRandomParcel, for Parcel views
    std::vector<std::unique_ptr<Parcel>> extraParcels;
};

/**
+8 −3
Original line number Diff line number Diff line
@@ -70,7 +70,7 @@ void doTransactFuzz(const char* backend, const sp<B>& binder, FuzzedDataProvider
    uint32_t code = provider.ConsumeIntegral<uint32_t>();
    uint32_t flag = provider.ConsumeIntegral<uint32_t>();

    FUZZ_LOG() << "backend: " << backend;
    FUZZ_LOG() << "doTransactFuzz backend: " << backend;

    RandomParcelOptions options;

@@ -101,7 +101,7 @@ void doReadFuzz(const char* backend, const std::vector<ParcelRead<P>>& reads,
    // since we are only using a byte to index
    CHECK_LE(reads.size(), 255u) << reads.size();

    FUZZ_LOG() << "backend: " << backend;
    FUZZ_LOG() << "doReadFuzz backend: " << backend;
    FUZZ_LOG() << "input: " << HexString(p.data(), p.dataSize());
    FUZZ_LOG() << "instructions: " << HexString(instructions.data(), instructions.size());

@@ -122,10 +122,15 @@ void doReadWriteFuzz(const char* backend, const std::vector<ParcelRead<P>>& read
    RandomParcelOptions options;
    P p;

    // small amount of initial Parcel data, since fillRandomParcel uses makeDangerousViewOf
    std::vector<uint8_t> parcelData =
            provider.ConsumeBytes<uint8_t>(provider.ConsumeIntegralInRange<size_t>(0, 20));
    fillRandomParcel(&p, FuzzedDataProvider(parcelData.data(), parcelData.size()), &options);

    // since we are only using a byte to index
    CHECK_LE(reads.size() + writes.size(), 255u) << reads.size();

    FUZZ_LOG() << "backend: " << backend;
    FUZZ_LOG() << "doReadWriteFuzz backend: " << backend;

    while (provider.remaining_bytes() > 0) {
        uint8_t idx = provider.ConsumeIntegralInRange<uint8_t>(0, reads.size() + writes.size() - 1);
+22 −2
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
#include <fuzzbinder/random_parcel.h>

#include <android-base/logging.h>
#include <binder/Functional.h>
#include <binder/RpcSession.h>
#include <binder/RpcTransportRaw.h>
#include <fuzzbinder/random_binder.h>
@@ -32,10 +33,29 @@ static void fillRandomParcelData(Parcel* p, FuzzedDataProvider&& provider) {
    CHECK(OK == p->write(data.data(), data.size()));
}

void fillRandomParcel(Parcel* p, FuzzedDataProvider&& provider, RandomParcelOptions* options) {
void fillRandomParcel(Parcel* outputParcel, FuzzedDataProvider&& provider,
                      RandomParcelOptions* options) {
    CHECK_NE(options, nullptr);

    if (provider.ConsumeBool()) {
    const uint8_t fuzzerParcelOptions = provider.ConsumeIntegral<uint8_t>();
    const bool resultShouldBeView = fuzzerParcelOptions & 1;
    const bool resultShouldBeRpc = fuzzerParcelOptions & 2;

    Parcel* p;
    if (resultShouldBeView) {
        options->extraParcels.push_back(std::make_unique<Parcel>());
        // held for duration of test, so that view will be valid
        p = options->extraParcels[options->extraParcels.size() - 1].get();
    } else {
        p = outputParcel; // directly fill out the output Parcel
    }
    auto viewify_guard = binder::impl::make_scope_guard([&]() {
        if (resultShouldBeView) {
            outputParcel->makeDangerousViewOf(p);
        }
    });

    if (resultShouldBeRpc) {
        auto session = RpcSession::make(RpcTransportCtxFactoryRaw::make());
        CHECK_EQ(OK, session->addNullDebuggingClient());
        // Set the protocol version so that we don't crash if the session
Loading