Skip to content

Commit 8f3cd54

Browse files
authored
Make sure the thread is attached to JVM before AIBinder_transact (grpc#29212)
NdkBinder expects the thread to be already attached to JVM when AIBinder_transact is called with a local Java binder. If that is not the case, a null pointer dereference will happen in NdkBinder. This commit tries to cache the pointer to JVM when a new connection is created (one of the from/toJavaBinder will be called in that case) and tries to ensure the JVM is attached to current thread before calling AIBinder_transact.
1 parent 902d7d9 commit 8f3cd54

File tree

1 file changed

+55
-0
lines changed

1 file changed

+55
-0
lines changed

src/core/ext/transport/binder/utils/ndk_binder.cc

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424

2525
#include <grpc/support/log.h>
2626

27+
#include "src/core/lib/gpr/tls.h"
28+
#include "src/core/lib/gprpp/sync.h"
29+
2730
namespace {
2831
void* GetNdkBinderHandle() {
2932
// TODO(mingcl): Consider using RTLD_NOLOAD to check if it is already loaded
@@ -37,6 +40,53 @@ void* GetNdkBinderHandle() {
3740
}
3841
return handle;
3942
}
43+
44+
JavaVM* g_jvm = nullptr;
45+
grpc_core::Mutex g_jvm_mu;
46+
47+
// Whether the thread has already attached to JVM (this is to prevent
48+
// repeated attachment in `AttachJvm()`)
49+
GPR_THREAD_LOCAL(bool) g_is_jvm_attached = false;
50+
51+
void SetJvm(JNIEnv* env) {
52+
// OK to lock here since this function will only be called once for each
53+
// connection.
54+
grpc_core::MutexLock lock(&g_jvm_mu);
55+
if (g_jvm != nullptr) {
56+
return;
57+
}
58+
JavaVM* jvm = nullptr;
59+
jint error = env->GetJavaVM(&jvm);
60+
if (error != JNI_OK) {
61+
gpr_log(GPR_ERROR, "Failed to get JVM");
62+
}
63+
g_jvm = jvm;
64+
gpr_log(GPR_INFO, "JVM cached");
65+
}
66+
67+
// `SetJvm` need to be called in the process before `AttachJvm`. This is always
68+
// the case because one of `AIBinder_fromJavaBinder`/`AIBinder_toJavaBinder`
69+
// will be called before we actually uses the binder. Return `false` if not able
70+
// to attach to JVM. Return `true` if JVM is attached (or already attached).
71+
bool AttachJvm() {
72+
if (g_is_jvm_attached) {
73+
return true;
74+
}
75+
// Note: The following code would be run at most once per thread.
76+
grpc_core::MutexLock lock(&g_jvm_mu);
77+
if (g_jvm == nullptr) {
78+
gpr_log(GPR_ERROR, "JVM not cached yet");
79+
return false;
80+
}
81+
JNIEnv* env_unused;
82+
// Note that attach a thread that is already attached is a no-op, so it is
83+
// fine to call this again if the thread has already been attached by other.
84+
g_jvm->AttachCurrentThread(&env_unused, /* thr_args= */ nullptr);
85+
gpr_log(GPR_INFO, "JVM attached successfully");
86+
g_is_jvm_attached = true;
87+
return true;
88+
}
89+
4090
} // namespace
4191

4292
namespace grpc_binder {
@@ -67,6 +117,7 @@ void* AIBinder_getUserData(AIBinder* binder) {
67117
uid_t AIBinder_getCallingUid() { FORWARD(AIBinder_getCallingUid)(); }
68118

69119
AIBinder* AIBinder_fromJavaBinder(JNIEnv* env, jobject binder) {
120+
SetJvm(env);
70121
FORWARD(AIBinder_fromJavaBinder)(env, binder);
71122
}
72123

@@ -97,6 +148,9 @@ void AIBinder_decStrong(AIBinder* binder) {
97148
binder_status_t AIBinder_transact(AIBinder* binder, transaction_code_t code,
98149
AParcel** in, AParcel** out,
99150
binder_flags_t flags) {
151+
if (!AttachJvm()) {
152+
gpr_log(GPR_ERROR, "failed to attach JVM. AIBinder_transact might fail.");
153+
}
100154
FORWARD(AIBinder_transact)(binder, code, in, out, flags);
101155
}
102156

@@ -155,6 +209,7 @@ binder_status_t AIBinder_prepareTransaction(AIBinder* binder, AParcel** in) {
155209
}
156210

157211
jobject AIBinder_toJavaBinder(JNIEnv* env, AIBinder* binder) {
212+
SetJvm(env);
158213
FORWARD(AIBinder_toJavaBinder)(env, binder);
159214
}
160215

0 commit comments

Comments
 (0)