Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FR] JavaVM access for native libraries #1320

Closed
alexcohn opened this issue Jul 20, 2020 · 20 comments
Closed

[FR] JavaVM access for native libraries #1320

alexcohn opened this issue Jul 20, 2020 · 20 comments

Comments

@alexcohn
Copy link

alexcohn commented Jul 20, 2020

Is your feature request related to a problem? Please describe.

Many platform features can be accessed only through Java APIs. This means that a utility library needs access to JVM if it needs to communicate with the platform. This includes all issues that involve file system locations (where can the library put its output files?), assets, etc.

Describe alternatives you've considered

Today, we work around these limitations by adding custom Java initialization code and callbacks to our library, calling loadLibrary() to pass the JavaVM *, and using these callbacks often. Another problem with these callbacks is that they are tricky to obfuscate, and provide an easy surface for reverse engineering, phishing, or other attacks.

Describe the solution you'd like

I'd like to have a platform way to get the JavaVM * from any point of native code (possibly through JNI_GetCreatedJavaVMs()), so that this pointer does not need to be passed through the long chain of dependency (some libraries "in the middle" may not be aware that they run on Android at all). I'd like the access to application context (via ActivityThread.currentActivityThread().getApplication()) be officially documented and wrapped for C++. I'd like an easy C++ wrapper support for JNI thread attach and detach. I'd like to be able to distribute my library as C++-only without Java part, reducing the integration headache.

Additional context

This approach is not intended to replace the current practice, and does not suit everybody. Most native libraries can and should continue to be distributed as AAR with Java and enclosed .so, and quite often it is right to keep Java the only public interface of these libraries.

@enh-google
Copy link
Collaborator

it's passed to JNI_OnLoad, and you could stash it in a global there? there's also JNI_GetCreatedJavaVMs.

@alexcohn
Copy link
Author

The problem with JNI_OnLoad() is that it's not invoked on a native library that is loaded from another native library (even if the latter is loaded explicitly from Java). As for, JNI_GetCreatedJavaVMs(), it would have answered my request, but, unfortunately, it has never been exported in Android.

@enh-google
Copy link
Collaborator

but, unfortunately, it has never been exported in Android

what makes you say that? it's in jni.h and it's implemented in ART.

@jmgao
Copy link
Contributor

jmgao commented Jul 20, 2020

As for, JNI_GetCreatedJavaVMs(), it would have answered my request, but, unfortunately, it has never been exported in Android.

Looks like this is a combination of a bad comment in the original headers and the JNI functions never being exported in a stub library, so you can't link against free functions. The comment is a lie: the function has always been there, so you should be able to just dlsym it.

@DanAlbert
Copy link
Member

It's exported by ART, right? Not something you can link against anyway? Would a weak symbol work?

@enh-google
Copy link
Collaborator

As for, JNI_GetCreatedJavaVMs(), it would have answered my request, but, unfortunately, it has never been exported in Android.

Looks like this is a combination of a bad comment in the original headers and the JNI functions never being exported in a stub library, so you can't link against free functions. The comment is a lie: the function has always been there, so you should be able to just dlsym it.

yeah, i've mailed oth@ because i notice that the platform libnativehelper has a dlsym() call for this. so (a) that ought to work right now (though i'd recommend you use NULL as the handle rather than assume you know exactly which library is providing the implementation), but (b) i suspect that longer term everyone's better off if we ensure this is actually exposed in the NDK libraries.

@alexcohn
Copy link
Author

As @jmgao pointed out, the function is not hidden by libart.so, and it was possible to dlsym() it once. I believe it is still possible to dlopen("libart.so") these days (API 29+), using some other hack (do_dlopen()), but not naϊvely.

This request is not about how it is possible to work around all these problems, but rather to provide an officially supported way.

@DanAlbert
Copy link
Member

This request is not about how it is possible to work around all these problems, but rather to provide an officially supported way.

No worries, we understand :) Sounds like we ought to expose this from some platform library going forward ("some platform library" is probably how this got overlooked in the first place, it's not clear where this belongs on Android) and make a wrapper to backport the dlsym dance for old devices.

@DanAlbert
Copy link
Member

(oh, but we forgot to reopen, that'll cause some confusion!)

@DanAlbert DanAlbert reopened this Jul 20, 2020
@jmgao
Copy link
Contributor

jmgao commented Jul 20, 2020

As @jmgao pointed out, the function is not hidden by libart.so, and it was possible to dlsym() it once. I believe it is still possible to dlopen("libart.so") these days (API 29+), using some other hack (do_dlopen()), but not naϊvely.

This request is not about how it is possible to work around all these problems, but rather to provide an officially supported way.

dlsym(RTLD_DEFAULT, "...") should Just Work, but this is probably worth testing against various versions due to linker namespace changes.

@alexcohn
Copy link
Author

alexcohn commented Jul 21, 2020

dlsym(RTLD_DEFAULT, "...") should Just Work, but this is probably worth testing against various versions due to linker namespace changes.

At least for me, on API 29 official emulator, it Just Doesn't.

On API 21, the same code works perfectly:

void *libart_dso = dlopen("libart.so", RTLD_NOW);
JNI_GetCreatedJavaVMs_t JNI_GetCreatedJavaVMs = (JNI_GetCreatedJavaVMs_t)dlsym(libart_dso, "JNI_GetCreatedJavaVMs");
int rc = JNI_GetCreatedJavaVMs(&javaVm, 1, &n);

and this is fine, too:

JNI_GetCreatedJavaVMs_t JNI_GetCreatedJavaVMs = (JNI_GetCreatedJavaVMs_t)dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs");

@alexcohn
Copy link
Author

If it were not for support of older platforms, one possible solution would be to have JNI_OnLoad() invoked for all libraries, even if they are dlopened not from Java.

@enh-google
Copy link
Collaborator

(the ART team has created internal bug b/161773988 "Expose JNI_GetCreatedVMs to apps" for this.)

@alexcohn
Copy link
Author

@lufinkey
Copy link

@alexcohn Do you have a solution for calling JNI_GetCreatedJavaVMs in the Android emulator? I've been stuck on this as well and have the same issue with JNI_OnLoad

@alexcohn
Copy link
Author

Do you have a solution for calling JNI_GetCreatedJavaVMs in the Android emulator? I've been stuck on this as well and have the same issue with JNI_OnLoad

I believe that on the emulator, having root access, you can load libart.so. See e.g. https://blog.quarkslab.com/android-runtime-restrictions-bypass.html

@enduringstack
Copy link

so how get JavaVm through libart.so on api 29+?

@jorgschulze73
Copy link

Any updates on this one? I'm also in the same situation: need to get an instance of a JVM from a native library for which JNI_OnLoad is not called, so I can't even cache it from there

@DanAlbert
Copy link
Member

JNI_GetCreatedJavaVMs was added in API 31.

https://android-review.googlesource.com/c/platform/libnativehelper/+/1414232/5/libnativehelper.map.txt#11

$ readelf -sW android-ndk-r23/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/31/libnativehelper.so | grep GetCreated 
readelf: Warning: Unrecognized form: 0x23
     5: 0000000000001028     8 FUNC    GLOBAL DEFAULT    9 JNI_GetCreatedJavaVMs@@LIBNATIVEHELPER_S
    23: 0000000000001028     8 FUNC    GLOBAL DEFAULT    9 JNI_GetCreatedJavaVMs

@paxbun
Copy link

paxbun commented Mar 14, 2024

https://blog.quarkslab.com/android-runtime-restrictions-bypass.html

@enduringstack @DanAlbert Thanks to the link to the post by @alexcohn, I achieved this in API level 29 and 30. This is my approach:

  1. Find libart.so using dl_iterate_phdr_callback(). You can retrieve the path of the .so file, and the base address that the ART is loaded.
  2. fread() libart.so and prase the file to get the symbol offset. I used goblin to parse the .so file.
  3. Add the symbol offset to the base address retrieved in 1. That is the address of JNI_GetCreatedJavaVMs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants