From 812a858761b2410faafbb3deb35689faf5119b71 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Tue, 9 Jun 2026 10:18:03 -0400 Subject: [PATCH] Reject attachments with unrecognized CDN numbers instead of crashing. --- .../thoughtcrime/securesms/attachments/Cdn.kt | 6 +++++- .../attachments/PointerAttachment.kt | 20 +++++++++++++++++-- .../v2/util/ArchiveConverterExtensions.kt | 11 +++++++++- .../contactshare/ContactModelMapper.java | 8 +++++--- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/Cdn.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/Cdn.kt index 7b4589f125..5477a5fe59 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/Cdn.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/Cdn.kt @@ -41,12 +41,16 @@ enum class Cdn(private val value: Int) { } fun fromCdnNumber(cdnNumber: Int): Cdn { + return fromCdnNumberOrNull(cdnNumber) ?: throw UnsupportedOperationException("Invalid CDN number: $cdnNumber") + } + + fun fromCdnNumberOrNull(cdnNumber: Int): Cdn? { return when (cdnNumber) { -1 -> S3 0 -> CDN_0 2 -> CDN_2 3 -> CDN_3 - else -> throw UnsupportedOperationException("Invalid CDN number: $cdnNumber") + else -> null } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.kt index 6fcc40d84d..7af60405b7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.kt @@ -5,6 +5,7 @@ import android.os.Parcel import androidx.annotation.VisibleForTesting import org.signal.blurhash.BlurHash import org.signal.core.util.Base64 +import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.database.AttachmentTable import org.thoughtcrime.securesms.stickers.StickerLocator import org.whispersystems.signalservice.api.InvalidMessageStructureException @@ -76,6 +77,8 @@ class PointerAttachment : Attachment { override val thumbnailUri: Uri? = null companion object { + private val TAG = Log.tag(PointerAttachment::class) + @JvmStatic fun forPointers(pointers: Optional>): List { if (!pointers.isPresent) { @@ -102,6 +105,13 @@ class PointerAttachment : Attachment { return Optional.empty() } + val cdnNumber = pointer.get().asPointer().cdnNumber + val cdn = Cdn.fromCdnNumberOrNull(cdnNumber) + if (cdn == null) { + Log.w(TAG, "Encountered an attachment pointer with an unsupported CDN number ($cdnNumber). Skipping attachment.") + return Optional.empty() + } + val encodedKey: String? = pointer.get().asPointer().key?.let { Base64.encodeWithPadding(it) } return Optional.of( @@ -110,7 +120,7 @@ class PointerAttachment : Attachment { transferState = transferState, size = pointer.get().asPointer().size.orElse(0).toLong(), fileName = pointer.get().asPointer().fileName.orElse(null), - cdn = Cdn.fromCdnNumber(pointer.get().asPointer().cdnNumber), + cdn = cdn, location = pointer.get().asPointer().remoteId.toString(), key = encodedKey, iv = null, @@ -145,7 +155,13 @@ class PointerAttachment : Attachment { return Optional.empty() } - val cdn = Cdn.fromCdnNumber(thumbnail?.asPointer()?.cdnNumber ?: 0) + val cdnNumber = thumbnail?.asPointer()?.cdnNumber ?: 0 + val cdn = Cdn.fromCdnNumberOrNull(cdnNumber) + if (cdn == null) { + Log.w(TAG, "Encountered a quote thumbnail with an unsupported CDN number ($cdnNumber). Skipping attachment.") + return Optional.empty() + } + if (cdn == Cdn.S3) { return Optional.empty() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/util/ArchiveConverterExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/util/ArchiveConverterExtensions.kt index af41e104f0..d943cb779c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/util/ArchiveConverterExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/util/ArchiveConverterExtensions.kt @@ -11,6 +11,7 @@ import org.signal.archive.proto.FilePointer import org.signal.core.util.Base64 import org.signal.core.util.UuidUtil import org.signal.core.util.isNotNullOrBlank +import org.signal.core.util.logging.Log import org.signal.core.util.nullIfBlank import org.signal.core.util.orNull import org.signal.libsignal.usernames.BaseUsernameException @@ -32,6 +33,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemo import java.util.Optional import org.signal.archive.proto.AvatarColor as RemoteAvatarColor +private const val TAG = "ArchiveConverter" + /** * Converts a [FilePointer] to a local [Attachment] object for inserting into the database. */ @@ -58,10 +61,16 @@ fun FilePointer?.toLocalAttachment( return when (attachmentType) { AttachmentType.ARCHIVE -> { + val cdnNumber = locatorInfo.transitCdnNumber ?: Cdn.CDN_0.cdnNumber + if (Cdn.fromCdnNumberOrNull(cdnNumber) == null) { + Log.w(TAG, "Encountered an archived attachment with an unsupported CDN number ($cdnNumber). Skipping attachment.") + return null + } + ArchivedAttachment( contentType = contentType, size = locatorInfo.size.toLong(), - cdn = locatorInfo.transitCdnNumber ?: Cdn.CDN_0.cdnNumber, + cdn = cdnNumber, uploadTimestamp = locatorInfo.transitTierUploadTimestamp ?: 0, key = locatorInfo.key.toByteArray(), cdnKey = locatorInfo.transitCdnKey?.nullIfBlank(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactModelMapper.java b/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactModelMapper.java index 0293a3a759..821fe1ed9b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactModelMapper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contactshare/ContactModelMapper.java @@ -119,13 +119,15 @@ public class ContactModelMapper { if (contact.avatar != null && contact.avatar.avatar != null) { try { SignalServiceAttachmentPointer attachmentPointer = AttachmentPointerUtil.createSignalAttachmentPointer(contact.avatar.avatar); - Attachment attachment = PointerAttachment.forPointer(Optional.of(attachmentPointer.asPointer())).get(); + Optional attachment = PointerAttachment.forPointer(Optional.of(attachmentPointer.asPointer())); - if (attachment.cdn == Cdn.S3) { + if (!attachment.isPresent()) { + Log.w(TAG, "Unable to create avatar attachment for contact. Ignoring avatar."); + } else if (attachment.get().cdn == Cdn.S3) { Log.w(TAG, "Ignoring contact avatar that resolves to the internal release-channel CDN."); } else { boolean isProfile = Boolean.TRUE.equals(contact.avatar.isProfile); - avatar = new Avatar(null, attachment, isProfile); + avatar = new Avatar(null, attachment.get(), isProfile); } } catch (InvalidMessageStructureException e) { Log.w(TAG, "Unable to create avatar for contact", e);