Commit message: None
CVE: CVE-2016-3750
CWE: CWE-20
Vulnerability description: libs/binder/Parcel.cpp in the Parcels Framework APIs in Android 4.x before 4.4.4, 5.0.x before 5.0.2, 5.1.x before 5.1.1, and 6.x before 2016-07-01 does not validate the return value of the dup system call, which allows attackers to bypass an isolation protection mechanism via a crafted application, aka internal bug 28395952.

Project: Android

Parcel::Blob::Blob() :
        mFd(-1), mData(NULL), mSize(0), mMutable(false) {
}

Project: Android

void Parcel::acquireObjects()
{
 const sp<ProcessState> proc(ProcessState::self());
 size_t i = mObjectsSize;
 uint8_t* const data = mData;
 binder_size_t* const objects = mObjects;
 while (i > 0) {
        i--;
 const flat_binder_object* flat
 = reinterpret_cast<flat_binder_object*>(data+objects[i]);
        acquire_object(proc, *flat, this, &mOpenAshmemSize);
 }
}

Project: Android

void acquire_object(const sp<ProcessState>& proc,
 const flat_binder_object& obj, const void* who, size_t* outAshmemSize)
{
 switch (obj.type) {
 case BINDER_TYPE_BINDER:
 if (obj.binder) {
                LOG_REFS("Parcel %p acquiring reference on local %p", who, obj.cookie);
 reinterpret_cast<IBinder*>(obj.cookie)->incStrong(who);
 }
 return;
 case BINDER_TYPE_WEAK_BINDER:
 if (obj.binder)
 reinterpret_cast<RefBase::weakref_type*>(obj.binder)->incWeak(who);
 return;
 case BINDER_TYPE_HANDLE: {
 const sp<IBinder> b = proc->getStrongProxyForHandle(obj.handle);
 if (b != NULL) {
                LOG_REFS("Parcel %p acquiring reference on remote %p", who, b.get());
                b->incStrong(who);
 }
 return;
 }
 case BINDER_TYPE_WEAK_HANDLE: {
 const wp<IBinder> b = proc->getWeakProxyForHandle(obj.handle);
 if (b != NULL) b.get_refs()->incWeak(who);
 return;
 }
 case BINDER_TYPE_FD: {
 if (obj.cookie != 0) {
 if (outAshmemSize != NULL) {
 int size = ashmem_get_size_region(obj.handle);
 if (size > 0) {
 *outAshmemSize += size;
 }
 }
 }
 return;
 }
 }

    ALOGD("Invalid object type 0x%08x", obj.type);
}

Project: Android

void acquire_object(const sp<ProcessState>& proc,
 const flat_binder_object& obj, const void* who)
{
    acquire_object(proc, obj, who, NULL);
}

Project: Android

status_t Parcel::appendFrom(const Parcel *parcel, size_t offset, size_t len)
{
 const sp<ProcessState> proc(ProcessState::self());
 status_t err;
 const uint8_t *data = parcel->mData;
 const binder_size_t *objects = parcel->mObjects;
 size_t size = parcel->mObjectsSize;
 int startPos = mDataPos;
 int firstIndex = -1, lastIndex = -2;

 if (len == 0) {
 return NO_ERROR;
 }

 if (len > INT32_MAX) {
 return BAD_VALUE;
 }

 if ((offset > parcel->mDataSize)
 || (len > parcel->mDataSize)
 || (offset + len > parcel->mDataSize)) {
 return BAD_VALUE;
 }

 for (int i = 0; i < (int) size; i++) {
 size_t off = objects[i];
 if ((off >= offset) && (off + sizeof(flat_binder_object) <= offset + len)) {
 if (firstIndex == -1) {
                firstIndex = i;
 }
            lastIndex = i;
 }
 }
 int numObjects = lastIndex - firstIndex + 1;

 if ((mDataSize+len) > mDataCapacity) {
        err = growData(len);
 if (err != NO_ERROR) {
 return err;
 }
 }

    memcpy(mData + mDataPos, data + offset, len);
    mDataPos += len;
    mDataSize += len;

    err = NO_ERROR;

 if (numObjects > 0) {
 if (mObjectsCapacity < mObjectsSize + numObjects) {
 size_t newSize = ((mObjectsSize + numObjects)*3)/2;
 if (newSize < mObjectsSize) return NO_MEMORY; // overflow
 binder_size_t *objects =
 (binder_size_t*)realloc(mObjects, newSize*sizeof(binder_size_t));
 if (objects == (binder_size_t*)0) {
 return NO_MEMORY;
 }
            mObjects = objects;
            mObjectsCapacity = newSize;
 }

 int idx = mObjectsSize;
 for (int i = firstIndex; i <= lastIndex; i++) {
 size_t off = objects[i] - offset + startPos;
            mObjects[idx++] = off;
            mObjectsSize++;

            flat_binder_object* flat
 = reinterpret_cast<flat_binder_object*>(mData + off);
            acquire_object(proc, *flat, this, &mOpenAshmemSize);

 if (flat->type == BINDER_TYPE_FD) {
                flat->handle = dup(flat->handle);
                flat->cookie = 1;
                mHasFds = mFdsKnown = true;
 if (!mAllowFds) {
                    err = FDS_NOT_ALLOWED;
 }
 }
 }
 }

 return err;
}

Project: Android

void Parcel::Blob::clear() {
    mFd = -1;
    mData = NULL;
    mSize = 0;
    mMutable = false;
}

Project: Android

status_t Parcel::finishWrite(size_t len)
{
 if (len > INT32_MAX) {
 return BAD_VALUE;
 }

    mDataPos += len;
    ALOGV("finishWrite Setting data pos of %p to %zu", this, mDataPos);
 if (mDataPos > mDataSize) {
        mDataSize = mDataPos;
        ALOGV("finishWrite Setting data size of %p to %zu", this, mDataSize);
 }
 return NO_ERROR;
}

Project: Android

size_t Parcel::getBlobAshmemSize() const
{
 return mOpenAshmemSize;
}

Project: Android

status_t Parcel::growData(size_t len)
{
 if (len > INT32_MAX) {
 return BAD_VALUE;
 }

 size_t newSize = ((mDataSize+len)*3)/2;
 return (newSize <= mDataSize)
 ? (status_t) NO_MEMORY
 : continueWrite(newSize);
}

Project: Android

void Parcel::Blob::init(int fd, void* data, size_t size, bool isMutable) {
    mFd = fd;
    mData = data;
    mSize = size;
    mMutable = isMutable;
}

Project: Android

void Parcel::initState()
{
    LOG_ALLOC("Parcel %p: initState", this);
    mError = NO_ERROR;
    mData = 0;
    mDataSize = 0;
    mDataCapacity = 0;
    mDataPos = 0;
    ALOGV("initState Setting data size of %p to %zu", this, mDataSize);
    ALOGV("initState Setting data pos of %p to %zu", this, mDataPos);
    mObjects = NULL;
    mObjectsSize = 0;
    mObjectsCapacity = 0;
    mNextObjectHint = 0;
    mHasFds = false;
    mFdsKnown = true;
    mAllowFds = true;
    mOwner = NULL;
    mOpenAshmemSize = 0;
}

Project: Android

void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize,
 const binder_size_t* objects, size_t objectsCount, release_func relFunc, void* relCookie)
{
 binder_size_t minOffset = 0;
    freeDataNoInit();
    mError = NO_ERROR;
    mData = const_cast<uint8_t*>(data);
    mDataSize = mDataCapacity = dataSize;
    mDataPos = 0;
    ALOGV("setDataReference Setting data pos of %p to %zu", this, mDataPos);
    mObjects = const_cast<binder_size_t*>(objects);
    mObjectsSize = mObjectsCapacity = objectsCount;
    mNextObjectHint = 0;
    mOwner = relFunc;
    mOwnerCookie = relCookie;
 for (size_t i = 0; i < mObjectsSize; i++) {
 binder_size_t offset = mObjects[i];
 if (offset < minOffset) {
            ALOGE("%s: bad object offset %" PRIu64 " < %" PRIu64 "\n",
                  __func__, (uint64_t)offset, (uint64_t)minOffset);
            mObjectsSize = 0;
 break;
 }
        minOffset = offset + sizeof(flat_binder_object);
 }
    scanForFds();
}

Project: Android

static size_t pad_size(size_t s) {
 if (s > (SIZE_T_MAX - 3)) {
        abort();
 }
 return PAD_SIZE_UNSAFE(s);
}

Project: Android

status_t Parcel::read(void* outData, size_t len) const
{
 if (len > INT32_MAX) {
 return BAD_VALUE;
 }

 if ((mDataPos+pad_size(len)) >= mDataPos && (mDataPos+pad_size(len)) <= mDataSize
 && len <= pad_size(len)) {
        memcpy(outData, mData+mDataPos, len);
        mDataPos += pad_size(len);
        ALOGV("read Setting data pos of %p to %zu", this, mDataPos);
 return NO_ERROR;
 }
 return NOT_ENOUGH_DATA;
}

Project: Android

status_t Parcel::read(FlattenableHelperInterface& val) const
{
 const size_t len = this->readInt32();
 const size_t fd_count = this->readInt32();

 if (len > INT32_MAX) {
 return BAD_VALUE;
 }

 void const* const buf = this->readInplace(pad_size(len));
 if (buf == NULL)
 return BAD_VALUE;

 int* fds = NULL;
 if (fd_count) {
        fds = new int[fd_count];
 }

 status_t err = NO_ERROR;
 for (size_t i=0 ; i<fd_count && err==NO_ERROR ; i++) {
        fds[i] = dup(this->readFileDescriptor());
 if (fds[i] < 0) {
            err = BAD_VALUE;
            ALOGE("dup() failed in Parcel::read, i is %zu, fds[i] is %d, fd_count is %zu, error: %s",
                i, fds[i], fd_count, strerror(errno));
 }
 }

 if (err == NO_ERROR) {
        err = val.unflatten(buf, len, fds, fd_count);
 }

 if (fd_count) {
 delete [] fds;
 }

 return err;
}

Project: Android

status_t Parcel::readAligned(T *pArg) const {
    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));

 if ((mDataPos+sizeof(T)) <= mDataSize) {
 const void* data = mData+mDataPos;
        mDataPos += sizeof(T);
 *pArg = *reinterpret_cast<const T*>(data);
 return NO_ERROR;
 } else {
 return NOT_ENOUGH_DATA;
 }
}

Project: Android

status_t Parcel::readBlob(size_t len, ReadableBlob* outBlob) const
{
 int32_t blobType;
 status_t status = readInt32(&blobType);
 if (status) return status;

 if (blobType == BLOB_INPLACE) {
        ALOGV("readBlob: read in place");
 const void* ptr = readInplace(len);
 if (!ptr) return BAD_VALUE;

        outBlob->init(-1, const_cast<void*>(ptr), len, false);
 return NO_ERROR;
 }

    ALOGV("readBlob: read from ashmem");
 bool isMutable = (blobType == BLOB_ASHMEM_MUTABLE);
 int fd = readFileDescriptor();
 if (fd == int(BAD_TYPE)) return BAD_VALUE;

 void* ptr = ::mmap(NULL, len, isMutable ? PROT_READ | PROT_WRITE : PROT_READ,
            MAP_SHARED, fd, 0);
 if (ptr == MAP_FAILED) return NO_MEMORY;

    outBlob->init(fd, ptr, len, isMutable);
 return NO_ERROR;
}

Project: Android

status_t Parcel::readUint32(uint32_t *pArg) const
{
 return readAligned(pArg);
}

Project: Android

uint32_t Parcel::readUint32() const
{
 return readAligned<uint32_t>();
}

Project: Android

uint64_t Parcel::readUint64() const
{
 return readAligned<uint64_t>();
}

Project: Android

void Parcel::Blob::release() {
 if (mFd != -1 && mData) {
 ::munmap(mData, mSize);
 }
    clear();
}

Project: Android

void Parcel::releaseObjects()
{
 const sp<ProcessState> proc(ProcessState::self());
 size_t i = mObjectsSize;
 uint8_t* const data = mData;
 binder_size_t* const objects = mObjects;
 while (i > 0) {
        i--;
 const flat_binder_object* flat
 = reinterpret_cast<flat_binder_object*>(data+objects[i]);
        release_object(proc, *flat, this, &mOpenAshmemSize);
 }
}

Project: Android

static void release_object(const sp<ProcessState>& proc,
 const flat_binder_object& obj, const void* who, size_t* outAshmemSize)
{
 switch (obj.type) {
 case BINDER_TYPE_BINDER:
 if (obj.binder) {
                LOG_REFS("Parcel %p releasing reference on local %p", who, obj.cookie);
 reinterpret_cast<IBinder*>(obj.cookie)->decStrong(who);
 }
 return;
 case BINDER_TYPE_WEAK_BINDER:
 if (obj.binder)
 reinterpret_cast<RefBase::weakref_type*>(obj.binder)->decWeak(who);
 return;
 case BINDER_TYPE_HANDLE: {
 const sp<IBinder> b = proc->getStrongProxyForHandle(obj.handle);
 if (b != NULL) {
                LOG_REFS("Parcel %p releasing reference on remote %p", who, b.get());
                b->decStrong(who);
 }
 return;
 }
 case BINDER_TYPE_WEAK_HANDLE: {
 const wp<IBinder> b = proc->getWeakProxyForHandle(obj.handle);
 if (b != NULL) b.get_refs()->decWeak(who);
 return;
 }
 case BINDER_TYPE_FD: {
 if (outAshmemSize != NULL) {
 if (obj.cookie != 0) {
 int size = ashmem_get_size_region(obj.handle);
 if (size > 0) {
 *outAshmemSize -= size;
 }

                    close(obj.handle);
 }
 }
 return;
 }
 }

    ALOGE("Invalid object type 0x%08x", obj.type);
}

Project: Android

status_t Parcel::restartWrite(size_t desired)
{
 if (desired > INT32_MAX) {
 return BAD_VALUE;
 }

 if (mOwner) {
        freeData();
 return continueWrite(desired);
 }

 uint8_t* data = (uint8_t*)realloc(mData, desired);
 if (!data && desired > mDataCapacity) {
        mError = NO_MEMORY;
 return NO_MEMORY;
 }

    releaseObjects();

 if (data) {
        LOG_ALLOC("Parcel %p: restart from %zu to %zu capacity", this, mDataCapacity, desired);
        pthread_mutex_lock(&gParcelGlobalAllocSizeLock);
        gParcelGlobalAllocSize += desired;
        gParcelGlobalAllocSize -= mDataCapacity;
        pthread_mutex_unlock(&gParcelGlobalAllocSizeLock);
        mData = data;
        mDataCapacity = desired;
 }

    mDataSize = mDataPos = 0;
    ALOGV("restartWrite Setting data size of %p to %zu", this, mDataSize);
    ALOGV("restartWrite Setting data pos of %p to %zu", this, mDataPos);

    free(mObjects);
    mObjects = NULL;
    mObjectsSize = mObjectsCapacity = 0;
    mNextObjectHint = 0;
    mHasFds = false;
    mFdsKnown = true;
    mAllowFds = true;

 return NO_ERROR;
}

Project: Android

status_t Parcel::setData(const uint8_t* buffer, size_t len)
{
 if (len > INT32_MAX) {
 return BAD_VALUE;
 }

 status_t err = restartWrite(len);
 if (err == NO_ERROR) {
        memcpy(const_cast<uint8_t*>(data()), buffer, len);
        mDataSize = len;
        mFdsKnown = false;
 }
 return err;
}

Project: Android

status_t Parcel::setDataCapacity(size_t size)
{
 if (size > INT32_MAX) {
 return BAD_VALUE;
 }

 if (size > mDataCapacity) return continueWrite(size);
 return NO_ERROR;
}

Project: Android

void Parcel::setDataPosition(size_t pos) const
{
 if (pos > INT32_MAX) {
        abort();
 }

    mDataPos = pos;
    mNextObjectHint = 0;
}

Project: Android

status_t Parcel::setDataSize(size_t size)
{
 if (size > INT32_MAX) {
 return BAD_VALUE;
 }

 status_t err;
    err = continueWrite(size);
 if (err == NO_ERROR) {
        mDataSize = size;
        ALOGV("setDataSize Setting data size of %p to %zu", this, mDataSize);
 }
 return err;
}

Project: Android

status_t Parcel::write(const void* data, size_t len)
{
 if (len > INT32_MAX) {
 return BAD_VALUE;
 }

 void* const d = writeInplace(len);
 if (d) {
        memcpy(d, data, len);
 return NO_ERROR;
 }
 return mError;
}

Project: Android

status_t Parcel::write(const FlattenableHelperInterface& val)
{
 status_t err;

 const size_t len = val.getFlattenedSize();
 const size_t fd_count = val.getFdCount();

 if ((len > INT32_MAX) || (fd_count > INT32_MAX)) {
 return BAD_VALUE;
 }

    err = this->writeInt32(len);
 if (err) return err;

    err = this->writeInt32(fd_count);
 if (err) return err;

 void* const buf = this->writeInplace(pad_size(len));
 if (buf == NULL)
 return BAD_VALUE;

 int* fds = NULL;
 if (fd_count) {
        fds = new int[fd_count];
 }

    err = val.flatten(buf, len, fds, fd_count);
 for (size_t i=0 ; i<fd_count && err==NO_ERROR ; i++) {
        err = this->writeDupFileDescriptor( fds[i] );
 }

 if (fd_count) {
 delete [] fds;
 }

 return err;
}

Project: Android

status_t Parcel::writeAligned(T val) {
    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));

 if ((mDataPos+sizeof(val)) <= mDataCapacity) {
restart_write:
 *reinterpret_cast<T*>(mData+mDataPos) = val;
 return finishWrite(sizeof(val));
 }

 status_t err = growData(sizeof(val));
 if (err == NO_ERROR) goto restart_write;
 return err;
}

Project: Android

status_t Parcel::writeBlob(size_t len, bool mutableCopy, WritableBlob* outBlob)
{
 if (len > INT32_MAX) {
 return BAD_VALUE;
 }

 status_t status;
 if (!mAllowFds || len <= BLOB_INPLACE_LIMIT) {
        ALOGV("writeBlob: write in place");
        status = writeInt32(BLOB_INPLACE);
 if (status) return status;

 void* ptr = writeInplace(len);
 if (!ptr) return NO_MEMORY;

        outBlob->init(-1, ptr, len, false);
 return NO_ERROR;
 }

    ALOGV("writeBlob: write to ashmem");
 int fd = ashmem_create_region("Parcel Blob", len);
 if (fd < 0) return NO_MEMORY;

 int result = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
 if (result < 0) {
        status = result;
 } else {
 void* ptr = ::mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
 if (ptr == MAP_FAILED) {
            status = -errno;
 } else {
 if (!mutableCopy) {
                result = ashmem_set_prot_region(fd, PROT_READ);
 }
 if (result < 0) {
                status = result;
 } else {
                status = writeInt32(mutableCopy ? BLOB_ASHMEM_MUTABLE : BLOB_ASHMEM_IMMUTABLE);
 if (!status) {
                    status = writeFileDescriptor(fd, true /*takeOwnership*/);
 if (!status) {
                        outBlob->init(fd, ptr, len, mutableCopy);
 return NO_ERROR;
 }
 }
 }
 }
 ::munmap(ptr, len);
 }
 ::close(fd);
 return status;
}

Project: Android

status_t Parcel::writeByteArray(size_t len, const uint8_t *val) {
 if (len > INT32_MAX) {
 return BAD_VALUE;
 }

 if (!val) {
 return writeInt32(-1);
 }
 status_t ret = writeInt32(static_cast<uint32_t>(len));
 if (ret == NO_ERROR) {
        ret = write(val, len * sizeof(*val));
 }
 return ret;
}

Project: Android

status_t Parcel::writeDupImmutableBlobFileDescriptor(int fd)
{
 if (!mAllowFds) return FDS_NOT_ALLOWED;
 status_t status = writeInt32(BLOB_ASHMEM_IMMUTABLE);
 if (status) return status;
 return writeDupFileDescriptor(fd);
}

Project: Android

void* Parcel::writeInplace(size_t len)
{
 if (len > INT32_MAX) {
 return NULL;
 }

 const size_t padded = pad_size(len);

 if (mDataPos+padded < mDataPos) {
 return NULL;
 }

 if ((mDataPos+padded) <= mDataCapacity) {
restart_write:
 uint8_t* const data = mData+mDataPos;

 if (padded != len) {
#if BYTE_ORDER == BIG_ENDIAN
 static const uint32_t mask[4] = {
 0x00000000, 0xffffff00, 0xffff0000, 0xff000000
 };
#endif
#if BYTE_ORDER == LITTLE_ENDIAN
 static const uint32_t mask[4] = {
 0x00000000, 0x00ffffff, 0x0000ffff, 0x000000ff
 };
#endif
 *reinterpret_cast<uint32_t*>(data+padded-4) &= mask[padded-len];
 }

        finishWrite(padded);
 return data;
 }

 status_t err = growData(padded);
 if (err == NO_ERROR) goto restart_write;
 return NULL;
}

Project: Android

status_t Parcel::writeInt32Array(size_t len, const int32_t *val) {
 if (len > INT32_MAX) {
 return BAD_VALUE;
 }

 if (!val) {
 return writeInt32(-1);
 }
 status_t ret = writeInt32(static_cast<uint32_t>(len));
 if (ret == NO_ERROR) {
        ret = write(val, len * sizeof(*val));
 }
 return ret;
}

Project: Android

status_t Parcel::writeObject(const flat_binder_object& val, bool nullMetaData)
{
 const bool enoughData = (mDataPos+sizeof(val)) <= mDataCapacity;
 const bool enoughObjects = mObjectsSize < mObjectsCapacity;
 if (enoughData && enoughObjects) {
restart_write:
 *reinterpret_cast<flat_binder_object*>(mData+mDataPos) = val;

 if (val.type == BINDER_TYPE_FD) {
 if (!mAllowFds) {
 return FDS_NOT_ALLOWED;
 }
            mHasFds = mFdsKnown = true;
 }

 if (nullMetaData || val.binder != 0) {
            mObjects[mObjectsSize] = mDataPos;
            acquire_object(ProcessState::self(), val, this, &mOpenAshmemSize);
            mObjectsSize++;
 }

 return finishWrite(sizeof(flat_binder_object));
 }

 if (!enoughData) {
 const status_t err = growData(sizeof(val));
 if (err != NO_ERROR) return err;
 }
 if (!enoughObjects) {
 size_t newSize = ((mObjectsSize+2)*3)/2;
 if (newSize < mObjectsSize) return NO_MEMORY; // overflow
 binder_size_t* objects = (binder_size_t*)realloc(mObjects, newSize*sizeof(binder_size_t));
 if (objects == NULL) return NO_MEMORY;
        mObjects = objects;
        mObjectsCapacity = newSize;
 }

 goto restart_write;
}

Project: Android

status_t Parcel::writeUint32(uint32_t val)
{
 return writeAligned(val);
}

Project: Android

status_t Parcel::writeUnpadded(const void* data, size_t len)
{
 if (len > INT32_MAX) {
 return BAD_VALUE;
 }

 size_t end = mDataPos + len;
 if (end < mDataPos) {
 return BAD_VALUE;
 }

 if (end <= mDataCapacity) {
restart_write:
        memcpy(mData+mDataPos, data, len);
 return finishWrite(len);
 }

 status_t err = growData(len);
 if (err == NO_ERROR) goto restart_write;
 return err;
}