This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch coheigea/deflate-bomb in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 8af7486fd0f75f2751853eaae57d44a3f837ef0f Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Mon Aug 24 14:50:47 2026 +0100 Fix deflate bomb in compression utils --- .../apache/cxf/common/util/CompressionUtils.java | 15 ++++ .../cxf/common/util/CompressionUtilsTest.java | 85 ++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/core/src/main/java/org/apache/cxf/common/util/CompressionUtils.java b/core/src/main/java/org/apache/cxf/common/util/CompressionUtils.java index 85477f2a198..27e3f772fbe 100644 --- a/core/src/main/java/org/apache/cxf/common/util/CompressionUtils.java +++ b/core/src/main/java/org/apache/cxf/common/util/CompressionUtils.java @@ -26,6 +26,10 @@ import java.util.zip.Deflater; import java.util.zip.Inflater; public final class CompressionUtils { + // Guards against decompression-bomb style inputs that expand to an unbounded size on the heap + public static final long DEFAULT_MAX_INFLATED_SIZE = + SystemPropertyAction.getInteger("org.apache.cxf.compression-max-inflated-size", 10 * 1024 * 1024); + private CompressionUtils() { } @@ -34,12 +38,17 @@ public final class CompressionUtils { return inflate(deflatedToken, true); } public static InputStream inflate(byte[] deflatedToken, boolean nowrap) + throws DataFormatException { + return inflate(deflatedToken, nowrap, DEFAULT_MAX_INFLATED_SIZE); + } + public static InputStream inflate(byte[] deflatedToken, boolean nowrap, long maxInflatedSize) throws DataFormatException { Inflater inflater = new Inflater(nowrap); inflater.setInput(deflatedToken); byte[] buffer = new byte[deflatedToken.length]; int inflateLen; + long totalInflated = 0; ByteArrayOutputStream inflatedToken = new ByteArrayOutputStream(); while (!inflater.finished()) { inflateLen = inflater.inflate(buffer, 0, deflatedToken.length); @@ -50,6 +59,12 @@ public final class CompressionUtils { break; } + totalInflated += inflateLen; + if (totalInflated > maxInflatedSize) { + throw new DataFormatException("Inflated data exceeds the maximum allowed size of " + + maxInflatedSize + " bytes"); + } + inflatedToken.write(buffer, 0, inflateLen); } diff --git a/core/src/test/java/org/apache/cxf/common/util/CompressionUtilsTest.java b/core/src/test/java/org/apache/cxf/common/util/CompressionUtilsTest.java new file mode 100644 index 00000000000..2f36207e743 --- /dev/null +++ b/core/src/test/java/org/apache/cxf/common/util/CompressionUtilsTest.java @@ -0,0 +1,85 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cxf.common.util; + +import java.io.InputStream; +import java.util.Arrays; +import java.util.zip.DataFormatException; + +import org.apache.cxf.helpers.IOUtils; + +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class CompressionUtilsTest { + + @Test + public void testDeflateInflateRoundTrip() throws Exception { + byte[] original = "the quick brown fox jumps over the lazy dog".getBytes(); + byte[] deflated = CompressionUtils.deflate(original, true); + + InputStream inflated = CompressionUtils.inflate(deflated, true); + assertArrayEquals(original, IOUtils.readBytesFromStream(inflated, original.length)); + } + + @Test + public void testInflateWithinExplicitCapSucceeds() throws Exception { + byte[] original = new byte[1024]; + Arrays.fill(original, (byte) 'a'); + byte[] deflated = CompressionUtils.deflate(original, true); + + InputStream inflated = CompressionUtils.inflate(deflated, true, original.length); + assertArrayEquals(original, IOUtils.readBytesFromStream(inflated, original.length)); + } + + @Test + public void testInflateRejectsOutputExceedingExplicitCap() { + // Highly compressible payload: deflates to a tiny stream but expands well past the cap + byte[] original = new byte[1024 * 1024]; + Arrays.fill(original, (byte) 0); + byte[] deflated = CompressionUtils.deflate(original, true); + + try { + CompressionUtils.inflate(deflated, true, 1024); + fail("Expected a DataFormatException as the inflated size exceeds the cap"); + } catch (DataFormatException e) { + assertEquals("Inflated data exceeds the maximum allowed size of 1024 bytes", e.getMessage()); + } + } + + @Test + public void testDefaultInflateRejectsDecompressionBomb() { + // Simulate a decompression bomb: a large run of zeros compresses to a very small stream + byte[] original = new byte[(int) CompressionUtils.DEFAULT_MAX_INFLATED_SIZE + (1024 * 1024)]; + Arrays.fill(original, (byte) 0); + byte[] deflated = CompressionUtils.deflate(original, true); + + try { + CompressionUtils.inflate(deflated, true); + fail("Expected a DataFormatException as the inflated size exceeds the default cap"); + } catch (DataFormatException e) { + assertEquals("Inflated data exceeds the maximum allowed size of " + + CompressionUtils.DEFAULT_MAX_INFLATED_SIZE + " bytes", e.getMessage()); + } + } +}
