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

Commit a316c5df authored by Jeff Brown's avatar Jeff Brown
Browse files

Fix Bitmap parceling through ashmem.

Fixes a bug where the Bitmap parceling code was unable to deal with
sending bitmaps through Parcels that disallow file descriptors.
Uses extended functionality of the Parcel blob interface to pass
buffers around more efficiently while adapting to whether FDs
are allowed.

Bug: 21428802
Change-Id: If24926f4388d29aa2aac627000436beb015edcb9
parent c554b77b
Loading
Loading
Loading
Loading
+110 −76
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@
#include <sys/mman.h>
#include <cutils/ashmem.h>

#define DEBUG_PARCEL 0

namespace android {

class WrappedPixelRef : public SkPixelRef {
@@ -959,75 +961,82 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {
        }
    }

    int fd = p->readFileDescriptor();
    int dupFd = dup(fd);
    // Read the bitmap blob.
    size_t size = bitmap->getSize();
    android::Parcel::ReadableBlob blob;
    android::status_t status = p->readBlob(size, &blob);
    if (status) {
        SkSafeUnref(ctable);
        doThrowRE(env, "Could not read bitmap blob.");
        return NULL;
    }

    // Map the bitmap in place from the ashmem region if possible otherwise copy.
    Bitmap* nativeBitmap;
    if (blob.fd() >= 0 && (blob.isMutable() || !isMutable)) {
#if DEBUG_PARCEL
        ALOGD("Bitmap.createFromParcel: mapped contents of %s bitmap from %s blob "
                "(fds %s)",
                isMutable ? "mutable" : "immutable",
                blob.isMutable() ? "mutable" : "immutable",
                p->allowFds() ? "allowed" : "forbidden");
#endif
        // Dup the file descriptor so we can keep a reference to it after the Parcel
        // is disposed.
        int dupFd = dup(blob.fd());
        if (dupFd < 0) {
            blob.release();
            SkSafeUnref(ctable);
        doThrowRE(env, "Could not dup parcel fd.");
            doThrowRE(env, "Could not allocate dup blob fd.");
            return NULL;
        }

    bool readOnlyMapping = !isMutable;
    Bitmap* nativeBitmap = GraphicsJNI::mapAshmemPixelRef(env, bitmap.get(),
        ctable, dupFd, readOnlyMapping);
        // Map the pixels in place and take ownership of the ashmem region.
        nativeBitmap = GraphicsJNI::mapAshmemPixelRef(env, bitmap.get(),
                ctable, dupFd, const_cast<void*>(blob.data()), !isMutable);
        SkSafeUnref(ctable);
        if (!nativeBitmap) {
            close(dupFd);
            blob.release();
            doThrowRE(env, "Could not allocate ashmem pixel ref.");
            return NULL;
        }
    bitmap->pixelRef()->setImmutable();

    return GraphicsJNI::createBitmap(env, nativeBitmap,
            getPremulBitmapCreateFlags(isMutable), NULL, NULL, density);
        // Clear the blob handle, don't release it.
        blob.clear();
    } else {
#if DEBUG_PARCEL
        if (blob.fd() >= 0) {
            ALOGD("Bitmap.createFromParcel: copied contents of mutable bitmap "
                    "from immutable blob (fds %s)",
                    p->allowFds() ? "allowed" : "forbidden");
        } else {
            ALOGD("Bitmap.createFromParcel: copied contents from %s blob "
                    "(fds %s)",
                    blob.isMutable() ? "mutable" : "immutable",
                    p->allowFds() ? "allowed" : "forbidden");
        }
#endif

class Ashmem {
public:
    Ashmem(size_t sz, bool removeWritePerm) : mSize(sz) {
        int fd = -1;
        void *addr = nullptr;

        // Create new ashmem region with read/write priv
        fd = ashmem_create_region("bitmap", sz);
        if (fd < 0) {
            goto error;
        }
        addr = mmap(nullptr, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if (addr == MAP_FAILED) {
            goto error;
        }
        // If requested, remove the ability to make additional writeable to
        // this memory.
        if (removeWritePerm) {
            if (ashmem_set_prot_region(fd, PROT_READ) < 0) {
                goto error;
            }
        }
        mFd = fd;
        mPtr = addr;
        return;
error:
        if (fd >= 0) {
            close(fd);
        }
        if (addr) {
            munmap(addr, sz);
        }
        // Copy the pixels into a new buffer.
        nativeBitmap = GraphicsJNI::allocateJavaPixelRef(env, bitmap.get(), ctable);
        SkSafeUnref(ctable);
        if (!nativeBitmap) {
            blob.release();
            doThrowRE(env, "Could not allocate java pixel ref.");
            return NULL;
        }
    ~Ashmem() {
        if (mPtr) {
            close(mFd);
            munmap(mPtr, mSize);
        bitmap->lockPixels();
        memcpy(bitmap->getPixels(), blob.data(), size);
        bitmap->unlockPixels();

        // Release the blob handle.
        blob.release();
    }

    return GraphicsJNI::createBitmap(env, nativeBitmap,
            getPremulBitmapCreateFlags(isMutable), NULL, NULL, density);
}
    void *getPtr() const { return mPtr; }
    int getFd() const { return mFd; }
private:
    int mFd = -1;
    int mSize;
    void* mPtr = nullptr;
};

static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
                                     jlong bitmapHandle,
@@ -1064,26 +1073,51 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
        }
    }

    bool ashmemSrc = androidBitmap->getAshmemFd() >= 0;
    if (ashmemSrc && !isMutable) {
        p->writeDupFileDescriptor(androidBitmap->getAshmemFd());
    } else {
        Ashmem dstAshmem(bitmap.getSize(), !isMutable);
        if (!dstAshmem.getPtr()) {
            doThrowRE(env, "Could not allocate ashmem for new bitmap.");
    // Transfer the underlying ashmem region if we have one and it's immutable.
    android::status_t status;
    int fd = androidBitmap->getAshmemFd();
    if (fd >= 0 && !isMutable && p->allowFds()) {
#if DEBUG_PARCEL
        ALOGD("Bitmap.writeToParcel: transferring immutable bitmap's ashmem fd as "
                "immutable blob (fds %s)",
                p->allowFds() ? "allowed" : "forbidden");
#endif

        status = p->writeDupImmutableBlobFileDescriptor(fd);
        if (status) {
            doThrowRE(env, "Could not write bitmap blob file descriptor.");
            return JNI_FALSE;
        }
        return JNI_TRUE;
    }

    // Copy the bitmap to a new blob.
    bool mutableCopy = isMutable;
#if DEBUG_PARCEL
    ALOGD("Bitmap.writeToParcel: copying %s bitmap into new %s blob (fds %s)",
            isMutable ? "mutable" : "immutable",
            mutableCopy ? "mutable" : "immutable",
            p->allowFds() ? "allowed" : "forbidden");
#endif

    size_t size = bitmap.getSize();
    android::Parcel::WritableBlob blob;
    status = p->writeBlob(size, mutableCopy, &blob);
    if (status) {
        doThrowRE(env, "Could not copy bitmap to parcel blob.");
        return JNI_FALSE;
    }

    bitmap.lockPixels();
    const void* pSrc =  bitmap.getPixels();
    if (pSrc == NULL) {
            memset(dstAshmem.getPtr(), 0, bitmap.getSize());
        memset(blob.data(), 0, size);
    } else {
            memcpy(dstAshmem.getPtr(), pSrc, bitmap.getSize());
        memcpy(blob.data(), pSrc, size);
    }
    bitmap.unlockPixels();
        p->writeDupFileDescriptor(dstAshmem.getFd());
    }

    blob.release();
    return JNI_TRUE;
}

+11 −8
Original line number Diff line number Diff line
@@ -623,21 +623,21 @@ android::Bitmap* GraphicsJNI::allocateAshmemPixelRef(JNIEnv* env, SkBitmap* bitm
}

android::Bitmap* GraphicsJNI::mapAshmemPixelRef(JNIEnv* env, SkBitmap* bitmap,
                                                SkColorTable* ctable, int fd, bool readOnly) {
    int flags;

        SkColorTable* ctable, int fd, void* addr, bool readOnly) {
    const SkImageInfo& info = bitmap->info();
    if (info.fColorType == kUnknown_SkColorType) {
        doThrowIAE(env, "unknown bitmap configuration");
        return nullptr;
    }

    // Create new ashmem region with read/write priv
    flags = readOnly ? (PROT_READ) : (PROT_READ | PROT_WRITE);
    void* addr = mmap(NULL, ashmem_get_size_region(fd), flags, MAP_SHARED, fd, 0);
    if (!addr) {
        // Map existing ashmem region if not already mapped.
        int flags = readOnly ? (PROT_READ) : (PROT_READ | PROT_WRITE);
        addr = mmap(NULL, ashmem_get_size_region(fd), flags, MAP_SHARED, fd, 0);
        if (addr == MAP_FAILED) {
            return nullptr;
        }
    }

    // we must respect the rowBytes value already set on the bitmap instead of
    // attempting to compute our own.
@@ -645,6 +645,9 @@ android::Bitmap* GraphicsJNI::mapAshmemPixelRef(JNIEnv* env, SkBitmap* bitmap,

    android::Bitmap* wrapper = new android::Bitmap(addr, fd, info, rowBytes, ctable);
    wrapper->getSkBitmap(bitmap);
    if (readOnly) {
        bitmap->pixelRef()->setImmutable();
    }
    // since we're already allocated, we lockPixels right away
    // HeapAllocator behaves this way too
    bitmap->lockPixels();
+1 −1
Original line number Diff line number Diff line
@@ -99,7 +99,7 @@ public:
            SkColorTable* ctable);

    static android::Bitmap* mapAshmemPixelRef(JNIEnv* env, SkBitmap* bitmap,
            SkColorTable* ctable, int fd, bool readOnly);
            SkColorTable* ctable, int fd, void* addr, bool readOnly);

    /**
     * Given a bitmap we natively allocate a memory block to store the contents
+1 −1
Original line number Diff line number Diff line
@@ -211,7 +211,7 @@ static void android_os_Parcel_writeBlob(JNIEnv* env, jclass clazz, jlong nativeP
    }

    android::Parcel::WritableBlob blob;
    android::status_t err2 = parcel->writeBlob(length, &blob);
    android::status_t err2 = parcel->writeBlob(length, false, &blob);
    if (err2 != NO_ERROR) {
        signalExceptionForError(env, clazz, err2);
        return;