Skip to content

CreateFrameBufferFromBytes() leaks acquired byte-array elements when FrameBuffer creation fails #1012

Description

@K-ANOY

I found a JNI array leak in CreateFrameBufferFromBytes(): it acquires the image bytes with GetByteArrayElements() and, if CreateFromRawBuffer() fails, returns the error without ever releasing them — and on that path Java never gets a handle to release them either.

File: tensorflow_lite_support/java/src/native/task/vision/jni_utils.cc

Function: CreateFrameBufferFromBytes

Relevant code:

jbyte* jimage_ptr = env->GetByteArrayElements(jimage_bytes, NULL);
// Free jimage_ptr together with frame_buffer after inference is finished.
jlong jimage_ptr_handle = reinterpret_cast<jlong>(jimage_ptr);
env->SetLongArrayRegion(jbyte_array_handle, 0, 1, &jimage_ptr_handle);

if (jimage_ptr == NULL) {
  ThrowException(env, kIllegalStateException, "...");
  return nullptr;
}

return CreateFromRawBuffer(
    reinterpret_cast<const uint8*>(jimage_ptr),
    FrameBuffer::Dimension{width, height},
    ConvertToFrameBufferFormat(env, jcolor_space_type),
    ConvertToFrameBufferOrientation(env, jorientation));

The acquisition uses a deferred-release scheme: jimage_ptr is stored in
jbyte_array_handle and is meant to be released later by deleteFrameBuffer()
(via ReleaseByteArrayElements()), once ownership reaches Java as a
FrameBufferData.

But CreateFromRawBuffer() returns a StatusOr that can fail (e.g. non-positive
dimensions on the planar paths, unsupported target format). The function returns
that status directly, so no branch releases jimage_ptr. The JNI entry point
(base_vision_task_api_jni.cc) then throws and returns kInvalidPointer, so Java
never receives a FrameBufferData and cannot call deleteFrameBuffer(). The
successful GetByteArrayElements() acquisition is lost — an image-sized buffer or a
pinned Java array, leaked on every failed construction.

Suggested fix: check the result before transferring ownership and release the
elements on failure. Since the code only reads the data, JNI_ABORT avoids a
copy-back:

auto frame_buffer_or = CreateFromRawBuffer(
    reinterpret_cast<const uint8*>(jimage_ptr),
    FrameBuffer::Dimension{width, height},
    ConvertToFrameBufferFormat(env, jcolor_space_type),
    ConvertToFrameBufferOrientation(env, jorientation));

if (!frame_buffer_or.ok()) {
  env->ReleaseByteArrayElements(jimage_bytes, jimage_ptr, JNI_ABORT);
  return frame_buffer_or.status();
}
return frame_buffer_or;

Ideally jimage_ptr should also be written into jbyte_array_handle only after
construction succeeds, so the handle never points at an already-released buffer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions