signalapp / libsignal

Home to the Signal Protocol as well as other cryptographic primitives which make Signal possible.
GNU Affero General Public License v3.0
3.05k stars 359 forks source link

Issue with cached class loader and graalvm native-image #576

Closed AsamK closed 1 month ago

AsamK commented 1 month ago

When using libsignal-client 0.46.0 (Java) in a project compiled to native code with graalvm, loading java classes from rust code doesn't work anymore. Somehow the cached class loader instance (introduced in 53699f11db41a28b13cb561ae3e8b49c289e957f) cannot load classes, that load fine with the normal env find_class function.

For testing I've modified the libsignal-client code locally to fallback to find_class in case the cached class loader fails to load a class, and that fixes the issue.

I see two possible fixes:

I can also make a PR, if you're ok one of the fixes or have another suggestion.

Example stack trace, but also happens with other classes:

java.lang.AssertionError: java.lang.ClassNotFoundException: org.signal.libsignal.protocol.message.SignalMessage
        at org.signal.libsignal.internal.FilterExceptions.reportUnexpectedException(FilterExceptions.java:70)
        at org.signal.libsignal.internal.FilterExceptions.filterExceptions(FilterExceptions.java:216)
        at org.signal.libsignal.protocol.SessionCipher.encrypt(SessionCipher.java:99)
        at org.signal.libsignal.protocol.SessionCipher.encrypt(SessionCipher.java:82)
        at org.whispersystems.signalservice.api.crypto.SignalSessionCipher.encrypt(SignalSessionCipher.java:32)
        at org.whispersystems.signalservice.api.crypto.EnvelopeContent$Encrypted.processSealedSender(EnvelopeContent.java:84)
        at org.whispersystems.signalservice.api.crypto.SignalServiceCipher.encrypt(SignalServiceCipher.java:123)
        at org.whispersystems.signalservice.api.SignalServiceMessageSender.getEncryptedMessage(SignalServiceMessageSender.java:2978)
        at org.whispersystems.signalservice.api.SignalServiceMessageSender.getEncryptedMessages(SignalServiceMessageSender.java:2936)
        at org.whispersystems.signalservice.api.SignalServiceMessageSender.sendMessage(SignalServiceMessageSender.java:2231)
        at org.whispersystems.signalservice.api.SignalServiceMessageSender.sendContent(SignalServiceMessageSender.java:512)
        at org.whispersystems.signalservice.api.SignalServiceMessageSender.sendDataMessage(SignalServiceMessageSender.java:467)
...
        at org.asamk.signal.Main.main(Main.java:56)
        at java.base@21.0.2/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: java.lang.ClassNotFoundException: org.signal.libsignal.protocol.message.SignalMessage
        at java.base@21.0.2/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:52)
        at java.base@21.0.2/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
        at java.base@21.0.2/java.lang.ClassLoader.loadClass(ClassLoader.java:121)
        at org.signal.libsignal.internal.Native.SessionCipher_EncryptMessage(Native Method)
        at org.signal.libsignal.protocol.SessionCipher.lambda$encrypt$0(SessionCipher.java:103)
        at org.signal.libsignal.internal.FilterExceptions.filterExceptions(FilterExceptions.java:206)
        ... 24 more

Reported here: AsamK/signal-cli#1530

akonradi-signal commented 1 month ago

Thanks for the report! We've been experimenting with different solutions to work around exception loading issues on Android, and caching the class loader seems to work well there and didn't seem to cause problems with the JVM - obviously we didn't test with GraalVM!

We only really need the workaround on Android, though, so I think your option 1 is the way to go. It looks like we already fall back to find_env if the loader isn't cached so the fix should be as simple as making this line run only on Android: https://github.com/signalapp/libsignal/blob/1aedf124cd5082c29cb3f9bbae581b3d79f5abf2/rust/bridge/jni/src/lib.rs#L53.

That being said, a PR would be great! I don't have a great way to test on GraalVM, but if you send a patch that works for you (and passes existing tests) I'd be happy to review!

AsamK commented 1 month ago

Fixed in 0.47.0