From 20f506fc3c2b2cc806529699711c922b4646c5c2 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 12:09:33 +0100 Subject: [PATCH] Contain JNI config failures and validate audio result buffers --- tests/test_android_replies.c | 1 + tools/chatgui-android/doc/ARCHITECTURE.md | 4 ++ .../jni_bridge/android_jni_bridge.c | 60 ++++++++++++++----- 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/tests/test_android_replies.c b/tests/test_android_replies.c index f92ee080..38370973 100644 --- a/tests/test_android_replies.c +++ b/tests/test_android_replies.c @@ -7,6 +7,7 @@ #include "../lib/debug_config.h" #include "../src/utun_instance.h" #include "../src/chat/chat_core.h" +#include "../src/chat/chat_core_priv.h" #include "../src/chat/chat_event.h" #include "../src/dm/dm_core.h" #include "../src/dm/dm_crypto.h" diff --git a/tools/chatgui-android/doc/ARCHITECTURE.md b/tools/chatgui-android/doc/ARCHITECTURE.md index 3ef9e996..72767605 100644 --- a/tools/chatgui-android/doc/ARCHITECTURE.md +++ b/tools/chatgui-android/doc/ARCHITECTURE.md @@ -72,6 +72,10 @@ UTUN-сервис включается отдельно через `[gui] utun_e Реестры команд освобождают также PCM/compressor и собственные cache-файлы при отмене до исполнения. JNI строки преобразуются через обычный UTF-8 byte[] в обе стороны: имена, JSON и пути сохраняют emoji. NUL во входной строке отклоняется, ошибки выделения возвращают NULL/отказ, а не усечённую строку. +SQL/память в channels/messages/members возвращает NULL, отдельно от подтверждённого пустого массива. +Массивы сокетов/линков строятся целиком с экранированием имён; при ошибке прежний UI-снимок сохраняется. +JNI event callback удерживает local ref на время вызова, независимо от замены global ref. +Ошибка копирования не доставляет повреждённый payload; исключение callback описывается/снимается для следующих событий. Заимствованные inst/ua не удерживают ресурс; они пригодны только в потоке владельца. `instance_lite_post` публикует команду под защитой времени жизни worker и заполняет её поле instance. Аргумент принадлежит callback, а при отмене — discard; отдельный реестр переживает удаление posted_task. diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.c b/tools/chatgui-android/jni_bridge/android_jni_bridge.c index c3af4b92..36b23b17 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.c +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.c @@ -176,6 +176,7 @@ int utun_bridge_destroy(void) { voice_recorder_deinit(); utun_config_set_provider(NULL); __atomic_store_n(&g_on_log, NULL, __ATOMIC_RELEASE); + g_on_event = NULL; g_cfg_string = NULL; g_cfg_int64 = NULL; g_cfg_int = NULL; @@ -1835,25 +1836,47 @@ void instance_event_handler(int type, const uint8_t* data, int len) { static jstring jni_get_string_callback(JNIEnv* env, const char* key) { if (!g_config_callback) return NULL; jclass cls = (*env)->GetObjectClass(env, g_config_callback); - jmethodID mid = (*env)->GetMethodID(env, cls, "getString", "(Ljava/lang/String;)Ljava/lang/String;"); - if (!mid) return NULL; - jstring jkey = jni_text(env, key); - jstring result = (jstring)(*env)->CallObjectMethod(env, g_config_callback, mid, jkey); - (*env)->DeleteLocalRef(env, jkey); + jmethodID mid = cls ? (*env)->GetMethodID(env, cls, "getString", "(Ljava/lang/String;)Ljava/lang/String;") : NULL; + jstring jkey = mid ? jni_text(env, key) : NULL; + jstring result = jkey ? (jstring)(*env)->CallObjectMethod(env, g_config_callback, mid, jkey) : NULL; + if (jkey) (*env)->DeleteLocalRef(env, jkey); + if (cls) (*env)->DeleteLocalRef(env, cls); return result; } static const char* jni_cfg_get_string(const char* key) { JNIEnv* env = NULL; - if (!g_jvm || (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) - return NULL; + static __thread char buf[4096]; + buf[0] = 0; + if (!g_jvm) { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI config: JVM unavailable"); return NULL; } + int attached = 0; + jint rc = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6); + if (rc == JNI_EDETACHED) { + rc = (*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL); + if (rc == JNI_OK) attached = 1; + } + if (rc != JNI_OK) { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI config: attach/GetEnv failed rc=%d", rc); return NULL; } + if ((*env)->ExceptionCheck(env)) { + __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI config: preserving existing exception key=%s", key); goto finish; + } jstring js = jni_get_string_callback(env, key); - if (!js) return NULL; - const char* result = jni_utf8(env, js); - static char buf[256]; - snprintf(buf, sizeof(buf), "%s", result ? result : ""); - if (result) u_free((void*)result); - (*env)->DeleteLocalRef(env, js); + if (js && !(*env)->ExceptionCheck(env)) { + char* result = jni_utf8(env, js); + if (result) { + size_t length = strlen(result); + if (length < sizeof(buf)) memcpy(buf, result, length + 1); + else __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI config: value too long key=%s bytes=%zu", key, length); + u_free(result); + } + } + if (js) (*env)->DeleteLocalRef(env, js); + if ((*env)->ExceptionCheck(env)) { + __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI config: callback failed key=%s", key); + (*env)->ExceptionDescribe(env); (*env)->ExceptionClear(env); + } +finish: + if (attached && (*g_jvm)->DetachCurrentThread(g_jvm) != JNI_OK) + __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI config: detach failed key=%s", key); return buf[0] ? buf : NULL; } @@ -2027,6 +2050,7 @@ JNIEXPORT jlong JNICALL Java_com_utun_chat_data_NativeLib_nativeDeriveNodeId( jsize pk_len = (*env)->GetArrayLength(env, pubkey); if (pk_len < 32) return 0; uint8_t* pk = (uint8_t*)(*env)->GetByteArrayElements(env, pubkey, NULL); + if (!pk) { bridge_log(BLEV_ERROR, "Node ID: public key JNI access failed"); return 0; } uint64_t node_id = sc_derive_node_id_from_pubkey(pk); (*env)->ReleaseByteArrayElements(env, pubkey, (jbyte*)pk, JNI_ABORT); return (jlong)node_id; @@ -2568,6 +2592,7 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeVoiceFeed( if (!samples) return JNI_FALSE; jsize len = (*env)->GetArrayLength(env, samples); jshort* data = (*env)->GetShortArrayElements(env, samples, NULL); + if (!data) { bridge_log(BLEV_ERROR, "Voice capture: JNI PCM access failed"); return JNI_FALSE; } int rc = utun_bridge_voice_feed((const int16_t*)data, (int)len); (*env)->ReleaseShortArrayElements(env, samples, data, JNI_ABORT); return (rc == 0) ? JNI_TRUE : JNI_FALSE; @@ -2689,13 +2714,18 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativePhotoSend( JNIEXPORT jlong JNICALL Java_com_utun_chat_data_NativeLib_nativeVoiceDecodeOpen( JNIEnv* env, jobject thiz, jstring filePath, jintArray outInfo) { (void)thiz; - if (!filePath || !outInfo) return 0; + if (!filePath || !outInfo || (*env)->GetArrayLength(env, outInfo) < 3) { + bridge_log(BLEV_ERROR, "Voice decode: expected path and IntArray(3)"); return 0; + } const char* fp = jni_utf8(env, filePath); int sr = 0, ch = 0, total = 0; void* h = utun_bridge_voice_decode_open(fp, &sr, &ch, &total); if (h) { jint info[3] = {sr, ch, total}; (*env)->SetIntArrayRegion(env, outInfo, 0, 3, info); + if ((*env)->ExceptionCheck(env)) { + bridge_log(BLEV_ERROR, "Voice decode: output metadata JNI copy failed"); utun_bridge_voice_decode_close(h); h = NULL; + } } if (fp) u_free((void*)fp); return (jlong)(intptr_t)h; @@ -2707,6 +2737,7 @@ JNIEXPORT jint JNICALL Java_com_utun_chat_data_NativeLib_nativeVoiceDecodeRead( if (handle == 0 || !buf) return -1; jsize max = (*env)->GetArrayLength(env, buf); jshort* data = (*env)->GetShortArrayElements(env, buf, NULL); + if (!data) { bridge_log(BLEV_ERROR, "Voice decode: JNI PCM buffer access failed"); return -1; } int read = utun_bridge_voice_decode_read((void*)(intptr_t)handle, (int16_t*)data, (int)max); (*env)->ReleaseShortArrayElements(env, buf, data, 0); return (jint)read; @@ -2971,6 +3002,7 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeCallAudioFeed } jsize len = (*env)->GetArrayLength(env, samples); jshort* data = (*env)->GetShortArrayElements(env, samples, NULL); + if (!data) { bridge_log(BLEV_ERROR, "Radio capture: JNI PCM access failed"); return JNI_FALSE; } if (!data) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call capture: JNI PCM access failed"); return JNI_FALSE;