From 1cce283aca84e59c0ac401f0ae0e32a12c34fc60 Mon Sep 17 00:00:00 2001 From: ziggy Date: Fri, 10 Jul 2026 17:48:12 +0300 Subject: [PATCH 1/5] Expose invalid envelope content in receive output --- .../api/InvalidEnvelopeContentException.java | 54 +++++++++++++++ .../signal/manager/api/MessageEnvelope.java | 4 ++ .../helper/IncomingMessageHandler.java | 69 ++++++++++++++++++- .../helper/IncomingMessageHandlerTest.java | 40 +++++++++++ .../java/org/asamk/signal/json/JsonError.java | 22 +++++- .../org/asamk/signal/json/JsonErrorTest.java | 50 ++++++++++++++ 6 files changed, 234 insertions(+), 5 deletions(-) create mode 100644 lib/src/main/java/org/asamk/signal/manager/api/InvalidEnvelopeContentException.java create mode 100644 lib/src/test/java/org/asamk/signal/manager/helper/IncomingMessageHandlerTest.java create mode 100644 src/test/java/org/asamk/signal/json/JsonErrorTest.java diff --git a/lib/src/main/java/org/asamk/signal/manager/api/InvalidEnvelopeContentException.java b/lib/src/main/java/org/asamk/signal/manager/api/InvalidEnvelopeContentException.java new file mode 100644 index 00000000..0ecabb35 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/InvalidEnvelopeContentException.java @@ -0,0 +1,54 @@ +package org.asamk.signal.manager.api; + +import java.util.List; + +public final class InvalidEnvelopeContentException extends Exception { + + public static final String INVALID_ENVELOPE_CONTENT = "INVALID_ENVELOPE_CONTENT"; + public static final String DATA_MESSAGE_BODY_RANGE_OUT_OF_BOUNDS = "DATA_MESSAGE_BODY_RANGE_OUT_OF_BOUNDS"; + + private final String code; + private final String sender; + private final int senderDevice; + private final Integer bodyLength; + private final List invalidBodyRanges; + + public InvalidEnvelopeContentException( + final String message, + final String code, + final String sender, + final int senderDevice, + final Integer bodyLength, + final List invalidBodyRanges, + final Throwable cause + ) { + super(message, cause); + this.code = code; + this.sender = sender; + this.senderDevice = senderDevice; + this.bodyLength = bodyLength; + this.invalidBodyRanges = List.copyOf(invalidBodyRanges); + } + + public String getCode() { + return code; + } + + public String getSender() { + return sender; + } + + public int getSenderDevice() { + return senderDevice; + } + + public Integer getBodyLength() { + return bodyLength; + } + + public List getInvalidBodyRanges() { + return invalidBodyRanges; + } + + public record InvalidBodyRange(int index, Integer start, Integer length, String type) {} +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java index 34b2274d..6b2c59da 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java @@ -4,6 +4,7 @@ import org.asamk.signal.manager.groups.GroupUtils; import org.asamk.signal.manager.helper.RecipientAddressResolver; import org.asamk.signal.manager.storage.recipients.RecipientResolver; import org.asamk.signal.manager.util.MimeUtils; +import org.signal.core.models.ServiceId; import org.signal.libsignal.metadata.ProtocolException; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; @@ -1032,6 +1033,8 @@ public record MessageEnvelope( ? recipientResolver.resolveRecipient(serviceId) : envelope.isUnidentifiedSender() && content != null ? recipientResolver.resolveRecipient(content.getSender()) + : exception instanceof InvalidEnvelopeContentException e && e.getSender() != null + ? recipientResolver.resolveRecipient(ServiceId.parseOrThrow(e.getSender())) : exception instanceof ProtocolException e ? recipientResolver.resolveRecipient(e.getSender()) : null; @@ -1039,6 +1042,7 @@ public record MessageEnvelope( ? envelope.getSourceDevice() : content != null ? content.getSenderDevice() + : exception instanceof InvalidEnvelopeContentException e ? e.getSenderDevice() : exception instanceof ProtocolException e ? e.getSenderDevice() : 0; Optional receipt; 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 5069749c..62545c52 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 @@ -21,6 +21,7 @@ import org.asamk.signal.manager.actions.SyncStorageDataAction; import org.asamk.signal.manager.actions.UpdateAccountAttributesAction; import org.asamk.signal.manager.api.GroupId; import org.asamk.signal.manager.api.GroupNotFoundException; +import org.asamk.signal.manager.api.InvalidEnvelopeContentException; import org.asamk.signal.manager.api.MessageEnvelope; import org.asamk.signal.manager.api.Pair; import org.asamk.signal.manager.api.ReceiveConfig; @@ -52,6 +53,7 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.InvalidMessageStructureException; +import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata; import org.whispersystems.signalservice.api.crypto.SignalGroupSessionBuilder; import org.whispersystems.signalservice.api.crypto.SignalServiceCipherResult; import org.whispersystems.signalservice.api.messages.EnvelopeContentValidator; @@ -69,6 +71,8 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage; 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.Envelope; import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; @@ -225,7 +229,8 @@ public final class IncomingMessageHandler { Envelope envelope, SignalServiceCipherResult cipherResult, long serverDeliveredTimestamp - ) throws ProtocolInvalidKeyException, ProtocolInvalidMessageException, UnsupportedDataMessageException, InvalidMessageStructureException { + ) throws ProtocolInvalidKeyException, ProtocolInvalidMessageException, UnsupportedDataMessageException, + InvalidMessageStructureException, InvalidEnvelopeContentException { final var content = cipherResult.getContent(); final var envelopeMetadata = cipherResult.getMetadata(); final var validationResult = EnvelopeContentValidator.INSTANCE.validate(envelope, @@ -234,8 +239,17 @@ public final class IncomingMessageHandler { cipherResult.getMetadata().getCiphertextMessageType()); if (validationResult instanceof EnvelopeContentValidator.Result.Invalid v) { - logger.warn("Invalid content! {}", v.getReason(), v.getThrowable()); - return null; + final var exception = createInvalidEnvelopeContentException(v, envelopeMetadata, content); + logger.warn("Invalid content! reason={} code={} source={} sourceDevice={} timestamp={} bodyLength={} invalidBodyRanges={}", + exception.getMessage(), + exception.getCode(), + exception.getSender(), + exception.getSenderDevice(), + envelope.clientTimestamp, + exception.getBodyLength(), + exception.getInvalidBodyRanges()); + logger.debug("Invalid content validation location", v.getThrowable()); + throw exception; } if (validationResult instanceof EnvelopeContentValidator.Result.UnsupportedDataMessage v) { @@ -252,6 +266,55 @@ public final class IncomingMessageHandler { serverDeliveredTimestamp); } + static InvalidEnvelopeContentException createInvalidEnvelopeContentException( + final EnvelopeContentValidator.Result.Invalid validationResult, + final EnvelopeMetadata envelopeMetadata, + final Content content + ) { + final var dataMessage = content.dataMessage; + final Integer bodyLength = dataMessage == null + ? null + : dataMessage.body == null ? 0 : dataMessage.body.length(); + final List invalidBodyRanges; + if (dataMessage == null) { + invalidBodyRanges = List.of(); + } else { + invalidBodyRanges = new ArrayList<>(); + for (int i = 0; i < dataMessage.bodyRanges.size(); i++) { + final var range = dataMessage.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) { + invalidBodyRanges.add(new InvalidEnvelopeContentException.InvalidBodyRange(i, + range.start, + range.length, + getBodyRangeType(range))); + } + } + } + + final var code = "[DataMessage] Body range with out-of-bounds start/length!".equals(validationResult.getReason()) + ? InvalidEnvelopeContentException.DATA_MESSAGE_BODY_RANGE_OUT_OF_BOUNDS + : InvalidEnvelopeContentException.INVALID_ENVELOPE_CONTENT; + return new InvalidEnvelopeContentException(validationResult.getReason(), + code, + envelopeMetadata.getSourceServiceId().toString(), + envelopeMetadata.getSourceDeviceId(), + bodyLength, + invalidBodyRanges, + validationResult.getThrowable()); + } + + private static String getBodyRangeType(final BodyRange range) { + if (range.style != null) { + return "STYLE_" + range.style.name(); + } + if (range.mentionAci != null || range.mentionAciBinary != null) { + return "MENTION"; + } + return "UNKNOWN"; + } + private List checkAndHandleMessage( final SignalServiceEnvelope envelope, final SignalServiceContent content, 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 new file mode 100644 index 00000000..33ecee72 --- /dev/null +++ b/lib/src/test/java/org/asamk/signal/manager/helper/IncomingMessageHandlerTest.java @@ -0,0 +1,40 @@ +package org.asamk.signal.manager.helper; + +import org.asamk.signal.manager.api.InvalidEnvelopeContentException; +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.internal.push.BodyRange; +import org.whispersystems.signalservice.internal.push.Content; +import org.whispersystems.signalservice.internal.push.DataMessage; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class IncomingMessageHandlerTest { + + @Test + void invalidEnvelopeContentReportsOutOfBoundsBodyRange() { + final var sender = ACI.parseOrThrow("2a04f0cc-199f-4b93-99d8-13c6b10a70de"); + final var bodyRange = new BodyRange.Builder().start(4).length(3).style(BodyRange.Style.BOLD).build(); + final var dataMessage = new DataMessage.Builder().body("hello").bodyRanges(List.of(bodyRange)).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] 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(sender.toString(), exception.getSender()); + assertEquals(2, exception.getSenderDevice()); + assertEquals(5, exception.getBodyLength()); + assertEquals(List.of(new InvalidEnvelopeContentException.InvalidBodyRange(0, 4, 3, "STYLE_BOLD")), + exception.getInvalidBodyRanges()); + } +} diff --git a/src/main/java/org/asamk/signal/json/JsonError.java b/src/main/java/org/asamk/signal/json/JsonError.java index d881c0b7..bc6e4254 100644 --- a/src/main/java/org/asamk/signal/json/JsonError.java +++ b/src/main/java/org/asamk/signal/json/JsonError.java @@ -1,11 +1,29 @@ package org.asamk.signal.json; +import com.fasterxml.jackson.annotation.JsonInclude; import io.micronaut.jsonschema.JsonSchema; +import org.asamk.signal.manager.api.InvalidEnvelopeContentException; + +import java.util.List; + @JsonSchema(title = "Error") -public record JsonError(String message, String type) { +public record JsonError( + String message, + String type, + @JsonInclude(JsonInclude.Include.NON_NULL) Details details +) { public static JsonError from(Throwable exception) { - return new JsonError(exception.getMessage(), exception.getClass().getSimpleName()); + final var details = exception instanceof InvalidEnvelopeContentException e + ? new Details(e.getCode(), e.getBodyLength(), e.getInvalidBodyRanges()) + : null; + return new JsonError(exception.getMessage(), exception.getClass().getSimpleName(), details); } + + public record Details( + String code, + Integer bodyLength, + List invalidBodyRanges + ) {} } diff --git a/src/test/java/org/asamk/signal/json/JsonErrorTest.java b/src/test/java/org/asamk/signal/json/JsonErrorTest.java new file mode 100644 index 00000000..8f3e43f0 --- /dev/null +++ b/src/test/java/org/asamk/signal/json/JsonErrorTest.java @@ -0,0 +1,50 @@ +package org.asamk.signal.json; + +import org.asamk.signal.manager.api.InvalidEnvelopeContentException; +import org.asamk.signal.util.Util; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class JsonErrorTest { + + @Test + void invalidEnvelopeContentIncludesStructuredDetails() throws Exception { + final var invalidRange = new InvalidEnvelopeContentException.InvalidBodyRange(2, 8, 4, "STYLE_BOLD"); + final var exception = new InvalidEnvelopeContentException("invalid body range", + InvalidEnvelopeContentException.DATA_MESSAGE_BODY_RANGE_OUT_OF_BOUNDS, + null, + 3, + 10, + List.of(invalidRange), + new Throwable()); + + final var error = JsonError.from(exception); + + assertEquals("invalid body range", error.message()); + assertEquals("InvalidEnvelopeContentException", error.type()); + assertEquals(InvalidEnvelopeContentException.DATA_MESSAGE_BODY_RANGE_OUT_OF_BOUNDS, + error.details().code()); + assertEquals(10, error.details().bodyLength()); + assertEquals(List.of(invalidRange), error.details().invalidBodyRanges()); + final var json = Util.createJsonObjectMapper().writeValueAsString(error); + assertTrue(json.contains("\"code\":\"DATA_MESSAGE_BODY_RANGE_OUT_OF_BOUNDS\"")); + assertTrue(json.contains("\"start\":8")); + } + + @Test + void ordinaryExceptionDoesNotIncludeDetails() throws Exception { + final var error = JsonError.from(new IllegalArgumentException("bad argument")); + + assertEquals("bad argument", error.message()); + assertEquals("IllegalArgumentException", error.type()); + assertNull(error.details()); + final var json = Util.createJsonObjectMapper().writeValueAsString(error); + assertFalse(json.contains("details")); + } +} From a770b03fe6f4c38607bc190e3930cbb59140dc33 Mon Sep 17 00:00:00 2001 From: ziggy Date: Fri, 10 Jul 2026 17:54:03 +0300 Subject: [PATCH 2/5] Complete invalid envelope diagnostics --- .../helper/IncomingMessageHandler.java | 6 ++++- .../helper/IncomingMessageHandlerTest.java | 23 +++++++++++++++++++ .../signal-cli/reachability-metadata.json | 12 ++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) 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 62545c52..b993ca83 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 @@ -271,7 +271,11 @@ public final class IncomingMessageHandler { final EnvelopeMetadata envelopeMetadata, final Content content ) { - final var dataMessage = content.dataMessage; + 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(); 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 33ecee72..65e0ba83 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 @@ -8,6 +8,7 @@ import org.whispersystems.signalservice.api.messages.EnvelopeContentValidator; 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.SyncMessage; import java.util.List; @@ -37,4 +38,26 @@ class IncomingMessageHandlerTest { assertEquals(List.of(new InvalidEnvelopeContentException.InvalidBodyRange(0, 4, 3, "STYLE_BOLD")), exception.getInvalidBodyRanges()); } + + @Test + void invalidEnvelopeContentReportsOutOfBoundsBodyRangeFromSyncMessage() { + final var sender = ACI.parseOrThrow("2a04f0cc-199f-4b93-99d8-13c6b10a70de"); + final var bodyRange = new BodyRange.Builder().start(-1).length(2).mentionAci(sender.toString()).build(); + final var dataMessage = new DataMessage.Builder().body("hello").bodyRanges(List.of(bodyRange)).build(); + final var sent = new SyncMessage.Sent.Builder().message(dataMessage).build(); + final var content = new Content.Builder().syncMessage(new SyncMessage.Builder().sent(sent).build()).build(); + final var metadata = new EnvelopeMetadata(sender, null, 2, false, null, sender, 1); + final var validationResult = new EnvelopeContentValidator.Result.Invalid( + "[DataMessage] 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, -1, 2, "MENTION")), + exception.getInvalidBodyRanges()); + } } diff --git a/src/main/resources/META-INF/native-image/org.asamk/signal-cli/reachability-metadata.json b/src/main/resources/META-INF/native-image/org.asamk/signal-cli/reachability-metadata.json index e69e2df6..9576f887 100644 --- a/src/main/resources/META-INF/native-image/org.asamk/signal-cli/reachability-metadata.json +++ b/src/main/resources/META-INF/native-image/org.asamk/signal-cli/reachability-metadata.json @@ -2949,6 +2949,12 @@ "allDeclaredMethods": true, "allDeclaredConstructors": true }, + { + "type": "org.asamk.signal.json.JsonError$Details", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, { "type": "org.asamk.signal.json.JsonGroupInfo", "allDeclaredFields": true, @@ -3419,6 +3425,12 @@ { "type": "org.asamk.signal.logging.LogConfigurator" }, + { + "type": "org.asamk.signal.manager.api.InvalidEnvelopeContentException$InvalidBodyRange", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, { "type": "org.asamk.signal.manager.api.PhoneNumberSharingMode", "allDeclaredFields": true From 8b74f0653d0a6a0e15fd39794f44774b2a2a5c57 Mon Sep 17 00:00:00 2001 From: ziggy Date: Fri, 10 Jul 2026 18:01:23 +0300 Subject: [PATCH 3/5] Handle invalid range variants safely --- .../helper/IncomingMessageHandler.java | 114 +++++++++++++++--- .../helper/IncomingMessageHandlerTest.java | 67 ++++++++++ 2 files changed, 161 insertions(+), 20 deletions(-) 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()); + } } From 712c9ea741dcacb44d658ab9d9d02b2d2514f714 Mon Sep 17 00:00:00 2001 From: ziggy Date: Fri, 10 Jul 2026 18:27:17 +0300 Subject: [PATCH 4/5] Preserve group filters for invalid content --- .../helper/IncomingMessageHandler.java | 183 ++++++++++++++---- .../helper/IncomingMessageHandlerTest.java | 44 +++++ 2 files changed, 185 insertions(+), 42 deletions(-) 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 a929ff9b..85368459 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 @@ -32,6 +32,7 @@ import org.asamk.signal.manager.groups.GroupUtils; import org.asamk.signal.manager.internal.SignalDependencies; import org.asamk.signal.manager.jobs.RetrieveStickerPackJob; import org.asamk.signal.manager.storage.SignalAccount; +import org.asamk.signal.manager.storage.groups.GroupInfo; import org.asamk.signal.manager.storage.groups.GroupInfoV1; import org.asamk.signal.manager.storage.recipients.RecipientAddress; import org.asamk.signal.manager.storage.recipients.RecipientId; @@ -49,6 +50,7 @@ import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.groups.GroupSessionBuilder; import org.signal.libsignal.protocol.message.DecryptionErrorMessage; import org.signal.libsignal.zkgroup.InvalidInputException; +import org.signal.libsignal.zkgroup.groups.GroupMasterKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -75,6 +77,8 @@ 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.GroupContext; +import org.whispersystems.signalservice.internal.push.GroupContextV2; import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; import java.io.IOException; @@ -141,7 +145,7 @@ public final class IncomingMessageHandler { account.getIdentityKeyStore().setRetryingDecryption(false); } } - actions.addAll(checkAndHandleMessage(envelope, content, receiveConfig, handler, null)); + actions.addAll(checkAndHandleMessage(envelope, content, null, receiveConfig, handler, null)); return new Pair<>(actions, null); } @@ -155,6 +159,7 @@ public final class IncomingMessageHandler { actions.add(RefreshPreKeysAction.create()); } SignalServiceContent content = null; + Content decryptedContent = null; Exception exception = null; if (envelope.getSourceServiceId() != null) { // Store uuid if we don't have it already @@ -178,6 +183,7 @@ public final class IncomingMessageHandler { final var cipherResult = dependencies.getCipher(destination == null || destination.equals(account.getAci()) ? ServiceIdType.ACI : ServiceIdType.PNI) .decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp()); + decryptedContent = cipherResult.getContent(); content = validate(envelope.getProto(), cipherResult, envelope.getServerDeliveredTimestamp()); if (content == null) { return new Pair<>(List.of(), null); @@ -228,7 +234,12 @@ public final class IncomingMessageHandler { } } - actions.addAll(checkAndHandleMessage(envelope, content, receiveConfig, handler, exception)); + actions.addAll(checkAndHandleMessage(envelope, + content, + exception instanceof InvalidEnvelopeContentException ? decryptedContent : null, + receiveConfig, + handler, + exception)); return new Pair<>(actions, exception); } @@ -371,6 +382,7 @@ public final class IncomingMessageHandler { private List checkAndHandleMessage( final SignalServiceEnvelope envelope, final SignalServiceContent content, + final Content invalidContent, final ReceiveConfig receiveConfig, final Manager.ReceiveMessageHandler handler, final Exception exception @@ -398,19 +410,21 @@ public final class IncomingMessageHandler { account.getMessageSendLogStore().deleteEntryForRecipient(envelope.getTimestamp(), sender, senderDeviceId); } - var notAllowedToSendToGroup = isNotAllowedToSendToGroup(envelope, content); + final var groupFilterInfo = getGroupFilterInfo(content, invalidContent, exception); + var notAllowedToSendToGroup = isNotAllowedToSendToGroup(envelope, content, exception, groupFilterInfo); final var groupContext = getGroupContext(content); if (groupContext != null && groupContext.getGroupV2().isPresent()) { handleGroupV2Context(groupContext.getGroupV2().get(), receiveConfig.ignoreAvatars()); } // Check again in case the user just joined the group - notAllowedToSendToGroup = notAllowedToSendToGroup && isNotAllowedToSendToGroup(envelope, content); + notAllowedToSendToGroup = notAllowedToSendToGroup + && isNotAllowedToSendToGroup(envelope, content, exception, groupFilterInfo); - if (isMessageBlocked(envelope, content, exception)) { + if (isMessageBlocked(envelope, content, exception, groupFilterInfo)) { logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp()); return List.of(); } else if (notAllowedToSendToGroup) { - final var senderAddress = getSenderAddress(envelope, content); + final var senderAddress = getSenderAddress(envelope, content, exception); logger.info("Ignoring a group message from an unauthorized sender (no member or admin): {} {}", senderAddress == null ? null : senderAddress.getIdentifier(), envelope.getTimestamp()); @@ -849,7 +863,7 @@ public final class IncomingMessageHandler { return new Pair<>(actions, longTexts); } - private SignalServiceGroupContext getGroupContext(SignalServiceContent content) { + private static SignalServiceGroupContext getGroupContext(SignalServiceContent content) { if (content == null) { return null; } @@ -878,52 +892,59 @@ public final class IncomingMessageHandler { private boolean isMessageBlocked( SignalServiceEnvelope envelope, SignalServiceContent content, - Exception exception + Exception exception, + GroupFilterInfo groupFilterInfo ) { SignalServiceAddress source = getSenderAddress(envelope, content, exception); - if (source == null) { - return false; - } - final var recipientId = account.getRecipientResolver().resolveRecipient(source); - if (context.getContactHelper().isContactBlocked(recipientId)) { - return true; + if (source != null) { + final var recipientId = account.getRecipientResolver().resolveRecipient(source); + if (context.getContactHelper().isContactBlocked(recipientId)) { + return true; + } } - final var groupContext = getGroupContext(content); - if (groupContext != null) { - var groupId = GroupUtils.getGroupId(groupContext); - return context.getGroupHelper().isGroupBlocked(groupId); + if (groupFilterInfo != null) { + return isGroupBlocked(context.getGroupHelper().getGroup(groupFilterInfo.groupId())); } return false; } - private boolean isNotAllowedToSendToGroup(SignalServiceEnvelope envelope, SignalServiceContent content) { - SignalServiceAddress source = getSenderAddress(envelope, content); + private boolean isNotAllowedToSendToGroup( + SignalServiceEnvelope envelope, + SignalServiceContent content, + Exception exception, + GroupFilterInfo groupFilterInfo + ) { + SignalServiceAddress source = getSenderAddress(envelope, content, exception); if (source == null) { return false; } - final var groupContext = getGroupContext(content); - if (groupContext == null) { + if (groupFilterInfo == null) { return false; } - if (groupContext.getGroupV1().isPresent()) { - var groupInfo = groupContext.getGroupV1().get(); - if (groupInfo.getType() == SignalServiceGroup.Type.QUIT) { - return false; - } + if (groupFilterInfo.isQuit()) { + return false; } - final var message = content.getDataMessage().orElse(null); - final var recipientId = account.getRecipientResolver().resolveRecipient(source); - final var groupId = GroupUtils.getGroupId(groupContext); - final var group = context.getGroupHelper().getGroup(groupId); + final var group = context.getGroupHelper().getGroup(groupFilterInfo.groupId()); + return isNotAllowedToSendToGroup(group, recipientId, groupFilterInfo); + } - if (message != null && message.getAdminDelete().isPresent() && (group == null || !group.isAdmin(recipientId))) { + static boolean isGroupBlocked(final GroupInfo group) { + return group != null && group.isBlocked(); + } + + static boolean isNotAllowedToSendToGroup( + final GroupInfo group, + final RecipientId recipientId, + final GroupFilterInfo groupFilterInfo + ) { + if (groupFilterInfo.hasAdminDelete() && (group == null || !group.isAdmin(recipientId))) { return true; } @@ -931,24 +952,102 @@ public final class IncomingMessageHandler { return false; } - if (!group.isMember(recipientId) && !( - group.isPendingMember(recipientId) && message != null && message.isGroupV2Update() - )) { + if (!group.isMember(recipientId) + && !(group.isPendingMember(recipientId) && groupFilterInfo.isGroupV2Update())) { return true; } if (group.isAnnouncementGroup() && !group.isAdmin(recipientId)) { - return message == null - || message.getBody().isPresent() - || message.getAttachments().isPresent() - || message.getQuote().isPresent() - || message.getPreviews().isPresent() - || message.getMentions().isPresent() - || message.getSticker().isPresent(); + return groupFilterInfo.hasAnnouncementContent(); } return false; } + static GroupFilterInfo getGroupFilterInfo( + final SignalServiceContent content, + final Content invalidContent, + final Exception exception + ) { + if (content != null) { + final var groupContext = getGroupContext(content); + if (groupContext == null) { + return null; + } + final var message = content.getDataMessage().orElse(null); + return new GroupFilterInfo(GroupUtils.getGroupId(groupContext), + groupContext.getGroupV1() + .map(group -> group.getType() == SignalServiceGroup.Type.QUIT) + .orElse(false), + message != null && message.getAdminDelete().isPresent(), + message != null && message.isGroupV2Update(), + message == null + || message.getBody().isPresent() + || message.getAttachments().isPresent() + || message.getQuote().isPresent() + || message.getPreviews().isPresent() + || message.getMentions().isPresent() + || message.getSticker().isPresent()); + } + + if (invalidContent == null || !(exception instanceof InvalidEnvelopeContentException e)) { + return null; + } + + final var message = getDataMessage(invalidContent, e.getMessage()); + if (message != null) { + final var groupId = getGroupId(message); + if (groupId == null) { + return null; + } + return new GroupFilterInfo(groupId, + message.group != null + && message.group.type == GroupContext.Type.QUIT, + message.adminDelete != null, + message.groupV2 != null + && message.groupV2.groupChange != null + && message.groupV2.groupChange.size() > 0, + message.body != null + || !message.attachments.isEmpty() + || message.quote != null + || !message.preview.isEmpty() + || message.bodyRanges.stream() + .anyMatch(range -> range.mentionAci != null || range.mentionAciBinary != null) + || message.sticker != null); + } + + if (invalidContent.storyMessage != null && invalidContent.storyMessage.group != null) { + final var groupId = getGroupId(invalidContent.storyMessage.group); + return groupId == null ? null : new GroupFilterInfo(groupId, false, false, false, true); + } + return null; + } + + private static GroupId getGroupId(final DataMessage message) { + if (message.group != null && message.group.id != null) { + return GroupId.v1(message.group.id.toByteArray()); + } + return message.groupV2 == null ? null : getGroupId(message.groupV2); + } + + private static GroupId getGroupId(final GroupContextV2 groupContext) { + if (groupContext.masterKey == null) { + return null; + } + try { + return GroupUtils.getGroupIdV2(new GroupMasterKey(groupContext.masterKey.toByteArray())); + } catch (InvalidInputException e) { + return null; + } + } + + record GroupFilterInfo( + GroupId groupId, + boolean isQuit, + boolean hasAdminDelete, + boolean isGroupV2Update, + boolean hasAnnouncementContent + ) {} + private Pair, Map> handleSignalServiceDataMessage( SignalServiceDataMessage message, boolean isSync, 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 569ad51a..9bd2fd92 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 @@ -1,6 +1,10 @@ package org.asamk.signal.manager.helper; +import okio.ByteString; +import org.asamk.signal.manager.api.GroupId; import org.asamk.signal.manager.api.InvalidEnvelopeContentException; +import org.asamk.signal.manager.storage.groups.GroupInfoV1; +import org.asamk.signal.manager.storage.recipients.TestRecipientId; import org.junit.jupiter.api.Test; import org.signal.core.models.ServiceId.ACI; import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata; @@ -11,11 +15,14 @@ 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.GroupContext; import org.whispersystems.signalservice.internal.push.SyncMessage; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; class IncomingMessageHandlerTest { @@ -127,4 +134,41 @@ class IncomingMessageHandlerTest { assertEquals(sender, source.getServiceId()); } + + @Test + void invalidGroupContextIsAvailableForFiltering() { + final var sender = ACI.parseOrThrow("2a04f0cc-199f-4b93-99d8-13c6b10a70de"); + final var groupId = new byte[16]; + final var group = new GroupContext.Builder() + .id(ByteString.of(groupId)) + .type(GroupContext.Type.DELIVER) + .build(); + final var bodyRange = new BodyRange.Builder().start(4).length(3).style(BodyRange.Style.BOLD).build(); + final var dataMessage = new DataMessage.Builder() + .body("hello") + .bodyRanges(List.of(bodyRange)) + .group(group) + .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] Body range with out-of-bounds start/length!", + new Throwable()); + final var exception = IncomingMessageHandler.createInvalidEnvelopeContentException(validationResult, + metadata, + content); + + final var filterInfo = IncomingMessageHandler.getGroupFilterInfo(null, content, exception); + + assertNotNull(filterInfo); + assertEquals(GroupId.v1(groupId), filterInfo.groupId()); + assertTrue(filterInfo.hasAnnouncementContent()); + + final var storedGroup = new GroupInfoV1(GroupId.v1(groupId)); + storedGroup.setBlocked(true); + assertTrue(IncomingMessageHandler.isGroupBlocked(storedGroup)); + assertTrue(IncomingMessageHandler.isNotAllowedToSendToGroup(storedGroup, + TestRecipientId.createTestId(1), + filterInfo)); + } } From a041826cd595e1c9f21ebbb4205076f49a6afcfb Mon Sep 17 00:00:00 2001 From: ziggy Date: Fri, 10 Jul 2026 19:16:03 +0300 Subject: [PATCH 5/5] Report invalid content from retried envelopes --- .../manager/helper/IncomingMessageHandler.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) 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 85368459..9b924bba 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 @@ -122,6 +122,8 @@ public final class IncomingMessageHandler { } SignalServiceContent content = null; + Content decryptedContent = null; + InvalidEnvelopeContentException validationException = null; if (!envelope.isReceipt()) { account.getIdentityKeyStore().setRetryingDecryption(true); try { @@ -129,6 +131,7 @@ public final class IncomingMessageHandler { final var cipherResult = dependencies.getCipher(destination == null || destination.equals(account.getAci()) ? ServiceIdType.ACI : ServiceIdType.PNI) .decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp()); + decryptedContent = cipherResult.getContent(); content = validate(envelope.getProto(), cipherResult, envelope.getServerDeliveredTimestamp()); if (content == null) { return new Pair<>(List.of(), null); @@ -139,14 +142,21 @@ public final class IncomingMessageHandler { .resolveRecipientAddress(recipientId) .toApiRecipientAddress(), e.getSenderDevice()); return new Pair<>(List.of(), exception); + } catch (InvalidEnvelopeContentException e) { + validationException = e; } catch (Exception e) { return new Pair<>(List.of(), e); } finally { account.getIdentityKeyStore().setRetryingDecryption(false); } } - actions.addAll(checkAndHandleMessage(envelope, content, null, receiveConfig, handler, null)); - return new Pair<>(actions, null); + actions.addAll(checkAndHandleMessage(envelope, + content, + validationException == null ? null : decryptedContent, + receiveConfig, + handler, + validationException)); + return new Pair<>(actions, validationException); } public Pair, Exception> handleEnvelope(