diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java index b993ca83..a929ff9b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java @@ -73,6 +73,7 @@ import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.SignalServiceAddress; 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.Envelope; import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; @@ -89,6 +90,12 @@ import java.util.stream.Collectors; public final class IncomingMessageHandler { private static final Logger logger = LoggerFactory.getLogger(IncomingMessageHandler.class); + private static final String DATA_MESSAGE_BODY_RANGE_OUT_OF_BOUNDS_REASON = + "[DataMessage] Body range with out-of-bounds start/length!"; + private static final String DATA_MESSAGE_QUOTE_BODY_RANGE_OUT_OF_BOUNDS_REASON = + "[DataMessage] Quote body range with out-of-bounds start/length!"; + private static final String EDIT_MESSAGE_BODY_RANGE_OUT_OF_BOUNDS_REASON = + "[EditMessage] Body range with out-of-bounds start/length!"; private final SignalAccount account; private final SignalDependencies dependencies; @@ -271,21 +278,31 @@ public final class IncomingMessageHandler { final EnvelopeMetadata envelopeMetadata, final Content content ) { - final var dataMessage = content.dataMessage != null - ? content.dataMessage - : content.syncMessage != null && content.syncMessage.sent != null - ? content.syncMessage.sent.message - : null; - final Integer bodyLength = dataMessage == null - ? null - : dataMessage.body == null ? 0 : dataMessage.body.length(); - final List invalidBodyRanges; - if (dataMessage == null) { - invalidBodyRanges = List.of(); + final var reason = validationResult.getReason(); + final var dataMessage = getDataMessage(content, reason); + final String body; + final List bodyRanges; + if (DATA_MESSAGE_QUOTE_BODY_RANGE_OUT_OF_BOUNDS_REASON.equals(reason)) { + if (dataMessage == null || dataMessage.quote == null) { + body = null; + bodyRanges = null; + } else { + body = dataMessage.quote.text; + bodyRanges = dataMessage.quote.bodyRanges; + } + } else if (dataMessage == null) { + body = null; + bodyRanges = null; } else { - invalidBodyRanges = new ArrayList<>(); - for (int i = 0; i < dataMessage.bodyRanges.size(); i++) { - final var range = dataMessage.bodyRanges.get(i); + body = dataMessage.body; + bodyRanges = dataMessage.bodyRanges; + } + + final Integer bodyLength = bodyRanges == null ? null : body == null ? 0 : body.length(); + final List invalidBodyRanges = new ArrayList<>(); + if (bodyRanges != null) { + for (int i = 0; i < bodyRanges.size(); i++) { + final var range = bodyRanges.get(i); final long start = range.start == null ? 0 : range.start; final long length = range.length == null ? 0 : range.length; if (start < 0 || length < 0 || start + length > bodyLength) { @@ -297,10 +314,10 @@ public final class IncomingMessageHandler { } } - final var code = "[DataMessage] Body range with out-of-bounds start/length!".equals(validationResult.getReason()) + final var code = isBodyRangeOutOfBoundsReason(reason) ? InvalidEnvelopeContentException.DATA_MESSAGE_BODY_RANGE_OUT_OF_BOUNDS : InvalidEnvelopeContentException.INVALID_ENVELOPE_CONTENT; - return new InvalidEnvelopeContentException(validationResult.getReason(), + return new InvalidEnvelopeContentException(reason, code, envelopeMetadata.getSourceServiceId().toString(), envelopeMetadata.getSourceDeviceId(), @@ -309,6 +326,38 @@ public final class IncomingMessageHandler { validationResult.getThrowable()); } + private static DataMessage getDataMessage(final Content content, final String validationReason) { + if (validationReason.startsWith("[EditMessage]")) { + return getEditDataMessage(content); + } + + if (content.dataMessage != null) { + return content.dataMessage; + } + if (content.syncMessage != null && content.syncMessage.sent != null + && content.syncMessage.sent.message != null) { + return content.syncMessage.sent.message; + } + return getEditDataMessage(content); + } + + private static DataMessage getEditDataMessage(final Content content) { + if (content.editMessage != null) { + return content.editMessage.dataMessage; + } + if (content.syncMessage != null && content.syncMessage.sent != null + && content.syncMessage.sent.editMessage != null) { + return content.syncMessage.sent.editMessage.dataMessage; + } + return null; + } + + private static boolean isBodyRangeOutOfBoundsReason(final String reason) { + return DATA_MESSAGE_BODY_RANGE_OUT_OF_BOUNDS_REASON.equals(reason) + || DATA_MESSAGE_QUOTE_BODY_RANGE_OUT_OF_BOUNDS_REASON.equals(reason) + || EDIT_MESSAGE_BODY_RANGE_OUT_OF_BOUNDS_REASON.equals(reason); + } + private static String getBodyRangeType(final BodyRange range) { if (range.style != null) { return "STYLE_" + range.style.name(); @@ -357,7 +406,7 @@ public final class IncomingMessageHandler { // Check again in case the user just joined the group notAllowedToSendToGroup = notAllowedToSendToGroup && isNotAllowedToSendToGroup(envelope, content); - if (isMessageBlocked(envelope, content)) { + if (isMessageBlocked(envelope, content, exception)) { logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp()); return List.of(); } else if (notAllowedToSendToGroup) { @@ -826,8 +875,12 @@ public final class IncomingMessageHandler { return null; } - private boolean isMessageBlocked(SignalServiceEnvelope envelope, SignalServiceContent content) { - SignalServiceAddress source = getSenderAddress(envelope, content); + private boolean isMessageBlocked( + SignalServiceEnvelope envelope, + SignalServiceContent content, + Exception exception + ) { + SignalServiceAddress source = getSenderAddress(envelope, content, exception); if (source == null) { return false; } @@ -1134,7 +1187,10 @@ public final class IncomingMessageHandler { this.account.getProfileStore().storeProfileKey(source, profileKey); } - private SignalServiceAddress getSenderAddress(SignalServiceEnvelope envelope, SignalServiceContent content) { + private static SignalServiceAddress getSenderAddress( + final SignalServiceEnvelope envelope, + final SignalServiceContent content + ) { final var serviceId = envelope.getSourceServiceId(); if (!envelope.isUnidentifiedSender() && serviceId != null) { return new SignalServiceAddress(serviceId); @@ -1145,6 +1201,24 @@ public final class IncomingMessageHandler { } } + static SignalServiceAddress getSenderAddress( + final SignalServiceEnvelope envelope, + final SignalServiceContent content, + final Exception exception + ) { + final var source = getSenderAddress(envelope, content); + if (source != null) { + return source; + } + if (exception instanceof InvalidEnvelopeContentException e && e.getSender() != null) { + final var sender = ServiceId.parseOrNull(e.getSender()); + if (sender != null) { + return new SignalServiceAddress(sender); + } + } + return null; + } + private DeviceAddress getSender(SignalServiceEnvelope envelope, SignalServiceContent content) { final var serviceId = envelope.getSourceServiceId(); if (!envelope.isUnidentifiedSender() && serviceId != null) { diff --git a/lib/src/test/java/org/asamk/signal/manager/helper/IncomingMessageHandlerTest.java b/lib/src/test/java/org/asamk/signal/manager/helper/IncomingMessageHandlerTest.java index 65e0ba83..569ad51a 100644 --- a/lib/src/test/java/org/asamk/signal/manager/helper/IncomingMessageHandlerTest.java +++ b/lib/src/test/java/org/asamk/signal/manager/helper/IncomingMessageHandlerTest.java @@ -5,9 +5,12 @@ import org.junit.jupiter.api.Test; import org.signal.core.models.ServiceId.ACI; import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata; import org.whispersystems.signalservice.api.messages.EnvelopeContentValidator; +import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; 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; +import org.whispersystems.signalservice.internal.push.Envelope; import org.whispersystems.signalservice.internal.push.SyncMessage; import java.util.List; @@ -60,4 +63,68 @@ class IncomingMessageHandlerTest { assertEquals(List.of(new InvalidEnvelopeContentException.InvalidBodyRange(0, -1, 2, "MENTION")), exception.getInvalidBodyRanges()); } + + @Test + void invalidEnvelopeContentReportsOutOfBoundsBodyRangeFromEditMessage() { + final var sender = ACI.parseOrThrow("2a04f0cc-199f-4b93-99d8-13c6b10a70de"); + final var bodyRange = new BodyRange.Builder().start(5).length(1).style(BodyRange.Style.ITALIC).build(); + final var dataMessage = new DataMessage.Builder().body("hello").bodyRanges(List.of(bodyRange)).build(); + final var editMessage = new EditMessage.Builder().targetSentTimestamp(1L).dataMessage(dataMessage).build(); + final var content = new Content.Builder().editMessage(editMessage).build(); + final var metadata = new EnvelopeMetadata(sender, null, 2, false, null, sender, 1); + final var validationResult = new EnvelopeContentValidator.Result.Invalid( + "[EditMessage] Body range with out-of-bounds start/length!", + new Throwable()); + + final var exception = IncomingMessageHandler.createInvalidEnvelopeContentException(validationResult, + metadata, + content); + + assertEquals(InvalidEnvelopeContentException.DATA_MESSAGE_BODY_RANGE_OUT_OF_BOUNDS, exception.getCode()); + assertEquals(5, exception.getBodyLength()); + assertEquals(List.of(new InvalidEnvelopeContentException.InvalidBodyRange(0, 5, 1, "STYLE_ITALIC")), + exception.getInvalidBodyRanges()); + } + + @Test + void invalidEnvelopeContentReportsOutOfBoundsBodyRangeFromQuote() { + final var sender = ACI.parseOrThrow("2a04f0cc-199f-4b93-99d8-13c6b10a70de"); + final var bodyRange = new BodyRange.Builder().start(2).length(2).style(BodyRange.Style.MONOSPACE).build(); + final var quote = new DataMessage.Quote.Builder().text("hey").bodyRanges(List.of(bodyRange)).build(); + final var dataMessage = new DataMessage.Builder().body("outer body").quote(quote).build(); + final var content = new Content.Builder().dataMessage(dataMessage).build(); + final var metadata = new EnvelopeMetadata(sender, null, 2, false, null, sender, 1); + final var validationResult = new EnvelopeContentValidator.Result.Invalid( + "[DataMessage] Quote body range with out-of-bounds start/length!", + new Throwable()); + + final var exception = IncomingMessageHandler.createInvalidEnvelopeContentException(validationResult, + metadata, + content); + + assertEquals(InvalidEnvelopeContentException.DATA_MESSAGE_BODY_RANGE_OUT_OF_BOUNDS, exception.getCode()); + assertEquals(3, exception.getBodyLength()); + assertEquals(List.of(new InvalidEnvelopeContentException.InvalidBodyRange(0, 2, 2, "STYLE_MONOSPACE")), + exception.getInvalidBodyRanges()); + } + + @Test + void invalidSealedSenderCanBeResolvedForBlocking() { + final var sender = ACI.parseOrThrow("2a04f0cc-199f-4b93-99d8-13c6b10a70de"); + final var envelope = new SignalServiceEnvelope(new Envelope.Builder() + .type(Envelope.Type.UNIDENTIFIED_SENDER) + .clientTimestamp(1L) + .build(), 2L); + final var exception = new InvalidEnvelopeContentException("invalid body range", + InvalidEnvelopeContentException.DATA_MESSAGE_BODY_RANGE_OUT_OF_BOUNDS, + sender.toString(), + 2, + 5, + List.of(), + new Throwable()); + + final var source = IncomingMessageHandler.getSenderAddress(envelope, null, exception); + + assertEquals(sender, source.getServiceId()); + } }