Validate incoming Group lengths and remote delete entries if wrong.

Ignore incoming messages with bad V1 group lengths.
This commit is contained in:
Alan Evans
2020-09-10 14:39:29 -03:00
committed by GitHub
parent bf4cac0c82
commit 3cffaddc0a
11 changed files with 218 additions and 77 deletions

View File

@@ -859,13 +859,14 @@ public class RecipientDatabase extends Database {
for (SignalGroupV2Record insert : groupV2Inserts) {
db.insertOrThrow(TABLE_NAME, null, getValuesForStorageGroupV2(insert));
GroupId.V2 groupId = GroupId.v2(insert.getMasterKey());
Recipient recipient = Recipient.externalGroup(context, groupId);
GroupMasterKey masterKey = insert.getMasterKeyOrThrow();
GroupId.V2 groupId = GroupId.v2(masterKey);
Recipient recipient = Recipient.externalGroup(context, groupId);
Log.i(TAG, "Creating restore placeholder for " + groupId);
DatabaseFactory.getGroupDatabase(context)
.create(insert.getMasterKey(),
.create(masterKey,
DecryptedGroup.newBuilder()
.setRevision(GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION)
.build());
@@ -886,7 +887,8 @@ public class RecipientDatabase extends Database {
throw new AssertionError("Had an update, but it didn't match any rows!");
}
Recipient recipient = Recipient.externalGroup(context, GroupId.v2(update.getOld().getMasterKey()));
GroupMasterKey masterKey = update.getOld().getMasterKeyOrThrow();
Recipient recipient = Recipient.externalGroup(context, GroupId.v2(masterKey));
threadDatabase.setArchived(recipient.getId(), update.getNew().isArchived());
needsRefresh.add(recipient.getId());
@@ -1031,7 +1033,7 @@ public class RecipientDatabase extends Database {
private static @NonNull ContentValues getValuesForStorageGroupV2(@NonNull SignalGroupV2Record groupV2) {
ContentValues values = new ContentValues();
values.put(GROUP_ID, GroupId.v2(groupV2.getMasterKey()).toString());
values.put(GROUP_ID, GroupId.v2(groupV2.getMasterKeyOrThrow()).toString());
values.put(GROUP_TYPE, GroupType.SIGNAL_V2.getId());
values.put(PROFILE_SHARING, groupV2.isProfileSharingEnabled() ? "1" : "0");
values.put(BLOCKED, groupV2.isBlocked() ? "1" : "0");

View File

@@ -19,6 +19,7 @@ public abstract class GroupId {
private static final String ENCODED_MMS_GROUP_PREFIX = "__signal_mms_group__!";
private static final int MMS_BYTE_LENGTH = 16;
private static final int V1_MMS_BYTE_LENGTH = 16;
private static final int V1_BYTE_LENGTH = 16;
private static final int V2_BYTE_LENGTH = GroupIdentifier.SIZE;
private final String encodedId;
@@ -46,6 +47,13 @@ public abstract class GroupId {
return new GroupId.V1(gv1GroupIdBytes);
}
public static @NonNull GroupId.V1 v1Exact(byte[] gv1GroupIdBytes) throws BadGroupIdException {
if (gv1GroupIdBytes.length != V1_BYTE_LENGTH) {
throw new BadGroupIdException();
}
return new GroupId.V1(gv1GroupIdBytes);
}
public static GroupId.V1 createV1(@NonNull SecureRandom secureRandom) {
return v1orThrow(Util.getSecretBytes(secureRandom, V1_MMS_BYTE_LENGTH));
}
@@ -54,15 +62,11 @@ public abstract class GroupId {
return mms(Util.getSecretBytes(secureRandom, MMS_BYTE_LENGTH));
}
public static GroupId.V2 v2orThrow(@NonNull byte[] bytes) {
try {
return v2(bytes);
} catch (BadGroupIdException e) {
throw new AssertionError(e);
}
}
public static GroupId.V2 v2(@NonNull byte[] bytes) throws BadGroupIdException {
/**
* Private because it's too easy to pass the {@link GroupMasterKey} bytes directly to this as they
* are the same length as the {@link GroupIdentifier}.
*/
private static GroupId.V2 v2(@NonNull byte[] bytes) throws BadGroupIdException {
if (bytes.length != V2_BYTE_LENGTH) {
throw new BadGroupIdException();
}
@@ -70,7 +74,11 @@ public abstract class GroupId {
}
public static GroupId.V2 v2(@NonNull GroupIdentifier groupIdentifier) {
return v2orThrow(groupIdentifier.serialize());
try {
return v2(groupIdentifier.serialize());
} catch (BadGroupIdException e) {
throw new AssertionError(e);
}
}
public static GroupId.V2 v2(@NonNull GroupMasterKey masterKey) {

View File

@@ -5,16 +5,16 @@ import androidx.annotation.NonNull;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.groups.BadGroupIdException;
import org.thoughtcrime.securesms.groups.GroupId;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
class GroupV1ConflictMerger implements StorageSyncHelper.ConflictMerger<SignalGroupV1Record> {
final class GroupV1ConflictMerger implements StorageSyncHelper.ConflictMerger<SignalGroupV1Record> {
private final Map<GroupId, SignalGroupV1Record> localByGroupId;
@@ -29,7 +29,9 @@ class GroupV1ConflictMerger implements StorageSyncHelper.ConflictMerger<SignalGr
@Override
public @NonNull Collection<SignalGroupV1Record> getInvalidEntries(@NonNull Collection<SignalGroupV1Record> remoteRecords) {
return Collections.emptySet();
return Stream.of(remoteRecords)
.filterNot(GroupV1ConflictMerger::isValidGroupId)
.toList();
}
@Override
@@ -54,4 +56,13 @@ class GroupV1ConflictMerger implements StorageSyncHelper.ConflictMerger<SignalGr
.build();
}
}
private static boolean isValidGroupId(@NonNull SignalGroupV1Record record) {
try {
GroupId.v1Exact(record.getGroupId());
return true;
} catch (BadGroupIdException e) {
return false;
}
}
}

View File

@@ -4,6 +4,7 @@ import androidx.annotation.NonNull;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import com.google.protobuf.ByteString;
import org.signal.zkgroup.groups.GroupMasterKey;
import org.whispersystems.libsignal.util.guava.Optional;
@@ -11,25 +12,26 @@ import org.whispersystems.signalservice.api.storage.SignalGroupV2Record;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
class GroupV2ConflictMerger implements StorageSyncHelper.ConflictMerger<SignalGroupV2Record> {
final class GroupV2ConflictMerger implements StorageSyncHelper.ConflictMerger<SignalGroupV2Record> {
private final Map<GroupMasterKey, SignalGroupV2Record> localByGroupId;
private final Map<ByteString, SignalGroupV2Record> localByMasterKeyBytes;
GroupV2ConflictMerger(@NonNull Collection<SignalGroupV2Record> localOnly) {
localByGroupId = Stream.of(localOnly).collect(Collectors.toMap(SignalGroupV2Record::getMasterKey, g -> g));
localByMasterKeyBytes = Stream.of(localOnly).collect(Collectors.toMap((SignalGroupV2Record signalGroupV2Record) -> ByteString.copyFrom(signalGroupV2Record.getMasterKeyBytes()), g -> g));
}
@Override
public @NonNull Optional<SignalGroupV2Record> getMatching(@NonNull SignalGroupV2Record record) {
return Optional.fromNullable(localByGroupId.get(record.getMasterKey()));
return Optional.fromNullable(localByMasterKeyBytes.get(ByteString.copyFrom(record.getMasterKeyBytes())));
}
@Override
public @NonNull Collection<SignalGroupV2Record> getInvalidEntries(@NonNull Collection<SignalGroupV2Record> remoteRecords) {
return Collections.emptySet();
return Stream.of(remoteRecords)
.filterNot(GroupV2ConflictMerger::isValidMasterKey)
.toList();
}
@Override
@@ -47,11 +49,15 @@ class GroupV2ConflictMerger implements StorageSyncHelper.ConflictMerger<SignalGr
} else if (matchesLocal) {
return local;
} else {
return new SignalGroupV2Record.Builder(keyGenerator.generate(), remote.getMasterKey())
return new SignalGroupV2Record.Builder(keyGenerator.generate(), remote.getMasterKeyBytes())
.setUnknownFields(unknownFields)
.setBlocked(blocked)
.setProfileSharingEnabled(blocked)
.build();
}
}
private static boolean isValidMasterKey(@NonNull SignalGroupV2Record record) {
return record.getMasterKeyBytes().length == GroupMasterKey.SIZE;
}
}

View File

@@ -282,6 +282,7 @@ public final class StorageSyncHelper {
Set<SignalRecord> remoteDeletes = new HashSet<>();
remoteDeletes.addAll(contactMergeResult.remoteDeletes);
remoteDeletes.addAll(groupV1MergeResult.remoteDeletes);
remoteDeletes.addAll(groupV2MergeResult.remoteDeletes);
remoteDeletes.addAll(accountMergeResult.remoteDeletes);
return new MergeResult(contactMergeResult.localInserts,
@@ -603,8 +604,8 @@ public final class StorageSyncHelper {
@Override
public @NonNull String toString() {
return String.format(Locale.ENGLISH,
"localContactInserts: %d, localContactUpdates: %d, localGroupV1Inserts: %d, localGroupV1Updates: %d, localGroupV2Inserts: %d, localGroupV2Updates: %d, localUnknownInserts: %d, localUnknownDeletes: %d, localAccountUpdate: %b, remoteInserts: %d, remoteUpdates: %d",
localContactInserts.size(), localContactUpdates.size(), localGroupV1Inserts.size(), localGroupV1Updates.size(), localGroupV2Inserts.size(), localGroupV2Updates.size(), localUnknownInserts.size(), localUnknownDeletes.size(), localAccountUpdate.isPresent(), remoteInserts.size(), remoteUpdates.size());
"localContactInserts: %d, localContactUpdates: %d, localGroupV1Inserts: %d, localGroupV1Updates: %d, localGroupV2Inserts: %d, localGroupV2Updates: %d, localUnknownInserts: %d, localUnknownDeletes: %d, localAccountUpdate: %b, remoteInserts: %d, remoteUpdates: %d, remoteDeletes: %d",
localContactInserts.size(), localContactUpdates.size(), localGroupV1Inserts.size(), localGroupV1Updates.size(), localGroupV2Inserts.size(), localGroupV2Updates.size(), localUnknownInserts.size(), localUnknownDeletes.size(), localAccountUpdate.isPresent(), remoteInserts.size(), remoteUpdates.size(), remoteDeletes.size());
}
}

View File

@@ -38,7 +38,7 @@ public final class GroupUtil {
throws BadGroupIdException
{
if (groupContext.getGroupV1().isPresent()) {
return GroupId.v1(groupContext.getGroupV1().get().getGroupId());
return GroupId.v1Exact(groupContext.getGroupV1().get().getGroupId());
} else if (groupContext.getGroupV2().isPresent()) {
return GroupId.v2(groupContext.getGroupV2().get().getMasterKey());
} else {