Skip to content

Commit 923e940

Browse files
committed
fix(android): bound JNI string local references
Android JNI entry points return string payloads for version checks, log drains, update checks, SNI probes, live stats, and pipeline diagnostics. Several of those methods are invoked repeatedly by the Kotlin UI while the proxy is running, so each path should keep JNI local-reference ownership explicit and bounded even when future payload construction adds intermediate Java objects. Add a shared string_to_jstring helper that builds returned Java strings inside an explicit local frame with with_local_frame_returning_local. The frame preserves only the returned jstring for the JVM caller and releases temporary local handles before the native method exits. Allocation or frame setup failures continue to return a null jstring, preserving the existing failure contract. Route every Android string-returning native entry point through the helper without changing payload contents, method names, signatures, threading behavior, or Kotlin call sites. This keeps repeated telemetry and diagnostics polling from relying on the implicit native-method frame and gives the JNI boundary a single audited conversion path.
1 parent 40b5386 commit 923e940

1 file changed

Lines changed: 21 additions & 14 deletions

File tree

src/android_jni.rs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use std::path::PathBuf;
2020
use std::sync::atomic::{AtomicU64, Ordering};
2121
use std::sync::{Arc, Mutex, OnceLock};
2222

23-
use jni::objects::{JClass, JString};
23+
use jni::objects::{JClass, JObject, JString};
2424
use jni::sys::{jboolean, jlong, jstring, JNI_FALSE, JNI_TRUE};
2525
use jni::JNIEnv;
2626
use tokio::runtime::Runtime;
@@ -145,6 +145,15 @@ fn jstring_to_string(env: &mut JNIEnv, s: &JString) -> String {
145145
.unwrap_or_else(|_| String::new())
146146
}
147147

148+
/// Helper: String -> jstring, returning null on allocation failure.
149+
fn string_to_jstring(env: &mut JNIEnv, value: &str) -> jstring {
150+
let obj: jni::errors::Result<JObject> = env.with_local_frame_returning_local(4, |env| {
151+
env.new_string(value).map(JObject::from)
152+
});
153+
obj.map(|s| s.into_raw() as jstring)
154+
.unwrap_or(std::ptr::null_mut())
155+
}
156+
148157
fn safe<F: FnOnce() -> R + std::panic::UnwindSafe, R>(default: R, f: F) -> R {
149158
std::panic::catch_unwind(f).unwrap_or(default)
150159
}
@@ -327,11 +336,11 @@ pub extern "system" fn Java_com_therealaleph_mhrv_Native_exportCa(
327336
/// `Native.version()` -> String. Trivial smoke test for the JNI linkage.
328337
#[no_mangle]
329338
pub extern "system" fn Java_com_therealaleph_mhrv_Native_version<'a>(
330-
env: JNIEnv<'a>,
339+
mut env: JNIEnv<'a>,
331340
_class: JClass,
332341
) -> jstring {
333342
let v = env!("CARGO_PKG_VERSION");
334-
env.new_string(v).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut())
343+
string_to_jstring(&mut env, v)
335344
}
336345

337346
/// `Native.drainLogs()` -> String. Returns the full ring buffer as a single
@@ -340,7 +349,7 @@ pub extern "system" fn Java_com_therealaleph_mhrv_Native_version<'a>(
340349
/// for display. Empty string when there's nothing to read.
341350
#[no_mangle]
342351
pub extern "system" fn Java_com_therealaleph_mhrv_Native_drainLogs<'a>(
343-
env: JNIEnv<'a>,
352+
mut env: JNIEnv<'a>,
344353
_class: JClass,
345354
) -> jstring {
346355
let out = safe(String::new(), AssertUnwindSafe(|| {
@@ -351,7 +360,7 @@ pub extern "system" fn Java_com_therealaleph_mhrv_Native_drainLogs<'a>(
351360
let lines: Vec<String> = g.drain(..).collect();
352361
lines.join("\n")
353362
}));
354-
env.new_string(out).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut())
363+
string_to_jstring(&mut env, &out)
355364
}
356365

357366
/// `Native.checkUpdate()` -> String. Runs the same `update_check::check`
@@ -367,7 +376,7 @@ pub extern "system" fn Java_com_therealaleph_mhrv_Native_drainLogs<'a>(
367376
/// Blocking — hit from a background dispatcher.
368377
#[no_mangle]
369378
pub extern "system" fn Java_com_therealaleph_mhrv_Native_checkUpdate<'a>(
370-
env: JNIEnv<'a>,
379+
mut env: JNIEnv<'a>,
371380
_class: JClass,
372381
) -> jstring {
373382
let result_json = safe(
@@ -383,9 +392,7 @@ pub extern "system" fn Java_com_therealaleph_mhrv_Native_checkUpdate<'a>(
383392
update_check_to_json(&outcome)
384393
}),
385394
);
386-
env.new_string(result_json)
387-
.map(|s| s.into_raw())
388-
.unwrap_or(std::ptr::null_mut())
395+
string_to_jstring(&mut env, &result_json)
389396
}
390397

391398
fn update_check_to_json(u: &crate::update_check::UpdateCheck) -> String {
@@ -455,7 +462,7 @@ pub extern "system" fn Java_com_therealaleph_mhrv_Native_testSni<'a>(
455462
_ => r#"{"ok":false,"error":"unknown"}"#.to_string(),
456463
}
457464
}));
458-
env.new_string(result_json).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut())
465+
string_to_jstring(&mut env, &result_json)
459466
}
460467

461468
/// `Native.statsJson(long handle)` -> String. Returns a JSON blob with the
@@ -466,7 +473,7 @@ pub extern "system" fn Java_com_therealaleph_mhrv_Native_testSni<'a>(
466473
/// timer to render the "Usage today (estimated)" card.
467474
#[no_mangle]
468475
pub extern "system" fn Java_com_therealaleph_mhrv_Native_statsJson<'a>(
469-
env: JNIEnv<'a>,
476+
mut env: JNIEnv<'a>,
470477
_class: JClass,
471478
handle: jlong,
472479
) -> jstring {
@@ -483,21 +490,21 @@ pub extern "system" fn Java_com_therealaleph_mhrv_Native_statsJson<'a>(
483490
};
484491
f.snapshot_stats().to_json()
485492
}));
486-
env.new_string(out).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut())
493+
string_to_jstring(&mut env, &out)
487494
}
488495

489496
/// `Native.pipelineDebugJson()` -> String. Snapshot of pipeline debug state:
490497
/// elevated session count, batch semaphore usage, recent ramp/drop events.
491498
/// Temporary — for the debug overlay.
492499
#[no_mangle]
493500
pub extern "system" fn Java_com_therealaleph_mhrv_Native_pipelineDebugJson<'a>(
494-
env: JNIEnv<'a>,
501+
mut env: JNIEnv<'a>,
495502
_class: JClass,
496503
) -> jstring {
497504
let out = safe(String::new(), AssertUnwindSafe(|| {
498505
crate::tunnel_client::pipeline_debug::to_json()
499506
}));
500-
env.new_string(out).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut())
507+
string_to_jstring(&mut env, &out)
501508
}
502509

503510
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)