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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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