This is an automated email from the ASF dual-hosted git repository.
acosentino pushed a commit to branch camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.18.x by this push:
new 036b416f2498 CAMEL-23297: Add deserialization filtering to camel-netty
converters and codecs (#22490)
036b416f2498 is described below
commit 036b416f2498a371a1f9ec2cb7589db06f4430a1
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Apr 8 20:32:50 2026 +0200
CAMEL-23297: Add deserialization filtering to camel-netty converters and
codecs (#22490)
Backport to camel-4.18.x. Adds ObjectInputFilter support to camel-netty's
deserialization paths to restrict which classes can be instantiated during
Java deserialization of network data:
- NettyConverter: applies default filter (java.**, javax.**,
org.apache.camel.**)
- ObjectDecoder: reimplemented with ObjectInputFilter support
- DatagramPacketObjectDecoder: new constructor accepting
deserializationFilter
Closes #22497
---
.../camel/component/netty/NettyConverter.java | 21 +++-
.../netty/codec/DatagramPacketObjectDecoder.java | 6 +-
.../camel/component/netty/codec/ObjectDecoder.java | 106 ++++++++++++++++++++-
3 files changed, 127 insertions(+), 6 deletions(-)
diff --git
a/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyConverter.java
b/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyConverter.java
index 6b1c54b4041e..b06cb7addc08 100644
---
a/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyConverter.java
+++
b/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyConverter.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.netty;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
+import java.io.ObjectInputFilter;
import java.io.ObjectInputStream;
import java.nio.charset.StandardCharsets;
@@ -34,12 +35,21 @@ import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufInputStream;
import org.apache.camel.Converter;
import org.apache.camel.Exchange;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* A set of converter methods for working with Netty types
*/
@Converter(generateLoader = true)
public final class NettyConverter {
+ private static final Logger LOG =
LoggerFactory.getLogger(NettyConverter.class);
+
+ /**
+ * Default deserialization filter that restricts which classes can be
deserialized. Allows standard Java types and
+ * Apache Camel types. Can be overridden via the JVM system property
{@code jdk.serialFilter}.
+ */
+ static final String DEFAULT_DESERIALIZATION_FILTER =
"java.**;javax.**;org.apache.camel.**;!*";
private NettyConverter() {
//Utility Class
@@ -79,7 +89,16 @@ public final class NettyConverter {
@Converter
public static ObjectInput toObjectInput(ByteBuf buffer, Exchange exchange)
throws IOException {
InputStream is = toInputStream(buffer, exchange);
- return new ObjectInputStream(is);
+ ObjectInputStream ois = new ObjectInputStream(is);
+ ObjectInputFilter jvmFilter =
ObjectInputFilter.Config.getSerialFilter();
+ if (jvmFilter != null) {
+ ois.setObjectInputFilter(jvmFilter);
+ } else {
+ LOG.debug("No JVM-wide deserialization filter set, applying
default Camel filter: {}",
+ DEFAULT_DESERIALIZATION_FILTER);
+
ois.setObjectInputFilter(ObjectInputFilter.Config.createFilter(DEFAULT_DESERIALIZATION_FILTER));
+ }
+ return ois;
}
@Converter
diff --git
a/components/camel-netty/src/main/java/org/apache/camel/component/netty/codec/DatagramPacketObjectDecoder.java
b/components/camel-netty/src/main/java/org/apache/camel/component/netty/codec/DatagramPacketObjectDecoder.java
index 20a205b6ea20..47b7f5f6f532 100644
---
a/components/camel-netty/src/main/java/org/apache/camel/component/netty/codec/DatagramPacketObjectDecoder.java
+++
b/components/camel-netty/src/main/java/org/apache/camel/component/netty/codec/DatagramPacketObjectDecoder.java
@@ -31,7 +31,11 @@ public class DatagramPacketObjectDecoder extends
MessageToMessageDecoder<Address
private final ObjectDecoder delegateDecoder;
public DatagramPacketObjectDecoder(ClassResolver resolver) {
- delegateDecoder = new ObjectDecoder(resolver);
+ this(resolver, null);
+ }
+
+ public DatagramPacketObjectDecoder(ClassResolver resolver, String
deserializationFilter) {
+ delegateDecoder = new ObjectDecoder(resolver, deserializationFilter);
}
@Override
diff --git
a/components/camel-netty/src/main/java/org/apache/camel/component/netty/codec/ObjectDecoder.java
b/components/camel-netty/src/main/java/org/apache/camel/component/netty/codec/ObjectDecoder.java
index 0a1aca75c01e..c1508fe8cd90 100644
---
a/components/camel-netty/src/main/java/org/apache/camel/component/netty/codec/ObjectDecoder.java
+++
b/components/camel-netty/src/main/java/org/apache/camel/component/netty/codec/ObjectDecoder.java
@@ -16,22 +16,120 @@
*/
package org.apache.camel.component.netty.codec;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputFilter;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.io.StreamCorruptedException;
+
import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufInputStream;
import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.serialization.ClassResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
- * Just expose the decode method for DatagramPacketObjectDecoder to use
+ * Decodes Java-serialized objects from Netty frames with optional {@link
ObjectInputFilter} support.
+ * <p>
+ * Compatible with Netty's {@code ObjectEncoder} compact wire format. When a
{@code deserializationFilter} is provided,
+ * only classes matching the filter pattern will be allowed during
deserialization.
*/
-public class ObjectDecoder extends
io.netty.handler.codec.serialization.ObjectDecoder {
+public class ObjectDecoder extends LengthFieldBasedFrameDecoder {
+ private static final Logger LOG =
LoggerFactory.getLogger(ObjectDecoder.class);
+ private static final int DEFAULT_MAX_OBJECT_SIZE = 1048576;
+
+ // Matches Netty's CompactObjectOutputStream type constants
+ private static final int TYPE_FAT_DESCRIPTOR = 0;
+ private static final int TYPE_THIN_DESCRIPTOR = 1;
+
+ private final ClassResolver classResolver;
+ private final String deserializationFilter;
public ObjectDecoder(ClassResolver classResolver) {
- super(classResolver);
+ this(classResolver, null);
+ }
+
+ public ObjectDecoder(ClassResolver classResolver, String
deserializationFilter) {
+ super(DEFAULT_MAX_OBJECT_SIZE, 0, 4, 0, 4);
+ this.classResolver = classResolver;
+ this.deserializationFilter = deserializationFilter;
+ if (deserializationFilter == null) {
+ LOG.warn("ObjectDecoder created without a deserialization filter."
+ + " Unrestricted deserialization of network data is a
security risk."
+ + " Consider setting a deserializationFilter to restrict
allowed classes.");
+ }
}
@Override
public Object decode(ChannelHandlerContext ctx, ByteBuf in) throws
Exception {
- return super.decode(ctx, in);
+ ByteBuf frame = (ByteBuf) super.decode(ctx, in);
+ if (frame == null) {
+ return null;
+ }
+ ObjectInputStream ois = new CompactFilteringObjectInputStream(
+ new ByteBufInputStream(frame, true), classResolver);
+ if (deserializationFilter != null) {
+
ois.setObjectInputFilter(ObjectInputFilter.Config.createFilter(deserializationFilter));
+ }
+ try {
+ return ois.readObject();
+ } finally {
+ ois.close();
+ }
}
+ /**
+ * ObjectInputStream that understands Netty's compact class descriptor
wire format (compatible with
+ * {@code CompactObjectOutputStream}) and resolves classes via a Netty
{@link ClassResolver}.
+ */
+ private static class CompactFilteringObjectInputStream extends
ObjectInputStream {
+ private final ClassResolver classResolver;
+
+ CompactFilteringObjectInputStream(InputStream in, ClassResolver
classResolver) throws IOException {
+ super(in);
+ this.classResolver = classResolver;
+ }
+
+ @Override
+ protected void readStreamHeader() throws IOException {
+ // Netty's CompactObjectOutputStream writes a single version byte
instead of the
+ // standard 4-byte Java stream header (ACED 0005)
+ int version = readByte();
+ if (version != 5) {
+ throw new StreamCorruptedException(
+ "Unsupported version: " + version);
+ }
+ }
+
+ @Override
+ protected ObjectStreamClass readClassDescriptor() throws IOException,
ClassNotFoundException {
+ int type = read();
+ if (type < 0) {
+ throw new EOFException();
+ }
+ switch (type) {
+ case TYPE_FAT_DESCRIPTOR:
+ return super.readClassDescriptor();
+ case TYPE_THIN_DESCRIPTOR:
+ String className = readUTF();
+ Class<?> clazz = classResolver.resolve(className);
+ return ObjectStreamClass.lookupAny(clazz);
+ default:
+ throw new StreamCorruptedException("Unexpected class
descriptor type: " + type);
+ }
+ }
+
+ @Override
+ protected Class<?> resolveClass(ObjectStreamClass desc) throws
IOException, ClassNotFoundException {
+ try {
+ return classResolver.resolve(desc.getName());
+ } catch (ClassNotFoundException e) {
+ return super.resolveClass(desc);
+ }
+ }
+ }
}