cover group removal path

This commit is contained in:
areebahmeddd 2026-08-02 00:22:57 +05:30
parent c836b10d68
commit cf2a1e77ec
No known key found for this signature in database
GPG Key ID: 3246A173263AD93A
2 changed files with 21 additions and 6 deletions

View File

@ -77,14 +77,17 @@ final class GroupStore: ObservableObject {
!group.members.isEmpty,
group.members.count <= BitchatGroup.maxMembers,
group.creator != nil else { return false }
// A group keeps the creator it was created with. `applyGroupState`
// checks the sender is the creator the state NAMES, but never against
// the creator already stored, so a peer naming themselves creator of a
// known groupID at a higher epoch replaces the roster and key wholesale.
// Pinned here, the one point every write goes through.
// A group keeps the creator it was created with. A peer naming
// themselves creator of a known groupID at a higher epoch would
// otherwise replace the roster and key wholesale. `applyGroupState`
// pins this before its removal branch; here is the one point every
// write goes through.
if let existing = groups.first(where: { $0.groupID == group.groupID }),
existing.creatorFingerprint != group.creatorFingerprint {
SecureLogger.warning("Refusing group state that changes the creator of an existing group", category: .security)
SecureLogger.warning(
"Refusing group state: creator \(group.creatorFingerprint.prefix(8))… does not match stored creator \(existing.creatorFingerprint.prefix(8))",
category: .security
)
return false
}
guard keychain.saveIdentityKey(key, forKey: Self.keychainKey(for: group.groupID)) else {

View File

@ -558,6 +558,18 @@ private extension ChatGroupCoordinator {
let myFingerprint = context.myNoiseFingerprint()
let existing = context.groupStore.group(withID: state.groupID)
// A group keeps the creator it was created with. The checks above only
// prove the sender is the creator the state names, which an attacker
// satisfies by naming themselves. Checked before the removal branch,
// which drops the group without ever reaching `upsert`.
if let existing, existing.creatorFingerprint != state.creatorFingerprint {
SecureLogger.warning(
"Dropping group state claiming creator \(state.creatorFingerprint.prefix(8))… for a group created by \(existing.creatorFingerprint.prefix(8))",
category: .security
)
return
}
// A creator-signed roster that no longer includes us is a removal.
guard state.members.contains(where: { $0.fingerprint == myFingerprint }) else {
if let existing {