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 <[email protected]>
This commit is contained in:
DenozordecandClaude Opus 5.5 committed 2026-10-10 09:05:09 +07:00
1 parent 5e38fc2f76
commit 793ca1b410
1 file changed
+11 -2
@@ -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)
{