Fix body range bounds validation for long text messages.

This commit is contained in:
Greyson Parrelli
2026-06-16 11:03:51 -04:00
parent 52dcbb8bc6
commit e877f43dde
3 changed files with 50 additions and 5 deletions
@@ -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);
@@ -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<BodyRange>.hasInvalidBounds(body: String?): Boolean {
private fun List<BodyRange>.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)
}
@@ -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(