From 199ff401944d5eaf6e2d83814e661f035b93157f Mon Sep 17 00:00:00 2001 From: Renan Lavarec Date: Thu, 16 Apr 2026 10:55:38 +0200 Subject: [PATCH] mediacodec: add check for surface existence Root Cause The crash occurs when MediaCodec.configure() receives a Java Surface object whose native peer has been released (Surface.release() or SurfaceTexture.release() was called). The Java object is still a valid JNI reference, but its internal mNativeObject field is 0/NULL. When the Android framework calls getSurface() -> RefBase::incStrong() on this null native pointer, it crashes with a null dereference in the atomic operation. The call path: control_thread -> op_start -> init_mediacodec -> avcodec_open2 -> FFmpeg mediacodec init -> MediaCodec.configure(format, surface, null, flags) -> CRASH. Fixes Applied (defense in depth, 3 layers) 1. mediacodec_surface.c:30: Added is_surface_valid() helper that calls Surface.isValid() via JNI, and integrated it into ff_mediacodec_surface_ref(). When the Surface is invalid, it returns NULL instead of creating a reference to a dead surface. This causes s->surface to be NULL in the decoder, preventing the crash. 2. mediacodec_wrapper.c: Added Surface.isValid() checks in both: mediacodec_jni_configure() -> before calling MediaCodec.configure() via JNI (the exact crash path from the stack trace) mediacodec_ndk_configure() -> before calling ANativeWindow_fromSurface() (which would also crash with the same issue) Both return AVERROR_EXTERNAL if the Surface is invalid, which is handled gracefully by the callers (configure failure -> decoder fallback or retry). --- libavcodec/mediacodec_surface.c | 31 ++++++++++++++++++++++++++- libavcodec/mediacodec_wrapper.c | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/libavcodec/mediacodec_surface.c b/libavcodec/mediacodec_surface.c index ef41cdafa7..7df32f8af1 100644 --- a/libavcodec/mediacodec_surface.c +++ b/libavcodec/mediacodec_surface.c @@ -27,6 +27,28 @@ #include "ffjni.h" #include "mediacodec_surface.h" +static int is_surface_valid(JNIEnv *env, void *surface, void *log_ctx) +{ + if (!env) + return 0; + jclass surface_class = (*env)->FindClass(env, "android/view/Surface"); + if (!surface_class) { + ff_jni_exception_check(env, 1, log_ctx); + av_log(log_ctx, AV_LOG_WARNING, "Could not find android/view/Surface class\n"); + return 1; /* assume valid if we cannot check */ + } + jmethodID is_valid_id = (*env)->GetMethodID(env, surface_class, "isValid", "()Z"); + if (!is_valid_id) { + (*env)->DeleteLocalRef(env, surface_class); + av_log(log_ctx, AV_LOG_WARNING, "Could not find Surface.isValid() method\n"); + return 1; /* assume valid if we cannot check */ + } + jboolean valid = (*env)->CallBooleanMethod(env, (jobject)surface, is_valid_id); + (*env)->DeleteLocalRef(env, surface_class); + ff_jni_exception_check(env, 1, log_ctx); + return valid; +} + FFANativeWindow *ff_mediacodec_surface_ref(void *surface, void *native_window, void *log_ctx) { FFANativeWindow *ret; @@ -39,8 +61,15 @@ FFANativeWindow *ff_mediacodec_surface_ref(void *surface, void *native_window, v JNIEnv *env = NULL; env = ff_jni_get_env(log_ctx); - if (env) + if (env) { + if (!is_surface_valid(env, surface, log_ctx)) { + av_log(log_ctx, AV_LOG_ERROR, + "Surface %p is not valid (native peer released?)\n", surface); + av_freep(&ret); + return NULL; + } ret->surface = (*env)->NewGlobalRef(env, surface); + } } if (native_window) { diff --git a/libavcodec/mediacodec_wrapper.c b/libavcodec/mediacodec_wrapper.c index e986fb6db9..892a608580 100644 --- a/libavcodec/mediacodec_wrapper.c +++ b/libavcodec/mediacodec_wrapper.c @@ -1638,6 +1638,26 @@ static int mediacodec_jni_configure(FFAMediaCodec *ctx, JNI_GET_ENV_OR_RETURN(env, codec, AVERROR_EXTERNAL); + /* Validate the Surface before passing to MediaCodec.configure(), + * which will crash if the Surface's native peer has been released */ + if (surface) { + jclass surface_class = (*env)->FindClass(env, "android/view/Surface"); + if (surface_class) { + jmethodID is_valid_id = (*env)->GetMethodID(env, surface_class, "isValid", "()Z"); + if (is_valid_id) { + jboolean valid = (*env)->CallBooleanMethod(env, (jobject)surface, is_valid_id); + if (!valid) { + av_log(ctx, AV_LOG_ERROR, "Surface is not valid, cannot configure codec\n"); + (*env)->DeleteLocalRef(env, surface_class); + ff_jni_exception_check(env, 0, 0); + return AVERROR_EXTERNAL; + } + } + (*env)->DeleteLocalRef(env, surface_class); + } + ff_jni_exception_check(env, 0, 0); + } + if (flags & codec->CONFIGURE_FLAG_ENCODE) { if (surface && !codec->jfields.set_input_surface_id) { av_log(ctx, AV_LOG_ERROR, "System doesn't support setInputSurface\n"); @@ -2390,6 +2410,23 @@ static int mediacodec_ndk_configure(FFAMediaCodec* ctx, if (window->surface) { JNIEnv *env = NULL; JNI_GET_ENV_OR_RETURN(env, ctx, -1); + /* Validate the Surface before calling ANativeWindow_fromSurface, + * which will crash if the Surface's native peer has been released */ + jclass surface_class = (*env)->FindClass(env, "android/view/Surface"); + if (surface_class) { + jmethodID is_valid_id = (*env)->GetMethodID(env, surface_class, "isValid", "()Z"); + if (is_valid_id) { + jboolean valid = (*env)->CallBooleanMethod(env, window->surface, is_valid_id); + if (!valid) { + av_log(ctx, AV_LOG_ERROR, "Surface is not valid, cannot configure codec\n"); + (*env)->DeleteLocalRef(env, surface_class); + ff_jni_exception_check(env, 0, 0); + return AVERROR_EXTERNAL; + } + } + (*env)->DeleteLocalRef(env, surface_class); + } + ff_jni_exception_check(env, 0, 0); native_window = ANativeWindow_fromSurface(env, window->surface); // Save for release codec->window = native_window;