-
Bug
-
Resolution: Not an Issue
-
P4
-
None
-
11.0.13
-
generic
-
generic
ADDITIONAL SYSTEM INFORMATION :
Debian bullseye openjdk release 11.0.13+8-1~deb11u1 on amd64.
A DESCRIPTION OF THE PROBLEM :
We have a project which contains a substantial quantity of native code (actually a Rust library exposed to Java via JNI bindings) which a user was hitting use-after-frees when calling it from Kotlin. Upon investigation, its clear that finalize() methods are being called while the object is still reachable on stack (while a native method is being called which references the object).
Relevant Java snippets showing the error:
Persist.java:bindings.LDKPersist instance:update_persisted_channel (roughly line 119) (top-level entry point into the bug)
ChannelMonitor data_hu_conv = null; if (data < 0 || data > 4096) { data_hu_conv = new ChannelMonitor(null, data); }
System.out.println("Got monitor in update_persisted_chan : " + data_hu_conv + " (ptr " + data + ")");
try {
MonitorUpdateId update_id_hu_conv = null; if (update_id < 0 || update_id > 4096) { update_id_hu_conv = new MonitorUpdateId(null, update_id); }
update_id_hu_conv.ptrs_to.add(this);
Result_NoneChannelMonitorUpdateErrZ ret = arg.update_persisted_channel(channel_id_hu_conv, update_hu_conv, data_hu_conv, update_id_hu_conv);
System.out.println("Done persisting! (monitor ptr " + data + ")");
long result = ret == null ? 0 : ret.clone_ptr();
return result;
} catch (Exception e) {
System.out.println("WTF");
}
ChannelMonitor.java's finzlie() override (the thing being free'd too early):
protected void finalize() throws Throwable {
super.finalize();
System.out.println("Freeing monitor " + this);
if (ptr != 0) { bindings.ChannelMonitor_free(ptr); }
}
Relevant output from runtime:
Cloned everything, now calling java update_persisted_channel with mon 107958298379521...
Got monitor in update_persisted_chan : org.ldk.structs.ChannelMonitor@6ebd9f7e (ptr 107958298379521)
start persist...
Freeing monitor org.ldk.structs.ChannelMonitor@6ebd9f7e
Freeing channel monitor! 107958298379521
note how the ChannelMonitor from Persist.java is clearly still reachable on the thread's stack (we don't ever hit the catch there - there is no exception, and the "Done persisting" print is never reached), but the instance @6ebd9f7e is finalize()d
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Place an object on the stack, call a Kotlin JVM function, call a native method (to hit ASAN user-after-free detection, the bug may exist with or without native code), watch finalize() get called while the object is still reachable on stack.
---------- BEGIN SOURCE ----------
Kotlin project at https://github.com/BlueWallet/HelloLightning/tree/master/src/main note that some effort is required to call the triggering function at https://github.com/BlueWallet/HelloLightning/blob/master/src/main/kotlin/RnLdkFunctions.kt#L176. VM access with installed/running/crashing code can be provided on request. It is easiest to trigger the crash by spawning a new thread which calls `System.gc(); System.runFinalization();` in a loop.
The underlying Java is available at https://github.com/lightningdevkit/ldk-garbagecollected/tree/main/src note that this is auto-generated code and the above code snippets (with minor modifications) are from https://github.com/lightningdevkit/ldk-garbagecollected/blob/main/src/main/java/org/ldk/structs/ChannelMonitor.java and https://github.com/lightningdevkit/ldk-garbagecollected/blob/main/src/main/java/org/ldk/structs/Persist.java
---------- END SOURCE ----------
Debian bullseye openjdk release 11.0.13+8-1~deb11u1 on amd64.
A DESCRIPTION OF THE PROBLEM :
We have a project which contains a substantial quantity of native code (actually a Rust library exposed to Java via JNI bindings) which a user was hitting use-after-frees when calling it from Kotlin. Upon investigation, its clear that finalize() methods are being called while the object is still reachable on stack (while a native method is being called which references the object).
Relevant Java snippets showing the error:
Persist.java:bindings.LDKPersist instance:update_persisted_channel (roughly line 119) (top-level entry point into the bug)
ChannelMonitor data_hu_conv = null; if (data < 0 || data > 4096) { data_hu_conv = new ChannelMonitor(null, data); }
System.out.println("Got monitor in update_persisted_chan : " + data_hu_conv + " (ptr " + data + ")");
try {
MonitorUpdateId update_id_hu_conv = null; if (update_id < 0 || update_id > 4096) { update_id_hu_conv = new MonitorUpdateId(null, update_id); }
update_id_hu_conv.ptrs_to.add(this);
Result_NoneChannelMonitorUpdateErrZ ret = arg.update_persisted_channel(channel_id_hu_conv, update_hu_conv, data_hu_conv, update_id_hu_conv);
System.out.println("Done persisting! (monitor ptr " + data + ")");
long result = ret == null ? 0 : ret.clone_ptr();
return result;
} catch (Exception e) {
System.out.println("WTF");
}
ChannelMonitor.java's finzlie() override (the thing being free'd too early):
protected void finalize() throws Throwable {
super.finalize();
System.out.println("Freeing monitor " + this);
if (ptr != 0) { bindings.ChannelMonitor_free(ptr); }
}
Relevant output from runtime:
Cloned everything, now calling java update_persisted_channel with mon 107958298379521...
Got monitor in update_persisted_chan : org.ldk.structs.ChannelMonitor@6ebd9f7e (ptr 107958298379521)
start persist...
Freeing monitor org.ldk.structs.ChannelMonitor@6ebd9f7e
Freeing channel monitor! 107958298379521
note how the ChannelMonitor from Persist.java is clearly still reachable on the thread's stack (we don't ever hit the catch there - there is no exception, and the "Done persisting" print is never reached), but the instance @6ebd9f7e is finalize()d
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Place an object on the stack, call a Kotlin JVM function, call a native method (to hit ASAN user-after-free detection, the bug may exist with or without native code), watch finalize() get called while the object is still reachable on stack.
---------- BEGIN SOURCE ----------
Kotlin project at https://github.com/BlueWallet/HelloLightning/tree/master/src/main note that some effort is required to call the triggering function at https://github.com/BlueWallet/HelloLightning/blob/master/src/main/kotlin/RnLdkFunctions.kt#L176. VM access with installed/running/crashing code can be provided on request. It is easiest to trigger the crash by spawning a new thread which calls `System.gc(); System.runFinalization();` in a loop.
The underlying Java is available at https://github.com/lightningdevkit/ldk-garbagecollected/tree/main/src note that this is auto-generated code and the above code snippets (with minor modifications) are from https://github.com/lightningdevkit/ldk-garbagecollected/blob/main/src/main/java/org/ldk/structs/ChannelMonitor.java and https://github.com/lightningdevkit/ldk-garbagecollected/blob/main/src/main/java/org/ldk/structs/Persist.java
---------- END SOURCE ----------