From 793ca1b4100f01560179b98ab37eaaecfaeb1dc8 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sat, 10 Oct 2026 09:05:09 +0700 Subject: [PATCH] android: native - hold charon_mutex while reading routing engine stats getRoutingStats/getDeadDirectTargets are polled from the UI once a second; a concurrent disconnect frees the engine under charon_mutex, so reading it without the lock was a use-after-free. Co-Authored-By: Claude Opus 5.5 --- .../src/main/jni/libandroidbridge/charonservice.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c index 5aad4e4cb..d0adadfea 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c @@ -1325,16 +1325,21 @@ JNI_METHOD(CharonVpnService, setUserspaceRoutingData, void, */ JNI_METHOD(CharonVpnService, getRoutingStats, jlongArray) { - direct_engine_t *engine = direct_engine_get(); + direct_engine_t *engine; direct_stats_t s = {}; jlong v[22]; jlongArray result; memset(&s, 0, sizeof(s)); + /* polled from the UI: a concurrent disconnect frees the engine under + * charon_mutex, so the pointer may only be used while holding it */ + pthread_mutex_lock(&charon_mutex); + engine = direct_engine_get(); if (engine) { engine->get_stats(engine, &s); } + pthread_mutex_unlock(&charon_mutex); v[0] = (jlong)s.tunnel_pkts; v[1] = (jlong)s.tunnel_bytes; v[2] = (jlong)s.direct_pkts; @@ -1370,7 +1375,7 @@ JNI_METHOD(CharonVpnService, getRoutingStats, jlongArray) */ JNI_METHOD(CharonVpnService, getDeadDirectTargets, jobjectArray) { - direct_engine_t *engine = direct_engine_get(); + direct_engine_t *engine; direct_dead_target_t t[DIRECT_DEAD_MAX]; jobjectArray result; jclass cls; @@ -1379,10 +1384,14 @@ JNI_METHOD(CharonVpnService, getDeadDirectTargets, jobjectArray) /* stack snapshot: zero it so no field can ever leak garbage */ memset(t, 0, sizeof(t)); + /* same race against a concurrent disconnect as getRoutingStats() */ + pthread_mutex_lock(&charon_mutex); + engine = direct_engine_get(); if (engine) { n = engine->get_dead_targets(engine, t, DIRECT_DEAD_MAX); } + pthread_mutex_unlock(&charon_mutex); cls = (*env)->FindClass(env, "java/lang/String"); if (!cls) {