Files
frameworks_base/libs/androidfw/CursorWindow.cpp
Jeff Sharkey 256da5a361 Add fuzzer for rewritten CursorWindow.
We recently rewrote CursorWindow, so let's get a fuzzer wired up
to see if it has any bugs.

This change creates a separate "libandroidfw_fuzz" library, since we
can't link to libbinder when building Windows host-side binaries;
the fuzzer doesn't need Window support.

And fix our first vulnerability where getFieldSlot() could be
tricked into reading out of bounds data.

The included corpus seed was generated using this example code:

    CursorWindow* w = nullptr;
    CursorWindow::create(android::String8("test"), 1 << 21, &w);
    w->setNumColumns(3);

    w->allocRow();
    w->putLong(0,0,0xcafe);
    w->putLong(0,1,0xcafe);
    w->putLong(0,2,0xcafe);

    // Row purposefully left empty
    w->allocRow();

    w->allocRow();
    w->putNull(2,0);
    w->putNull(2,1);
    w->putNull(2,2);

    w->allocRow();
    w->putString(3,0,"cafe",5);
    w->putString(3,1,"cafe",5);
    w->putString(3,2,"cafe",5);

    w->allocRow();
    w->putDouble(4,0,3.14159f);
    w->putDouble(4,1,3.14159f);
    w->putDouble(4,2,3.14159f);

    Parcel p;
    w->writeToParcel(&p);

Bug: 169251528
Test: atest libandroidfw_tests:CursorWindowTest
Test: SANITIZE_HOST=address make ${FUZZER_NAME} && ${ANDROID_HOST_OUT}/fuzz/$(get_build_var HOST_ARCH)/${FUZZER_NAME}/${FUZZER_NAME}
Change-Id: I405d377900943de0ad732d3f1a1a0970e17d5140
2020-10-19 16:07:16 -06:00

438 lines
12 KiB
C++

/*
* Copyright (C) 2006-2007 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.
*/
#define LOG_TAG "CursorWindow"
#include <androidfw/CursorWindow.h>
#include <sys/mman.h>
#include "android-base/logging.h"
#include "cutils/ashmem.h"
namespace android {
/**
* By default windows are lightweight inline allocations of this size;
* they're only inflated to ashmem regions when more space is needed.
*/
static constexpr const size_t kInlineSize = 16384;
static constexpr const size_t kSlotShift = 4;
static constexpr const size_t kSlotSizeBytes = 1 << kSlotShift;
CursorWindow::CursorWindow() {
}
CursorWindow::~CursorWindow() {
if (mAshmemFd != -1) {
::munmap(mData, mSize);
::close(mAshmemFd);
} else {
free(mData);
}
}
status_t CursorWindow::create(const String8 &name, size_t inflatedSize, CursorWindow **outWindow) {
*outWindow = nullptr;
CursorWindow* window = new CursorWindow();
if (!window) goto fail;
window->mName = name;
window->mSize = std::min(kInlineSize, inflatedSize);
window->mInflatedSize = inflatedSize;
window->mData = malloc(window->mSize);
if (!window->mData) goto fail;
window->mReadOnly = false;
window->clear();
window->updateSlotsData();
LOG(DEBUG) << "Created: " << window->toString();
*outWindow = window;
return OK;
fail:
LOG(ERROR) << "Failed create";
fail_silent:
delete window;
return UNKNOWN_ERROR;
}
status_t CursorWindow::maybeInflate() {
int ashmemFd = 0;
void* newData = nullptr;
// Bail early when we can't expand any further
if (mReadOnly || mSize == mInflatedSize) {
return INVALID_OPERATION;
}
String8 ashmemName("CursorWindow: ");
ashmemName.append(mName);
ashmemFd = ashmem_create_region(ashmemName.string(), mInflatedSize);
if (ashmemFd < 0) {
PLOG(ERROR) << "Failed ashmem_create_region";
goto fail_silent;
}
if (ashmem_set_prot_region(ashmemFd, PROT_READ | PROT_WRITE) < 0) {
PLOG(ERROR) << "Failed ashmem_set_prot_region";
goto fail_silent;
}
newData = ::mmap(nullptr, mInflatedSize, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0);
if (newData == MAP_FAILED) {
PLOG(ERROR) << "Failed mmap";
goto fail_silent;
}
if (ashmem_set_prot_region(ashmemFd, PROT_READ) < 0) {
PLOG(ERROR) << "Failed ashmem_set_prot_region";
goto fail_silent;
}
{
// Migrate existing contents into new ashmem region
uint32_t slotsSize = mSize - mSlotsOffset;
uint32_t newSlotsOffset = mInflatedSize - slotsSize;
memcpy(static_cast<uint8_t*>(newData),
static_cast<uint8_t*>(mData), mAllocOffset);
memcpy(static_cast<uint8_t*>(newData) + newSlotsOffset,
static_cast<uint8_t*>(mData) + mSlotsOffset, slotsSize);
free(mData);
mAshmemFd = ashmemFd;
mData = newData;
mSize = mInflatedSize;
mSlotsOffset = newSlotsOffset;
updateSlotsData();
}
LOG(DEBUG) << "Inflated: " << this->toString();
return OK;
fail:
LOG(ERROR) << "Failed maybeInflate";
fail_silent:
::munmap(newData, mInflatedSize);
::close(ashmemFd);
return UNKNOWN_ERROR;
}
status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outWindow) {
*outWindow = nullptr;
CursorWindow* window = new CursorWindow();
if (!window) goto fail;
if (parcel->readString8(&window->mName)) goto fail;
if (parcel->readUint32(&window->mNumRows)) goto fail;
if (parcel->readUint32(&window->mNumColumns)) goto fail;
if (parcel->readUint32(&window->mSize)) goto fail;
if ((window->mNumRows * window->mNumColumns * kSlotSizeBytes) > window->mSize) {
LOG(ERROR) << "Unexpected size " << window->mSize << " for " << window->mNumRows
<< " rows and " << window->mNumColumns << " columns";
goto fail_silent;
}
bool isAshmem;
if (parcel->readBool(&isAshmem)) goto fail;
if (isAshmem) {
window->mAshmemFd = parcel->readFileDescriptor();
if (window->mAshmemFd < 0) {
LOG(ERROR) << "Failed readFileDescriptor";
goto fail_silent;
}
window->mAshmemFd = ::fcntl(window->mAshmemFd, F_DUPFD_CLOEXEC, 0);
if (window->mAshmemFd < 0) {
PLOG(ERROR) << "Failed F_DUPFD_CLOEXEC";
goto fail_silent;
}
window->mData = ::mmap(nullptr, window->mSize, PROT_READ, MAP_SHARED, window->mAshmemFd, 0);
if (window->mData == MAP_FAILED) {
PLOG(ERROR) << "Failed mmap";
goto fail_silent;
}
} else {
window->mAshmemFd = -1;
if (window->mSize > kInlineSize) {
LOG(ERROR) << "Unexpected size " << window->mSize << " for inline window";
goto fail_silent;
}
window->mData = malloc(window->mSize);
if (!window->mData) goto fail;
if (parcel->read(window->mData, window->mSize)) goto fail;
}
// We just came from a remote source, so we're read-only
// and we can't inflate ourselves
window->mInflatedSize = window->mSize;
window->mReadOnly = true;
window->updateSlotsData();
LOG(DEBUG) << "Created from parcel: " << window->toString();
*outWindow = window;
return OK;
fail:
LOG(ERROR) << "Failed createFromParcel";
fail_silent:
delete window;
return UNKNOWN_ERROR;
}
status_t CursorWindow::writeToParcel(Parcel* parcel) {
LOG(DEBUG) << "Writing to parcel: " << this->toString();
if (parcel->writeString8(mName)) goto fail;
if (parcel->writeUint32(mNumRows)) goto fail;
if (parcel->writeUint32(mNumColumns)) goto fail;
if (mAshmemFd != -1) {
if (parcel->writeUint32(mSize)) goto fail;
if (parcel->writeBool(true)) goto fail;
if (parcel->writeDupFileDescriptor(mAshmemFd)) goto fail;
} else {
// Since we know we're going to be read-only on the remote side,
// we can compact ourselves on the wire, with just enough padding
// to ensure our slots stay aligned
size_t slotsSize = mSize - mSlotsOffset;
size_t compactedSize = mAllocOffset + slotsSize;
compactedSize = (compactedSize + 3) & ~3;
if (parcel->writeUint32(compactedSize)) goto fail;
if (parcel->writeBool(false)) goto fail;
void* dest = parcel->writeInplace(compactedSize);
if (!dest) goto fail;
memcpy(static_cast<uint8_t*>(dest),
static_cast<uint8_t*>(mData), mAllocOffset);
memcpy(static_cast<uint8_t*>(dest) + compactedSize - slotsSize,
static_cast<uint8_t*>(mData) + mSlotsOffset, slotsSize);
}
return OK;
fail:
LOG(ERROR) << "Failed writeToParcel";
fail_silent:
return UNKNOWN_ERROR;
}
status_t CursorWindow::clear() {
if (mReadOnly) {
return INVALID_OPERATION;
}
mAllocOffset = 0;
mSlotsOffset = mSize;
mNumRows = 0;
mNumColumns = 0;
return OK;
}
void CursorWindow::updateSlotsData() {
mSlotsStart = static_cast<uint8_t*>(mData) + mSize - kSlotSizeBytes;
mSlotsEnd = static_cast<uint8_t*>(mData) + mSlotsOffset;
}
void* CursorWindow::offsetToPtr(uint32_t offset, uint32_t bufferSize = 0) {
if (offset > mSize) {
LOG(ERROR) << "Offset " << offset
<< " out of bounds, max value " << mSize;
return nullptr;
}
if (offset + bufferSize > mSize) {
LOG(ERROR) << "End offset " << (offset + bufferSize)
<< " out of bounds, max value " << mSize;
return nullptr;
}
return static_cast<uint8_t*>(mData) + offset;
}
uint32_t CursorWindow::offsetFromPtr(void* ptr) {
return static_cast<uint8_t*>(ptr) - static_cast<uint8_t*>(mData);
}
status_t CursorWindow::setNumColumns(uint32_t numColumns) {
if (mReadOnly) {
return INVALID_OPERATION;
}
uint32_t cur = mNumColumns;
if ((cur > 0 || mNumRows > 0) && cur != numColumns) {
LOG(ERROR) << "Trying to go from " << cur << " columns to " << numColumns;
return INVALID_OPERATION;
}
mNumColumns = numColumns;
return OK;
}
status_t CursorWindow::allocRow() {
if (mReadOnly) {
return INVALID_OPERATION;
}
size_t size = mNumColumns * kSlotSizeBytes;
off_t newOffset = mSlotsOffset - size;
if (newOffset < mAllocOffset) {
maybeInflate();
newOffset = mSlotsOffset - size;
if (newOffset < mAllocOffset) {
return NO_MEMORY;
}
}
memset(offsetToPtr(newOffset), 0, size);
mSlotsOffset = newOffset;
updateSlotsData();
mNumRows++;
return OK;
}
status_t CursorWindow::freeLastRow() {
if (mReadOnly) {
return INVALID_OPERATION;
}
size_t size = mNumColumns * kSlotSizeBytes;
off_t newOffset = mSlotsOffset + size;
if (newOffset > mSize) {
return NO_MEMORY;
}
mSlotsOffset = newOffset;
updateSlotsData();
mNumRows--;
return OK;
}
status_t CursorWindow::alloc(size_t size, uint32_t* outOffset) {
if (mReadOnly) {
return INVALID_OPERATION;
}
size_t alignedSize = (size + 3) & ~3;
off_t newOffset = mAllocOffset + alignedSize;
if (newOffset > mSlotsOffset) {
maybeInflate();
newOffset = mAllocOffset + alignedSize;
if (newOffset > mSlotsOffset) {
return NO_MEMORY;
}
}
*outOffset = mAllocOffset;
mAllocOffset = newOffset;
return OK;
}
CursorWindow::FieldSlot* CursorWindow::getFieldSlot(uint32_t row, uint32_t column) {
// This is carefully tuned to use as few cycles as
// possible, since this is an extremely hot code path;
// see CursorWindow_bench.cpp for more details
void *result = static_cast<uint8_t*>(mSlotsStart)
- (((row * mNumColumns) + column) << kSlotShift);
if (result < mSlotsEnd || column >= mNumColumns) {
LOG(ERROR) << "Failed to read row " << row << ", column " << column
<< " from a window with " << mNumRows << " rows, " << mNumColumns << " columns";
return nullptr;
} else {
return static_cast<FieldSlot*>(result);
}
}
status_t CursorWindow::putBlob(uint32_t row, uint32_t column, const void* value, size_t size) {
return putBlobOrString(row, column, value, size, FIELD_TYPE_BLOB);
}
status_t CursorWindow::putString(uint32_t row, uint32_t column, const char* value,
size_t sizeIncludingNull) {
return putBlobOrString(row, column, value, sizeIncludingNull, FIELD_TYPE_STRING);
}
status_t CursorWindow::putBlobOrString(uint32_t row, uint32_t column,
const void* value, size_t size, int32_t type) {
if (mReadOnly) {
return INVALID_OPERATION;
}
FieldSlot* fieldSlot = getFieldSlot(row, column);
if (!fieldSlot) {
return BAD_VALUE;
}
uint32_t offset;
if (alloc(size, &offset)) {
return NO_MEMORY;
}
memcpy(offsetToPtr(offset), value, size);
fieldSlot = getFieldSlot(row, column);
fieldSlot->type = type;
fieldSlot->data.buffer.offset = offset;
fieldSlot->data.buffer.size = size;
return OK;
}
status_t CursorWindow::putLong(uint32_t row, uint32_t column, int64_t value) {
if (mReadOnly) {
return INVALID_OPERATION;
}
FieldSlot* fieldSlot = getFieldSlot(row, column);
if (!fieldSlot) {
return BAD_VALUE;
}
fieldSlot->type = FIELD_TYPE_INTEGER;
fieldSlot->data.l = value;
return OK;
}
status_t CursorWindow::putDouble(uint32_t row, uint32_t column, double value) {
if (mReadOnly) {
return INVALID_OPERATION;
}
FieldSlot* fieldSlot = getFieldSlot(row, column);
if (!fieldSlot) {
return BAD_VALUE;
}
fieldSlot->type = FIELD_TYPE_FLOAT;
fieldSlot->data.d = value;
return OK;
}
status_t CursorWindow::putNull(uint32_t row, uint32_t column) {
if (mReadOnly) {
return INVALID_OPERATION;
}
FieldSlot* fieldSlot = getFieldSlot(row, column);
if (!fieldSlot) {
return BAD_VALUE;
}
fieldSlot->type = FIELD_TYPE_NULL;
fieldSlot->data.buffer.offset = 0;
fieldSlot->data.buffer.size = 0;
return OK;
}
}; // namespace android