From def2272c9ac8f8d69908f799be377d0107f56294 Mon Sep 17 00:00:00 2001 From: Halil Sener Date: Tue, 7 Apr 2026 16:09:24 +0200 Subject: [PATCH 1/3] Fix `_finishLongTextAscii` returning negative remaining length `return len - outPtr` was wrong when `outPtr` accumulated across multiple outer-loop iterations: `len` had already been decremented for previous iterations, so subtracting the full `outPtr` produced a negative value, causing `_finishLongText` to skip decoding the non-ASCII byte. Fix: use `return len - _inputPtr`, which holds only the bytes consumed in the current iteration. --- .../jackson/dataformat/cbor/CBORParser.java | 5 +- .../parse/CBORFinishLongTextAsciiBugTest.java | 81 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/CBORFinishLongTextAsciiBugTest.java diff --git a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java index 7af94d27c..3f96da091 100644 --- a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java +++ b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java @@ -2646,7 +2646,10 @@ private final int _finishLongTextAscii(int len) throws IOException --outPtr; _inputPtr = inPtr - 1; _textBuffer.setCurrentLength(outPtr); - return len - outPtr; + // `len` was already decremented for all previous iterations; subtract only + // the bytes consumed in THIS iteration (= _inputPtr, since _tryToLoadToHaveAtLeast + // always resets _inputPtr to 0 before the inner loop). + return len - _inputPtr; } _inputPtr = inPtr; if (outPtr >= outBuf.length) { diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/CBORFinishLongTextAsciiBugTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/CBORFinishLongTextAsciiBugTest.java new file mode 100644 index 000000000..f00597c9c --- /dev/null +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/CBORFinishLongTextAsciiBugTest.java @@ -0,0 +1,81 @@ +package com.fasterxml.jackson.dataformat.cbor.parse; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.cbor.CBORFactory; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +// For [dataformats-binary#686] +public class CBORFinishLongTextAsciiBugTest +{ + // TextBuffer segment sizes grow 1.5× per flip, starting at 200 chars (MIN=500): + // S0=200, S1=500, S2=750, S3=1125, S4=1687, S5=2530, S6=3795, S7=5692, S8=8538 + // After 8 flips the segment is 8538 chars — the first one larger than the 8000-byte I/O buffer. + // Total chars consumed reaching S8: 200+500+750+1125+1687+2530+3795+5692 = 16279. + private static final int CHARS_TO_REACH_S8 = 16279; + private static final int IO_BUFFER_SIZE = 8000; + + /** + * Parses a definite-length CBOR map {a: <16279 'a' bytes>, b: <8000 'a' bytes + 0xB7>, n: "x"} + * via InputStream and expects all tokens to be read without error. + *

+ * Actual result on affected versions: JsonParseException "Unsupported major type (5)" + */ + @Test + public void testFinishLongTextAsciiDoesNotLeaveNonAsciiByte() + { + byte[] strA = new byte[CHARS_TO_REACH_S8]; + Arrays.fill(strA, (byte) 'a'); + + // 0xC2 0xB7 = U+00B7 "·" (middle dot): a valid 2-byte UTF-8 sequence. + // 0xC2 is non-ASCII so _finishLongTextAscii exits, but it must leave len non-negative + // so that _finishLongText's while loop can still decode the sequence. + byte[] strB = new byte[IO_BUFFER_SIZE + 2]; + Arrays.fill(strB, (byte) 'a'); + strB[IO_BUFFER_SIZE] = (byte) 0xC2; + strB[IO_BUFFER_SIZE + 1] = (byte) 0xB7; + + assertDoesNotThrow(() -> + new ObjectMapper(new CBORFactory()) + .readValue(new ByteArrayInputStream(buildMap(strA, strB)), Object.class)); + } + + /** + * definite-length map(3): {a: strA, b: strB, n: "x"} + */ + private static byte[] buildMap(byte[] strA, byte[] strB) throws IOException + { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bos.write(0xa3); + writeText(bos, new byte[]{'a'}); + writeText(bos, strA); + writeText(bos, new byte[]{'b'}); + writeText(bos, strB); + writeText(bos, new byte[]{'n'}); + writeText(bos, new byte[]{'x'}); + return bos.toByteArray(); + } + + private static void writeText(ByteArrayOutputStream bos, byte[] bytes) throws IOException + { + int n = bytes.length; + if (n <= 23) { + bos.write(0x60 | n); + } else if (n <= 0xFF) { + bos.write(0x78); + bos.write(n); + } else if (n <= 0xFFFF) { + bos.write(0x79); + bos.write((n >> 8) & 0xFF); + bos.write(n & 0xFF); + } + bos.write(bytes); + } +} From 819d6c603f773e639676b30aeeaa8aca685c3b00 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 8 Apr 2026 20:14:23 -0700 Subject: [PATCH 2/3] Add release notes --- .../com/fasterxml/jackson/dataformat/cbor/CBORParser.java | 7 ++++--- ...TextAsciiBugTest.java => CBORLongAsciiRead686Test.java} | 2 +- release-notes/CREDITS-2.x | 7 ++++++- release-notes/VERSION-2.x | 6 ++++++ 4 files changed, 17 insertions(+), 5 deletions(-) rename cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/{CBORFinishLongTextAsciiBugTest.java => CBORLongAsciiRead686Test.java} (98%) diff --git a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java index 3f96da091..533db5e5b 100644 --- a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java +++ b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java @@ -2620,7 +2620,8 @@ private final String _finishLongText(int len) throws IOException } /** - * Consumes as many ascii chars as possible in a tight loop. Returns the amount of bytes remaining. + * Consumes as many ascii chars as possible in a tight loop. + * Returns the amount of bytes remaining. */ private final int _finishLongTextAscii(int len) throws IOException { @@ -2647,8 +2648,8 @@ private final int _finishLongTextAscii(int len) throws IOException _inputPtr = inPtr - 1; _textBuffer.setCurrentLength(outPtr); // `len` was already decremented for all previous iterations; subtract only - // the bytes consumed in THIS iteration (= _inputPtr, since _tryToLoadToHaveAtLeast - // always resets _inputPtr to 0 before the inner loop). + // the bytes consumed in THIS iteration (= _inputPtr), since + // _tryToLoadToHaveAtLeast always resets _inputPtr to 0 before the inner loop. return len - _inputPtr; } _inputPtr = inPtr; diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/CBORFinishLongTextAsciiBugTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/CBORLongAsciiRead686Test.java similarity index 98% rename from cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/CBORFinishLongTextAsciiBugTest.java rename to cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/CBORLongAsciiRead686Test.java index f00597c9c..f832f6ef8 100644 --- a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/CBORFinishLongTextAsciiBugTest.java +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/CBORLongAsciiRead686Test.java @@ -13,7 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; // For [dataformats-binary#686] -public class CBORFinishLongTextAsciiBugTest +public class CBORLongAsciiRead686Test { // TextBuffer segment sizes grow 1.5× per flip, starting at 200 chars (MIN=500): // S0=200, S1=500, S2=750, S3=1125, S4=1687, S5=2530, S6=3795, S7=5692, S8=8538 diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index f2e46c421..453530011 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -429,5 +429,10 @@ Vincent Eigenberger (@beseder1) (2.20.1) Yohei Kishimoto (@morokosi) - * Reported #599: (cbor) Unable to deserialize stringref-enabled CBOR with ignored properties + * Reported #599: (cbor) Unable to deserialize stringref-enabled CBOR with ignored propertie (2.21.0) + +Halil İbrahim Şener (@hisener) + * Fixed #686: `CBORParser._finishLongTextAscii` returns negative length when `TextBuffer` + segment > I/O buffer, leaving non-ASCII byte unconsumed + (2.21.3) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 153c5e080..2e46031d0 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -14,6 +14,12 @@ Active maintainers: === Releases === ------------------------------------------------------------------------ +2.21.3 (not yet released) + +#686: `CBORParser._finishLongTextAscii` returns negative length when `TextBuffer` + segment > I/O buffer, leaving non-ASCII byte unconsumed + (fixed by Halil İbrahim Ş) + 2.21.2 (20-Mar-2026) No changes since 2.21.1. From bf84e7162b6b9d86651493d52f35e3ed68c5e281 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 8 Apr 2026 20:17:46 -0700 Subject: [PATCH 3/3] Drop JDK 11 from CI --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fae6d21f5..5589cdc2e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - java_version: ['8', '11', '17', '21'] + java_version: ['8', '17', '21'] include: - java_version: '8' release_build: 'R'