mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 04:05:20 +00:00
Fix critical thread-safety crash in PeerID fragment handler
CRITICAL: The PeerID version of _handleFragment was accessing incomingFragments dictionary without collectionsQueue synchronization, causing crashes when multiple BLE threads processed fragments concurrently. Crash stack trace pointed to line 3431 (dictionary subscript) with: 'doesNotRecognizeSelector' - classic concurrent mutation crash. Fix: - Wrapped ALL incomingFragments/fragmentMetadata access in collectionsQueue.sync(flags: .barrier) - Matches the thread-safe pattern used in String version - Separate cleanup into its own barrier block after reassembly - Prevents concurrent dictionary mutations from multiple BLE threads This is the same pattern as the String version (line 1128) which didn't crash.
This commit is contained in:
@@ -3417,8 +3417,14 @@ extension BLEService {
|
|||||||
// Sanity checks - add reasonable upper bound on total to prevent DoS
|
// Sanity checks - add reasonable upper bound on total to prevent DoS
|
||||||
guard total > 0 && total <= 10000 && index >= 0 && index < total else { return }
|
guard total > 0 && total <= 10000 && index >= 0 && index < total else { return }
|
||||||
|
|
||||||
// Store fragment
|
// Compute fragment key for this assembly
|
||||||
let key = FragmentKey(sender: senderU64, id: fragU64)
|
let key = FragmentKey(sender: senderU64, id: fragU64)
|
||||||
|
|
||||||
|
// Critical section: Store fragment and check completion status
|
||||||
|
var shouldReassemble: Bool = false
|
||||||
|
var fragmentsToReassemble: [Int: Data]? = nil
|
||||||
|
|
||||||
|
collectionsQueue.sync(flags: .barrier) {
|
||||||
if incomingFragments[key] == nil {
|
if incomingFragments[key] == nil {
|
||||||
// Cap in-flight assemblies to prevent memory/battery blowups
|
// Cap in-flight assemblies to prevent memory/battery blowups
|
||||||
if incomingFragments.count >= maxInFlightAssemblies {
|
if incomingFragments.count >= maxInFlightAssemblies {
|
||||||
@@ -3450,15 +3456,26 @@ extension BLEService {
|
|||||||
)
|
)
|
||||||
incomingFragments.removeValue(forKey: key)
|
incomingFragments.removeValue(forKey: key)
|
||||||
fragmentMetadata.removeValue(forKey: key)
|
fragmentMetadata.removeValue(forKey: key)
|
||||||
|
shouldReassemble = false
|
||||||
|
fragmentsToReassemble = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
incomingFragments[key]?[index] = Data(fragmentData)
|
incomingFragments[key]?[index] = Data(fragmentData)
|
||||||
|
|
||||||
// Check if complete
|
// Check if complete
|
||||||
if let fragments = incomingFragments[key],
|
if let fragments = incomingFragments[key], fragments.count == total {
|
||||||
fragments.count == total {
|
shouldReassemble = true
|
||||||
// Reassemble
|
fragmentsToReassemble = fragments
|
||||||
|
} else {
|
||||||
|
shouldReassemble = false
|
||||||
|
fragmentsToReassemble = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heavy work outside lock: reassemble and decode
|
||||||
|
guard shouldReassemble, let fragments = fragmentsToReassemble else { return }
|
||||||
|
|
||||||
var reassembled = Data()
|
var reassembled = Data()
|
||||||
for i in 0..<total {
|
for i in 0..<total {
|
||||||
if let fragment = fragments[i] {
|
if let fragment = fragments[i] {
|
||||||
@@ -3473,7 +3490,8 @@ extension BLEService {
|
|||||||
SecureLogger.error("❌ Failed to decode reassembled packet (type=\(originalType), total=\(total))", category: .session)
|
SecureLogger.error("❌ Failed to decode reassembled packet (type=\(originalType), total=\(total))", category: .session)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup
|
// Critical section: Cleanup completed assembly
|
||||||
|
collectionsQueue.sync(flags: .barrier) {
|
||||||
incomingFragments.removeValue(forKey: key)
|
incomingFragments.removeValue(forKey: key)
|
||||||
fragmentMetadata.removeValue(forKey: key)
|
fragmentMetadata.removeValue(forKey: key)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user