Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
5c0110f
AMQ-8398 - Fix Stomp to OpenWire UTF-8 translation
cshannon Aug 28, 2024
66e1788
CVE-2026-49432: Update Stomp transports with improved validation (#20…
cshannon Jun 2, 2026
7fd1924
CVE-2026-50734: Add validation for WireFormatInfo (#2080) (#2082)
cshannon Jun 5, 2026
282e549
CVE-2026-50750: Ensure at most one BrokerInfo command is received (#2…
cshannon Jun 8, 2026
f84be00
CVE-2026-49877: Restrict full web console URI to admins role (#2074) …
cshannon Jun 8, 2026
acc5b15
CVE-2026-52760: Adding missing jsp escapes (#2089) (#2093)
cshannon Jun 8, 2026
637b0b1
CVE-2026-49434: Add validation for LDAP network connector URIs (#2077…
cshannon Jun 8, 2026
0a0eec1
CVE-2026-52760: Add back missing closing tag (#2100) (#2106)
cshannon Jun 11, 2026
f78c665
CVE-2026-53916: Validate Stomp headers against max frame size (#2104)…
cshannon Jun 12, 2026
faf6b79
CVE-2026-53916: Remove extra buffer copy in StompNIOSSLTransport (#21…
cshannon Jun 12, 2026
260300d
CVE-2026-54475: Replace active temp dests map with set (#2113) (#2115)
cshannon Jun 15, 2026
492de60
CVE-2026-53917: Provide a flexible filter style input stream that lim…
cshannon Jun 15, 2026
48abd95
CVE-2026-54475: Add flag to optionally enable temp destination steali…
cshannon Jun 17, 2026
ecad2a1
CVE-2026-53917: Improve the broker's handling of message corruption (…
cshannon Jun 23, 2026
465511e
CVE-2026-54475: Fix test by enabling allowTempDestinationStealing (#2…
cshannon Jun 23, 2026
782ae97
CVE-2026-53917: Improve handling of partial reads and EOF in Frame si…
cshannon Jun 24, 2026
6d984b8
Adapt backported CVE fixes/tests to JDK 8 and JMS 1.1 on 5.15.x-TT.x
Jul 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.activemq.ActiveMQMessageFormatException;
import org.apache.activemq.broker.region.AbstractSubscription;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ActiveMQMessage;
Expand All @@ -42,6 +43,7 @@
import org.apache.activemq.transport.amqp.message.AutoOutboundTransformer;
import org.apache.activemq.transport.amqp.message.EncodedMessage;
import org.apache.activemq.transport.amqp.message.OutboundTransformer;
import org.apache.activemq.util.ExceptionUtils;
import org.apache.qpid.proton.amqp.messaging.Accepted;
import org.apache.qpid.proton.amqp.messaging.Modified;
import org.apache.qpid.proton.amqp.messaging.Outcome;
Expand Down Expand Up @@ -492,7 +494,16 @@ public void pumpOutbound() throws Exception {
}
}
} catch (Exception e) {
LOG.warn("Error detected while flushing outbound messages: {}", e.getMessage());
// Check if there is a format error trying to convert the message. This error means the
// message can't be converted (corruption, etc). This will wrap and throw the message
// so it can be handled by the transport
ActiveMQMessageFormatException formatError = ExceptionUtils.createMessageFormatException(e);
if (formatError != null) {
LOG.warn("Message conversion error while flushing outbound messages: {}", e.getMessage(), e);
throw formatError;
} else {
LOG.warn("Error detected while flushing outbound messages: {}", e.getMessage());
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand All @@ -30,20 +32,25 @@
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.jms.BytesMessage;
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.*;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.BrokerFilter;
import org.apache.activemq.broker.BrokerPlugin;
import org.apache.activemq.broker.BrokerPluginSupport;
import org.apache.activemq.broker.ConnectionContext;
import org.apache.activemq.broker.region.MessageReference;
import org.apache.activemq.broker.region.Subscription;
import org.apache.activemq.command.ActiveMQBytesMessage;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ActiveMQMessage;
import org.apache.activemq.util.ByteSequenceData;
import org.apache.activemq.util.Wait;
import org.apache.qpid.proton.amqp.Binary;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
Expand All @@ -59,6 +66,7 @@ public class JMSInteroperabilityTest extends JMSClientTestSupport {

protected static final Logger LOG = LoggerFactory.getLogger(JMSInteroperabilityTest.class);

private final AtomicBoolean sentToDlq = new AtomicBoolean(false);
private final String transformer;

@Parameters(name="Transformer->{0}")
Expand All @@ -70,6 +78,13 @@ public static Collection<Object[]> data() {
});
}

@Before
@Override
public void setUp() throws Exception {
super.setUp();
sentToDlq.set(false);
}

public JMSInteroperabilityTest(String transformer) {
this.transformer = transformer;
}
Expand All @@ -84,7 +99,26 @@ protected String getAmqpTransformer() {
return transformer;
}

//----- Tests for property handling between protocols --------------------//
@Override
protected void addAdditionalPlugins(List<BrokerPlugin> plugins) throws Exception {
super.addAdditionalPlugins(plugins);
plugins.add(new BrokerPluginSupport() {
@Override
public Broker installPlugin(Broker broker) {
return new BrokerFilter(broker) {
@Override
public boolean sendToDeadLetterQueue(ConnectionContext context,
MessageReference messageReference, Subscription subscription,
Throwable poisonCause) {
sentToDlq.set(true);
return super.sendToDeadLetterQueue(context, messageReference,
subscription, poisonCause);
}
};
}
});
}
//----- Tests for property handling between protocols --------------------//

@SuppressWarnings("unchecked")
@Test(timeout = 60000)
Expand Down Expand Up @@ -483,8 +517,127 @@ public void testOpenWireToQpidObjectMessageWithOpenWireCompression() throws Exce
assertNotNull(payload);
assertTrue(payload instanceof UUID);

amqp.close();
openwire.close();
}

// The following tests for corruption will corrupt the headers or body
// to test that the AMQP protocol correctly passes the error during
// dispatch to allow the Transport Connection to properly handle
// with a poison ack so the message will be removed from the subscription.
// No selectors are set so these messages are only going to error
// during the protocol conversion.

@Test
public void testCorruptMessageErrorHeaders() throws Exception {
testCorruptMessageError(session -> {
ActiveMQBytesMessage message = (ActiveMQBytesMessage) session.createBytesMessage();
message.setStringProperty("testestt", "Testestt");
message.setStringProperty("prop2", "Testestt");
try {
message.beforeMarshall(null);
ByteSequenceData.writeIntBig(message.getMarshalledProperties(), 1024 * 1024);
} catch (IOException e) {
throw new RuntimeException(e);
}
return message;
}, false);
}

@Test
public void testCorruptMessageErrorMap() throws Exception {
testCorruptMessageError(session -> {
MapMessage message = session.createMapMessage();
message.setString("id", UUID.randomUUID().toString());
return message;
}, false);
}

// Check durable sub as well which is also a prefetch subscription so a
// poison ack will be handled the same way and DLQ
@Test
public void testCorruptMessageErrorMapDurableSub() throws Exception {
testCorruptMessageError(session -> {
MapMessage message = session.createMapMessage();
message.setString("id", UUID.randomUUID().toString());
return message;
}, true);
}

@Test
public void testCorruptMessageErrorText() throws Exception {
testCorruptMessageError(session -> {
TextMessage message = session.createTextMessage();
message.setText(UUID.randomUUID().toString());
return message;
}, false);
}

@Test
public void testCorruptMessageErrorTextDurableSub() throws Exception {
testCorruptMessageError(session -> {
TextMessage message = session.createTextMessage();
message.setText(UUID.randomUUID().toString());
return message;
}, true);
}


@Test
public void testCorruptMessageErrorStream() throws Exception {
testCorruptMessageError(session -> {
StreamMessage message = session.createStreamMessage();
message.writeBytes(UUID.randomUUID().toString().getBytes());
return message;
}, false);
}

private void testCorruptMessageError(MessageCreator messageCreator, boolean topic) throws Exception {
// Raw Transformer doesn't expand the body
assumeFalse(!transformer.equals("jms"));

Connection openwire = createJMSConnection();
Connection amqp = createConnection();
try {
openwire.start();
amqp.start();
Session openwireSession = openwire.createSession(false, Session.AUTO_ACKNOWLEDGE);
Session amqpSession = amqp.createSession(false, Session.AUTO_ACKNOWLEDGE);

ActiveMQDestination dest = topic ?
(ActiveMQDestination) openwireSession.createTopic(getDestinationName()) :
(ActiveMQDestination) openwireSession.createQueue(getDestinationName());
MessageProducer openwireProducer = openwireSession.createProducer(dest);
MessageConsumer amqpConsumer = topic ?
amqpSession.createDurableSubscriber((Topic) dest, "sub") :
amqpSession.createConsumer(dest);

// Create and send the Message
ActiveMQMessage outgoing = (ActiveMQMessage) messageCreator.create(openwireSession);
outgoing.storeContentAndClear();

// corrupt the buffer
// might be null if we are testing headers only
if (outgoing.getContent() != null && outgoing.getContent().length > 0) {
ByteSequenceData.writeIntBig(outgoing.getContent(), 1000);
}

openwireProducer.send(outgoing);

// Now try to consume the Message, should not be received
Message received = amqpConsumer.receive(2000);
assertNull(received);

// verify message is gone off the dest and went to the DLQ
assertTrue(Wait.waitFor(() -> brokerService.getDestination(dest)
.getDestinationStatistics().getMessages().getCount() == 0, 500, 10));
assertTrue(sentToDlq.get());
} finally {
amqp.close();
openwire.close();
}
}

private interface MessageCreator {
Message create(Session session) throws JMSException;
}

@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@
import org.apache.activemq.transport.TransmitCallback;
import org.apache.activemq.transport.Transport;
import org.apache.activemq.transport.TransportDisposedIOException;
import org.apache.activemq.util.ExceptionUtils;
import org.apache.activemq.ActiveMQMessageFormatException;
import org.apache.activemq.util.IntrospectionSupport;
import org.apache.activemq.util.MarshallingSupport;
import org.apache.activemq.util.NetworkBridgeUtils;
Expand Down Expand Up @@ -995,6 +997,18 @@ protected void processDispatch(Command command) throws IOException {
if (sub != null) {
sub.onFailure();
}
// Check if this is a type of message format error which indicates the
// message was corrupt and there was some problem unmarshaling. For these
// errors we can handle by acking with a poison ack (which will send to the DLQ
// if durable/queue sub) and remove them from the consumer so the consumer can
// continue. We do not want to throw the exception as that would close the connection.
ActiveMQMessageFormatException marshallingError = ExceptionUtils.createMessageFormatException(e);
if (marshallingError != null) {
handleMessageFormatError(marshallingError, messageDispatch);
// must set to null so when we return the finally block is skipped
messageDispatch = null;
return;
}
messageDispatch = null;
throw e;
} else {
Expand All @@ -1014,6 +1028,34 @@ protected void processDispatch(Command command) throws IOException {
}
}

private void handleMessageFormatError(ActiveMQMessageFormatException e, MessageDispatch messageDispatch) {
if (TRANSPORTLOG.isDebugEnabled()) {
TRANSPORTLOG.debug("{} had an unexpected Message format error: {}", this, e.getMessage(), e);
} else if (TRANSPORTLOG.isWarnEnabled()) {
TRANSPORTLOG.warn("{} had an unexpected Message format error: {}", this, e.getMessage());
}

ConsumerBrokerExchange consumerExchange = getConsumerBrokerExchange(messageDispatch.getConsumerId());
try {
// acknowledge with the consumer exchange for this dispatch
// This should exist because this error happened during dispatch, but if for some
// reason it is null it should get handled when delivery is attempted again
if (consumerExchange != null) {
MessageAck ack = new MessageAck();
// Acking with a poison ack will send to the DLQ
ack.setAckType(MessageAck.POSION_ACK_TYPE);
ack.setPoisonCause(e);
ack.setConsumerId(messageDispatch.getConsumerId());
ack.setDestination(messageDispatch.getDestination());
ack.setMessageID(messageDispatch.getMessage().getMessageId());
broker.acknowledge(consumerExchange, ack);
}
} catch (Exception ex) {
TRANSPORTLOG.warn("{} could not acknowledge and send message to the DLQ after"
+ " ActiveMQMessageFormatException: {}", this, e.getMessage());
}
}

@Override
public boolean iterate() {
try {
Expand Down Expand Up @@ -1390,7 +1432,14 @@ private NetworkBridgeConfiguration getNetworkConfiguration(final BrokerInfo info
}

@Override
public Response processBrokerInfo(BrokerInfo info) {
public Response processBrokerInfo(BrokerInfo info) throws IOException {
// We only expect to get at most one broker info command per connection
// Log and throw an IOException to close the connection if we receive more
// one because this is a protocol violation
if (this.brokerInfo != null) {
LOG.warn("Unexpected extra broker info command received: {}", info);
throw new IOException("Unexpected extra broker info command received from: " + info.getBrokerId());
}
if (info.isSlaveBroker()) {
LOG.error(" Slave Brokers are no longer supported - slave trying to attach is: {}", info.getBrokerName());
} else if (info.isNetworkConnection() && !info.isDuplexConnection()) {
Expand Down Expand Up @@ -1463,10 +1512,6 @@ public Response processBrokerInfo(BrokerInfo info) {
return null;
}
}
// We only expect to get one broker info command per connection
if (this.brokerInfo != null) {
LOG.warn("Unexpected extra broker info command received: {}", info);
}
this.brokerInfo = info;
networkConnection = true;
List<TransportConnectionState> connectionStates = listConnectionStates();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.apache.activemq.transport.TransportServer;
import org.apache.activemq.transport.discovery.DiscoveryAgent;
import org.apache.activemq.transport.discovery.DiscoveryAgentFactory;
import org.apache.activemq.util.ExceptionUtils;
import org.apache.activemq.util.ServiceStopper;
import org.apache.activemq.util.ServiceSupport;
import org.slf4j.Logger;
Expand Down Expand Up @@ -242,10 +243,10 @@ public void onAcceptError(Exception error) {

private void onAcceptError(Exception error, String remoteHost) {
if (brokerService != null && brokerService.isStopping()) {
LOG.info("Could not accept connection during shutdown {} : {} ({})", (remoteHost == null ? "" : "from " + remoteHost), error.getLocalizedMessage(), getRootCause(error).getMessage());
LOG.info("Could not accept connection during shutdown {} : {} ({})", (remoteHost == null ? "" : "from " + remoteHost), error.getLocalizedMessage(), ExceptionUtils.getRootCause(error).getMessage());
} else {
LOG.warn("Could not accept connection {}: {} ({})", (remoteHost == null ? "" : "from " + remoteHost), error.getMessage(), getRootCause(error).getMessage());
LOG.debug("Reason: " + error.getMessage(), error);
LOG.warn("Could not accept connection {}: {} ({})", (remoteHost == null ? "" : "from " + remoteHost), error.getMessage(), ExceptionUtils.getRootCause(error).getMessage());
LOG.debug("Reason: {}", error.getMessage(), error);
}
}
});
Expand All @@ -265,20 +266,6 @@ private void onAcceptError(Exception error, String remoteHost) {
LOG.info("Connector {} started", getName());
}

static Throwable getRootCause(final Throwable throwable) {
final List<Throwable> list = getThrowableList(throwable);
return list.isEmpty() ? null : list.get(list.size() - 1);
}

static List<Throwable> getThrowableList(Throwable throwable) {
final List<Throwable> list = new ArrayList<>();
while (throwable != null && !list.contains(throwable)) {
list.add(throwable);
throwable = throwable.getCause();
}
return list;
}

public String getPublishableConnectString() throws Exception {
String publishableConnectString = publishedAddressPolicy.getPublishableConnectString(this);
LOG.debug("Publishing: {} for broker transport URI: {}", publishableConnectString, getConnectUri());
Expand Down
Loading