Improve body range validations.

This commit is contained in:
Greyson Parrelli 2026-06-08 16:22:42 +00:00 committed by Cody Henthorne
parent 26b1d3a0f8
commit ccfbb27695
2 changed files with 113 additions and 0 deletions

View File

@ -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<BodyRange>.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)
}

View File

@ -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())