From e877f43dde238eef1d992200d6c0410fc9008380 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Tue, 16 Jun 2026 11:03:51 -0400 Subject: [PATCH] Fix body range bounds validation for long text messages. --- .../logsubmit/LogSectionNotifications.java | 3 +- .../api/messages/EnvelopeContentValidator.kt | 13 +++++-- .../messages/EnvelopeContentValidatorTest.kt | 39 +++++++++++++++++++ 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionNotifications.java b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionNotifications.java index 7d5ac275db..a56d7627e9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionNotifications.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionNotifications.java @@ -28,7 +28,8 @@ final class LogSectionNotifications implements LogSection { .append("New contact alerts : ").append(SignalStore.settings().isNotifyWhenContactJoinsSignal()).append("\n") .append("In-chat sounds : ").append(SignalStore.settings().isMessageNotificationsInChatSoundsEnabled()).append("\n") .append("Repeat alerts : ").append(SignalStore.settings().getMessageNotificationsRepeatAlerts()).append("\n") - .append("Notification display : ").append(SignalStore.settings().getMessageNotificationsPrivacy()).append("\n\n"); + .append("Notification display : ").append(SignalStore.settings().getMessageNotificationsPrivacy()).append("\n") + .append("Force websocket : ").append(SignalStore.settings().getForceWebsocketMode()).append("\n\n"); if (Build.VERSION.SDK_INT >= 26) { NotificationManager manager = ServiceUtil.getNotificationManager(context); diff --git a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt index 0579e215b6..d5144afbee 100644 --- a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt +++ b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt @@ -35,6 +35,7 @@ object EnvelopeContentValidator { private const val MAX_POLL_CHARACTER_LENGTH = 100 private const val MIN_POLL_OPTIONS = 2 private const val MAX_POLL_OPTIONS = 10 + private const val LONG_TEXT_CONTENT_TYPE = "text/x-signal-plain" fun validate(envelope: Envelope, content: Content, localAci: ACI, ciphertextMessageType: Int): Result { if (envelope.type == Envelope.Type.PLAINTEXT_CONTENT || ciphertextMessageType == CiphertextMessage.PLAINTEXT_CONTENT_TYPE) { @@ -121,7 +122,7 @@ object EnvelopeContentValidator { return Result.Invalid("[DataMessage] Style body range is missing a start or length!") } - if (dataMessage.bodyRanges.hasInvalidBounds(dataMessage.body)) { + if (dataMessage.bodyRanges.hasInvalidBounds(dataMessage.body, allowOutOfBounds = dataMessage.hasLongTextAttachment())) { return Result.Invalid("[DataMessage] Body range with out-of-bounds start/length!") } @@ -375,7 +376,7 @@ object EnvelopeContentValidator { return Result.Invalid("[EditMessage] Style body range is missing a start or length!") } - if (dataMessage.bodyRanges.hasInvalidBounds(dataMessage.body)) { + if (dataMessage.bodyRanges.hasInvalidBounds(dataMessage.body, allowOutOfBounds = dataMessage.hasLongTextAttachment())) { return Result.Invalid("[EditMessage] Body range with out-of-bounds start/length!") } @@ -390,17 +391,21 @@ object EnvelopeContentValidator { return Result.Valid } - private fun List.hasInvalidBounds(body: String?): Boolean { + private fun List.hasInvalidBounds(body: String?, allowOutOfBounds: Boolean = false): Boolean { val bodyLength: Long = (body?.length ?: 0).toLong() return this.any { range -> val start: Long = (range.start ?: 0).toLong() val length: Long = (range.length ?: 0).toLong() - start < 0 || length < 0 || start + length > bodyLength + start < 0 || length < 0 || (!allowOutOfBounds && start + length > bodyLength) } } + private fun DataMessage.hasLongTextAttachment(): Boolean { + return this.attachments.any { it.contentType == LONG_TEXT_CONTENT_TYPE } + } + private fun BodyRange.isStyleRangeMissingOffsets(): Boolean { return this.style != null && (this.start == null || this.length == null) } diff --git a/lib/libsignal-service/src/test/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidatorTest.kt b/lib/libsignal-service/src/test/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidatorTest.kt index 748391dfe9..f6e958d85f 100644 --- a/lib/libsignal-service/src/test/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidatorTest.kt +++ b/lib/libsignal-service/src/test/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidatorTest.kt @@ -10,6 +10,7 @@ import org.junit.Test import org.signal.core.models.ServiceId import org.signal.libsignal.protocol.message.CiphertextMessage import org.signal.libsignal.protocol.message.DecryptionErrorMessage +import org.whispersystems.signalservice.internal.push.AttachmentPointer import org.whispersystems.signalservice.internal.push.BodyRange import org.whispersystems.signalservice.internal.push.Content import org.whispersystems.signalservice.internal.push.DataMessage @@ -412,6 +413,44 @@ class EnvelopeContentValidatorTest { assert(result is EnvelopeContentValidator.Result.Invalid) } + @Test + fun `validate - ensure body range extending past the end of the body is marked valid when a long text attachment is present`() { + val content = Content( + dataMessage = DataMessage( + timestamp = 1234, + body = "hello", + bodyRanges = listOf( + BodyRange(start = 3, length = 10, style = BodyRange.Style.BOLD) + ), + attachments = listOf( + AttachmentPointer(cdnKey = "abc", contentType = "text/x-signal-plain") + ) + ) + ) + + val result = EnvelopeContentValidator.validate(Envelope(clientTimestamp = 1234), content, SELF_ACI, CiphertextMessage.WHISPER_TYPE) + assert(result is EnvelopeContentValidator.Result.Valid) + } + + @Test + fun `validate - ensure body range with negative start is marked invalid even when a long text attachment is present`() { + val content = Content( + dataMessage = DataMessage( + timestamp = 1234, + body = "hello", + bodyRanges = listOf( + BodyRange(start = -1, length = 10, style = BodyRange.Style.BOLD) + ), + attachments = listOf( + AttachmentPointer(cdnKey = "abc", contentType = "text/x-signal-plain") + ) + ) + ) + + val result = EnvelopeContentValidator.validate(Envelope(clientTimestamp = 1234), content, SELF_ACI, CiphertextMessage.WHISPER_TYPE) + assert(result is EnvelopeContentValidator.Result.Invalid) + } + @Test fun `validate - ensure body range that exactly covers the body is marked valid`() { val content = Content(