take the bwt shutdown handle once across overlapping shutdowns, and do nothing in a shutdown that finds it taken
What changed, and why it matters
This commit fixes a race condition in how Sparrow Wallet shuts down its bundled Bitcoin Wallet Tracker (bwt) daemon. Previously, if two shutdown attempts overlapped, the same internal 'handle' could be used twice or cleared while still in use, which could crash the wallet or leave it in an inconsistent state. The fix makes the shutdown handle a one-time-use token and synchronizes access to it so only one shutdown can actually act.
Treat as a stability/reliability fix. Include in routine release notes. No urgent security advisory appears warranted unless the crash can be demonstrated to cause data loss or code execution.
Security signals we found
Race condition in shutdown path
Potential double-use of native daemon handle
Synchronization added around shared mutable pointer
No input validation or cryptographic changes
Evidence from the diff
The patch modifies Bwt.java to prevent double-free/use-after-free-like behavior during bwt daemon shutdown. It introduces synchronized access to shutdownPtr, takes the pointer locally before nulling it, and skips shutdown if the pointer was already taken by another overlapping shutdown. It also synchronizes the assignment of shutdownPtr in onBooting. This addresses a concurrency bug but does not appear to be a remotely exploitable vulnerability.
Changed components
src/main/java/com/sparrowwallet/sparrow/net/Bwt.javaNative bwt daemon shutdown pathInspect captured patch +13 / −5
### src/main/java/com/sparrowwallet/sparrow/net/Bwt.java
@@ -227,15 +227,21 @@ private HostAndPort getTorProxy() {
*
*/
private void shutdown() {
- if(shutdownPtr == null) {
- terminating = true;
+ //The handle is taken before it is used, so that it is passed to the daemon once however many shutdowns are under way
+ Long ptr;
+ synchronized(this) {
+ ptr = shutdownPtr;
+ shutdownPtr = null;
+ }
+
+ //A shutdown is only requested of a running daemon, so one finding no handle has been overtaken by another and has nothing to do
+ if(ptr == null) {
return;
}
- NativeBwtDaemon.shutdown(shutdownPtr);
+ NativeBwtDaemon.shutdown(ptr);
this.terminating = false;
this.ready = false;
- this.shutdownPtr = null;
}
public boolean isRunning() {
@@ -342,7 +348,9 @@ protected Void call() {
public void onBooting(long shutdownPtr) {
log.debug("Booting bwt");
- Bwt.this.shutdownPtr = shutdownPtr;
+ synchronized(Bwt.this) {
+ Bwt.this.shutdownPtr = shutdownPtr;
+ }
if(terminating) {
Bwt.this.shutdown();
terminating = false;Why this scored 29/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.