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 2c08e6b8aa..dda3a4d6dc 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 @@ -10,6 +10,7 @@ import org.signal.libsignal.zkgroup.InvalidInputException import org.signal.libsignal.zkgroup.groups.GroupMasterKey import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation 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 import org.whispersystems.signalservice.internal.push.EditMessage @@ -96,6 +97,10 @@ object EnvelopeContentValidator { return Result.Invalid("[DataMessage] Invalid ACI on quote body range!") } + if (dataMessage.quote != null && dataMessage.quote.bodyRanges.hasInvalidBounds(dataMessage.quote.text)) { + return Result.Invalid("[DataMessage] Quote body range with out-of-bounds start/length!") + } + if (dataMessage.contact.any { it.avatar != null && it.avatar.avatar.isPresentAndInvalid() }) { return Result.Invalid("[DataMessage] Invalid AttachmentPointer on DataMessage.contactList.avatar!") } @@ -108,6 +113,10 @@ object EnvelopeContentValidator { return Result.Invalid("[DataMessage] Invalid ACI on body range!") } + if (dataMessage.bodyRanges.hasInvalidBounds(dataMessage.body)) { + return Result.Invalid("[DataMessage] Body range with out-of-bounds start/length!") + } + if (dataMessage.sticker != null && dataMessage.sticker.data_.isNullOrInvalid()) { return Result.Invalid("[DataMessage] Invalid AttachmentPointer on DataMessage.sticker!") } @@ -354,6 +363,10 @@ object EnvelopeContentValidator { return Result.Invalid("[EditMessage] Invalid UUID on body range!") } + if (dataMessage.bodyRanges.hasInvalidBounds(dataMessage.body)) { + return Result.Invalid("[EditMessage] Body range with out-of-bounds start/length!") + } + if (dataMessage.attachments.any { it.isNullOrInvalid() }) { return Result.Invalid("[EditMessage] Invalid attachments!") } @@ -365,6 +378,17 @@ object EnvelopeContentValidator { return Result.Valid } + private fun List.hasInvalidBounds(body: String?): 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 + } + } + private fun AttachmentPointer?.isNullOrInvalid(): Boolean { return this == null || (this.cdnId == null && this.cdnKey == 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 cd97c19ba8..8560f37545 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 @@ -328,6 +328,7 @@ class EnvelopeContentValidatorTest { quote = DataMessage.Quote( id = 1000, authorAci = OTHER_ACI.toString(), + text = "hello", bodyRanges = listOf( BodyRange(start = 0, length = 1, mentionAci = OTHER_ACI.toString()) ) @@ -339,6 +340,94 @@ class EnvelopeContentValidatorTest { assert(result is EnvelopeContentValidator.Result.Valid) } + @Test + fun `validate - ensure quote body range whose start plus length overflows is marked invalid`() { + val content = Content( + dataMessage = DataMessage( + timestamp = 1234, + quote = DataMessage.Quote( + id = 1000, + authorAci = OTHER_ACI.toString(), + text = "hello", + bodyRanges = listOf( + BodyRange(start = 1, length = Int.MAX_VALUE, mentionAci = OTHER_ACI.toString()) + ) + ) + ) + ) + + val result = EnvelopeContentValidator.validate(Envelope(clientTimestamp = 1234), content, SELF_ACI, CiphertextMessage.WHISPER_TYPE) + assert(result is EnvelopeContentValidator.Result.Invalid) + } + + @Test + fun `validate - ensure quote body range extending past the end of the text is marked invalid`() { + val content = Content( + dataMessage = DataMessage( + timestamp = 1234, + quote = DataMessage.Quote( + id = 1000, + authorAci = OTHER_ACI.toString(), + text = "hello", + bodyRanges = listOf( + BodyRange(start = 3, length = 10, style = BodyRange.Style.BOLD) + ) + ) + ) + ) + + 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 whose start plus length overflows is marked invalid`() { + val content = Content( + dataMessage = DataMessage( + timestamp = 1234, + body = "hello", + bodyRanges = listOf( + BodyRange(start = 1, length = Int.MAX_VALUE, mentionAci = OTHER_ACI.toString()) + ) + ) + ) + + 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 extending past the end of the body is marked invalid`() { + val content = Content( + dataMessage = DataMessage( + timestamp = 1234, + body = "hello", + bodyRanges = listOf( + BodyRange(start = 3, length = 10, style = BodyRange.Style.BOLD) + ) + ) + ) + + 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( + dataMessage = DataMessage( + timestamp = 1234, + body = "hello", + bodyRanges = listOf( + BodyRange(start = 0, length = 5, style = BodyRange.Style.BOLD) + ) + ) + ) + + val result = EnvelopeContentValidator.validate(Envelope(clientTimestamp = 1234), content, SELF_ACI, CiphertextMessage.WHISPER_TYPE) + assert(result is EnvelopeContentValidator.Result.Valid) + } + @Test fun `validate - ensure sync blocked with invalid string aci and empty binary list is marked invalid`() { val envelope = Envelope(sourceServiceId = SELF_ACI.toString())