Add protections around exporting unknown recipients in dlists.

This commit is contained in:
Greyson Parrelli
2025-10-29 16:31:30 -04:00
committed by Michelle Tang
parent 107ee5268e
commit 765c1eeab0
4 changed files with 39 additions and 9 deletions

View File

@@ -7,19 +7,20 @@ package org.thoughtcrime.securesms.backup.v2.database
import org.signal.core.util.select
import org.signal.core.util.withinTransaction
import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.exporters.DistributionListArchiveExporter
import org.thoughtcrime.securesms.database.DistributionListTables
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
import org.thoughtcrime.securesms.recipients.RecipientId
fun DistributionListTables.getAllForBackup(selfRecipientId: RecipientId): DistributionListArchiveExporter {
fun DistributionListTables.getAllForBackup(selfRecipientId: RecipientId, exportState: ExportState): DistributionListArchiveExporter {
val cursor = readableDatabase
.select()
.from(DistributionListTables.ListTable.TABLE_NAME)
.run()
return DistributionListArchiveExporter(cursor, this, selfRecipientId)
return DistributionListArchiveExporter(cursor, this, selfRecipientId, exportState)
}
fun DistributionListTables.getMembersForBackup(id: DistributionListId): List<RecipientId> {

View File

@@ -317,6 +317,11 @@ class ChatItemArchiveExporter(
}
MessageTypes.isGroupV2(record.type) && MessageTypes.isGroupUpdate(record.type) -> {
if (builder.authorId != selfRecipientId.toLong() && exportState.recipientIdToAci[builder.authorId] == null) {
Log.w(TAG, ExportSkips.groupUpdateHasInvalidAuthor(record.dateSent))
continue
}
val update = record.toRemoteGroupUpdate() ?: continue
if (update.groupChange!!.updates.isEmpty()) {
Log.w(TAG, ExportSkips.groupUpdateHasNoUpdates(record.dateSent))
@@ -430,7 +435,7 @@ class ChatItemArchiveExporter(
if (record.latestRevisionId == null) {
builder.revisions = revisionMap.remove(record.id)?.repairRevisions(builder) ?: emptyList()
val chatItem = builder.build().validateChatItem(exportState) ?: continue
val chatItem = builder.build().validateChatItem(exportState, selfRecipientId) ?: continue
buffer += chatItem
} else {
var previousEdits = revisionMap[record.latestRevisionId]
@@ -1340,6 +1345,10 @@ private fun ByteArray.toRemoteBodyRanges(dateSent: Long): List<BackupBodyRange>
null
}
if (mention == null && style == null) {
return emptyList()
}
BackupBodyRange(
start = it.start,
length = it.length,
@@ -1589,7 +1598,7 @@ private fun <T> ExecutorService.submitTyped(callable: Callable<T>): Future<T> {
return this.submit(callable)
}
private fun ChatItem.validateChatItem(exportState: ExportState): ChatItem? {
private fun ChatItem.validateChatItem(exportState: ExportState, selfRecipientId: RecipientId): ChatItem? {
if (this.standardMessage == null &&
this.contactMessage == null &&
this.stickerMessage == null &&
@@ -1610,6 +1619,16 @@ private fun ChatItem.validateChatItem(exportState: ExportState): ChatItem? {
return null
}
if (this.updateMessage != null && this.updateMessage.canOnlyBeAuthoredBySelf() && this.authorId != selfRecipientId.toLong()) {
Log.w(TAG, ExportSkips.individualChatUpdateNotAuthoredBySelf(this.dateSent))
return null
}
if (this.incoming != null && exportState.recipientIdToAci[this.authorId] == null && exportState.recipientIdToE164[this.authorId] == null) {
Log.w(TAG, ExportSkips.incomingMessageAuthorDoesNotHaveAciOrE164(this.dateSent))
return null
}
return this
}
@@ -1621,6 +1640,13 @@ private fun ChatUpdateMessage.isOnlyForIndividualChats(): Boolean {
this.simpleUpdate?.type == SimpleChatUpdate.Type.PAYMENTS_ACTIVATED
}
private fun ChatUpdateMessage.canOnlyBeAuthoredBySelf(): Boolean {
return this.simpleUpdate?.type == SimpleChatUpdate.Type.REPORTED_SPAM ||
this.simpleUpdate?.type == SimpleChatUpdate.Type.MESSAGE_REQUEST_ACCEPTED ||
this.simpleUpdate?.type == SimpleChatUpdate.Type.BLOCKED ||
this.simpleUpdate?.type == SimpleChatUpdate.Type.UNBLOCKED
}
private fun List<ChatItem>.repairRevisions(current: ChatItem.Builder): List<ChatItem> {
return if (current.standardMessage != null) {
val filtered = this

View File

@@ -14,6 +14,7 @@ import org.signal.core.util.requireNonNullString
import org.signal.core.util.requireObject
import org.thoughtcrime.securesms.backup.v2.ArchiveRecipient
import org.thoughtcrime.securesms.backup.v2.ExportOddities
import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.database.getMembersForBackup
import org.thoughtcrime.securesms.backup.v2.proto.DistributionList
import org.thoughtcrime.securesms.backup.v2.proto.DistributionListItem
@@ -32,7 +33,8 @@ private val TAG = Log.tag(DistributionListArchiveExporter::class)
class DistributionListArchiveExporter(
private val cursor: Cursor,
private val distributionListTables: DistributionListTables,
private val selfRecipientId: RecipientId
private val selfRecipientId: RecipientId,
private val exportState: ExportState
) : Iterator<ArchiveRecipient>, Closeable {
override fun hasNext(): Boolean {
@@ -66,7 +68,7 @@ class DistributionListArchiveExporter(
deletionTimestamp = record.deletedAtTimestamp
)
} else {
val members = record.members.toRemoteMemberList(selfRecipientId)
val members = record.members.toRemoteMemberList(selfRecipientId, exportState)
DistributionListItem(
distributionId = record.distributionId.asUuid().toByteArray().toByteString(),
distributionList = DistributionList(
@@ -104,10 +106,11 @@ private fun DistributionListPrivacyMode.toBackupPrivacyMode(memberCount: Int): D
}
}
private fun List<RecipientId>.toRemoteMemberList(selfRecipientId: RecipientId): List<Long> {
private fun List<RecipientId>.toRemoteMemberList(selfRecipientId: RecipientId, exportState: ExportState): List<Long> {
val filtered = this.filter { it != selfRecipientId }.map { it.toLong() }
if (filtered.size != this.size) {
Log.w(TAG, ExportOddities.distributionListHadSelfAsMember())
}
return filtered
return filtered.filter { exportState.recipientIdToAci[it] != null || exportState.recipientIdToE164[it] != null }
}

View File

@@ -82,7 +82,7 @@ object RecipientArchiveProcessor {
}
}
db.distributionListTables.getAllForBackup(selfRecipientId).use { reader ->
db.distributionListTables.getAllForBackup(selfRecipientId, exportState).use { reader ->
for (recipient in reader) {
exportState.recipientIds.add(recipient.id)
emitter.emit(Frame(recipient = recipient))