From 00c56b0f6240b05bdfcf069902d071fea8cdd45e Mon Sep 17 00:00:00 2001 From: "valery.bokov" Date: Sun, 19 Jul 2026 15:20:39 +0200 Subject: [PATCH 1/2] fix method CCITTFactory.readlong --- .../pdfbox/pdmodel/graphics/image/CCITTFactory.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java index b6c37c11a04..bbb020f257f 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java @@ -303,7 +303,7 @@ private static void extractFromTiff(RandomAccessRead reader, { int tag = readshort(endianess, reader); int type = readshort(endianess, reader); - int count = readlong(endianess, reader); + int count = (int) readlong(endianess, reader); int val; // Note that when the type is shorter than 4 bytes, the rest can be garbage // and must be ignored. E.g. short (2 bytes) from "01 00 38 32" (little endian) @@ -322,7 +322,7 @@ private static void extractFromTiff(RandomAccessRead reader, reader.read(); break; default: // long and other types - val = readlong(endianess, reader); + val = (int) readlong(endianess, reader); break; } switch (tag) @@ -472,13 +472,14 @@ private static int readshort(char endianess, RandomAccessRead raf) throws IOExce return (raf.read() << 8) | raf.read(); } - private static int readlong(char endianess, RandomAccessRead raf) throws IOException + private static long readlong(char endianess, RandomAccessRead raf) throws IOException { + // TIFF LONG is an unsigned 32-bit value; mask so it widens correctly if (endianess == 'I') { - return raf.read() | (raf.read() << 8) | (raf.read() << 16) | (raf.read() << 24); + return (raf.read() | (raf.read() << 8) | (raf.read() << 16) | (raf.read() << 24)) & 0xFFFFFFFFL; } - return (raf.read() << 24) | (raf.read() << 16) | (raf.read() << 8) | raf.read(); + return ((raf.read() << 24) | (raf.read() << 16) | (raf.read() << 8) | raf.read()) & 0xFFFFFFFFL; } private static final byte[] fliptable = new byte[] From 2992347ffe2a852a87eb0ac1f6494cfaa12c0e7a Mon Sep 17 00:00:00 2001 From: "valery.bokov" Date: Sun, 19 Jul 2026 15:21:11 +0200 Subject: [PATCH 2/2] add CCITTFactoryTest.testReadLongIsUnsigned --- .../graphics/image/CCITTFactoryTest.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java b/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java index a70dfc4e810..4ec33db4bc4 100644 --- a/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java +++ b/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; @@ -34,6 +35,8 @@ import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.Loader; +import org.apache.pdfbox.io.RandomAccessRead; +import org.apache.pdfbox.io.RandomAccessReadBuffer; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.PDPageContentStream; @@ -316,4 +319,44 @@ void testFillOrder2() throws IOException, URISyntaxException assertEquals(1, document.getNumberOfPages()); } } + + /** + * Tests that CCITTFactory's private readlong() reads a TIFF LONG as an unsigned 32-bit + * value. The previous implementation returned a (possibly negative) int, which was then + * sign-extended when widened to long, corrupting IFD offsets/counts whose high bit is set + * (e.g. 0x80000000 and above). + */ + @Test + void testReadLongIsUnsigned() throws Exception + { + Method readLongMethod = + CCITTFactory.class.getDeclaredMethod("readlong", char.class, RandomAccessRead.class); + readLongMethod.setAccessible(true); + + // all bits set: 0xFFFFFFFF == 4294967295 as an unsigned TIFF LONG. + // The buggy code returned the int -1, which as a long is -1, not 4294967295. + byte[] allOnes = { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }; + assertReadLongUnsigned(readLongMethod, 'I', allOnes, 0xFFFFFFFFL); + assertReadLongUnsigned(readLongMethod, 'M', allOnes, 0xFFFFFFFFL); + + // only the top bit set, in each byte order: 0x80000000 == 2147483648 unsigned. + // The buggy code returned the int Integer.MIN_VALUE, which sign-extends to a + // large negative long instead of 2147483648. + byte[] littleEndianTopBit = { 0x00, 0x00, 0x00, (byte) 0x80 }; + assertReadLongUnsigned(readLongMethod, 'I', littleEndianTopBit, 0x80000000L); + + byte[] bigEndianTopBit = { (byte) 0x80, 0x00, 0x00, 0x00 }; + assertReadLongUnsigned(readLongMethod, 'M', bigEndianTopBit, 0x80000000L); + } + + private static void assertReadLongUnsigned(Method readLongMethod, char endianess, byte[] bytes, + long expected) throws Exception + { + try (RandomAccessRead raf = new RandomAccessReadBuffer(bytes)) + { + long value = (long) readLongMethod.invoke(null, endianess, raf); + assertEquals(expected, value); + assertTrue(value >= 0, "TIFF LONG must be read as unsigned, not sign-extended"); + } + } }