This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch backport/22490-to-camel-4.18.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit 7b0861c83ffbdb0d7e7f9c449f695b655231ef72 Author: Andrea Cosentino <[email protected]> AuthorDate: Wed Apr 8 14:58:31 2026 +0200 CAMEL-23297: Add deserialization filtering to camel-netty converters and codecs (#22490) - NettyConverter.toObjectInput(): Apply default ObjectInputFilter restricting to java.**, javax.**, org.apache.camel.** (respects JVM-wide filter if set) - ObjectDecoder: Reimplement with ObjectInputFilter support, compatible with Netty's CompactObjectOutputStream wire format. Accepts optional deserializationFilter parameter. Logs warning when no filter is configured. - DatagramPacketObjectDecoder: Add constructor accepting deserializationFilter, passed through to ObjectDecoder. Signed-off-by: Andrea Cosentino <[email protected]> Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> --- .../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); + } + } + } }
