Description:  CVE-2020-28491

--- a/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java
+++ b/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java
@@ -65,6 +65,10 @@ public final class CBORParser extends Pa
     private final static double MATH_POW_2_10 = Math.pow(2, 10);
     private final static double MATH_POW_2_NEG14 = Math.pow(2, -14);
     
+    // 2.11.4: [dataformats-binary#186] Avoid OOME/DoS for bigger binary;
+    //  read only up to 250k
+    protected final static int LONGEST_NON_CHUNKED_BINARY = 250000;
+
     /*
     /**********************************************************
     /* Configuration
@@ -1376,13 +1380,15 @@ public final class CBORParser extends Pa
         }
     }
 
-    private int _readAndWriteBytes(OutputStream out, int total) throws IOException
+    private int _readAndWriteBytes(OutputStream out, final int total) throws IOException
     {
         int left = total;
         while (left > 0) {
             int avail = _inputEnd - _inputPtr;
             if (_inputPtr >= _inputEnd) {
-                loadMoreGuaranteed();
+                 if (!loadMore()) {
+                    _reportIncompleteBinaryRead(total, total-left);
+                }
                 avail = _inputEnd - _inputPtr;
             }
             int count = Math.min(avail, left);
@@ -2087,32 +2093,54 @@ public final class CBORParser extends Pa
         // either way, got it now
         return _inputBuffer[_inputPtr++];
     }
-    
+    /**
+     * Helper called to complete reading of binary data ("byte string") in
+     * case contents are needed.
+     */
     @SuppressWarnings("resource")
     protected byte[] _finishBytes(int len) throws IOException
     {
+        // Chunked?
         // First, simple: non-chunked
-        if (len >= 0) {
+        if (len <= 0) {
             if (len == 0) {
                 return NO_BYTES;
             }
-            byte[] b = new byte[len];
-            if (_inputPtr >= _inputEnd) {
-                loadMoreGuaranteed();
+            return _finishChunkedBytes();
             }
-            int ptr = 0;
-            while (true) {
-                int toAdd = Math.min(len, _inputEnd - _inputPtr);
-                System.arraycopy(_inputBuffer, _inputPtr, b, ptr, toAdd);
-                _inputPtr += toAdd;
-                ptr += toAdd;
-                len -= toAdd;
-                if (len <= 0) {
-                    return b;
-                }
-                loadMoreGuaranteed();
+        // Non-chunked, contiguous
+        if (len > LONGEST_NON_CHUNKED_BINARY) {
+            // [dataformats-binary#186]: avoid immediate allocation for longest
+            return _finishLongContiguousBytes(len);
+        }
+
+        final byte[] b = new byte[len];
+        final int expLen = len;
+        if (_inputPtr >= _inputEnd) {
+            if (!loadMore()) {
+                _reportIncompleteBinaryRead(expLen, 0);
+            }
+         }
+
+        int ptr = 0;
+        while (true) {
+            int toAdd = Math.min(len, _inputEnd - _inputPtr);
+            System.arraycopy(_inputBuffer, _inputPtr, b, ptr, toAdd);
+            _inputPtr += toAdd;
+            ptr += toAdd;
+            len -= toAdd;
+            if (len <= 0) {
+                return b;
+            }
+            if (!loadMore()) {
+                _reportIncompleteBinaryRead(expLen, ptr);
             }
         }
+     }
+
+    // @since 2.12
+    protected byte[] _finishChunkedBytes() throws IOException
+    {
 
         // or, if not, chunked...
         ByteArrayBuilder bb = _getByteArrayBuilder();
@@ -2130,14 +2158,17 @@ public final class CBORParser extends Pa
                 throw _constructError("Mismatched chunk in chunked content: expected "+CBORConstants.MAJOR_TYPE_BYTES
                         +" but encountered "+type);
             }
-            len = _decodeExplicitLength(ch & 0x1F);
+            int len = _decodeExplicitLength(ch & 0x1F);
             if (len < 0) {
                 throw _constructError("Illegal chunked-length indicator within chunked-length value (type "+CBORConstants.MAJOR_TYPE_BYTES+")");
             }
+            final int chunkLen = len;
             while (len > 0) {
                 int avail = _inputEnd - _inputPtr;
                 if (_inputPtr >= _inputEnd) {
-                    loadMoreGuaranteed();
+                     if (!loadMore()) {
+                        _reportIncompleteBinaryRead(chunkLen, chunkLen-len);
+                    }
                     avail = _inputEnd - _inputPtr;
                 }
                 int count = Math.min(avail, len);
@@ -2149,6 +2180,35 @@ public final class CBORParser extends Pa
         return bb.toByteArray();
     }
     
+     // @since 2.12
+    protected byte[] _finishLongContiguousBytes(final int expLen) throws IOException
+    {
+        int left = expLen;
+
+        // 04-Dec-2020, tatu: Let's NOT use recycled instance since we have much
+        //   longer content and there is likely less benefit of trying to recycle
+        //   segments
+            try{
+            final ByteArrayBuilder bb = new ByteArrayBuilder(LONGEST_NON_CHUNKED_BINARY >> 1);
+            while (left > 0) {
+                int avail = _inputEnd - _inputPtr;
+                if (avail <= 0) {
+                    if (!loadMore()) {
+                        _reportIncompleteBinaryRead(expLen, expLen-left);
+                    }
+                    avail = _inputEnd - _inputPtr;
+                }
+                int count = Math.min(avail, left);
+                bb.write(_inputBuffer, _inputPtr, count);
+                _inputPtr += count;
+                left -= count;        
+            }
+            return bb.toByteArray();
+       } catch(Exception e) {
+         throw new IllegalStateException(e);
+        }
+    }
+
     protected final JsonToken _decodeFieldName() throws IOException
     {     
         if (_inputPtr >= _inputEnd) {
@@ -2297,9 +2357,8 @@ public final class CBORParser extends Pa
         } else if (type == CBORConstants.MAJOR_TYPE_INT_NEG) {
             name = _numberToName(ch, true);
         } else if (type == CBORConstants.MAJOR_TYPE_BYTES) {
-            /* 08-Sep-2014, tatu: As per [Issue#5], there are codecs
-             *   (f.ex. Perl module "CBOR::XS") that use Binary data...
-             */
+           // 08-Sep-2014, tatu: As per [Issue#5], there are codecs
+            //   (f.ex. Perl module "CBOR::XS") that use Binary data...
             final int blen = _decodeExplicitLength(ch & 0x1F);
             byte[] b = _finishBytes(blen);
             // TODO: Optimize, if this becomes commonly used & bottleneck; we have
@@ -2852,7 +2911,7 @@ public final class CBORParser extends Pa
     /**********************************************************
      */
 
-    protected final boolean loadMore() throws IOException
+    protected boolean loadMore() throws IOException
     {
         if (_inputStream != null) {
             _currInputProcessed += _inputEnd;
@@ -2873,7 +2932,7 @@ public final class CBORParser extends Pa
         return false;
     }
 
-    protected final void loadMoreGuaranteed() throws IOException {
+    protected void loadMoreGuaranteed() throws IOException {
         if (!loadMore()) { _reportInvalidEOF(); }
     }
     
@@ -2973,5 +3032,12 @@ public final class CBORParser extends Pa
         _inputPtr = ptr;
         _reportInvalidOther(mask);
     }
+    // @since 2.12
+    protected void _reportIncompleteBinaryRead(int expLen, int actLen) throws IOException
+    {
+        _reportInvalidEOF(String.format(" for Binary value: expected %d bytes, only found %d",
+                expLen, actLen), _currToken);
+    }
+
 }
     
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/dataformat/cbor/BrokenLongBinary186Test.java
@@ -0,0 +1,95 @@
+package com.fasterxml.jackson.dataformat.cbor;
+
+import java.io.ByteArrayOutputStream;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+// Mostly for [dataformats-binary#186]: corrupt encoding indicating humongous payload
+public class BrokenLongBinary186Test extends CBORTestBase
+{
+    private final ObjectMapper MAPPER = cborMapper();
+    final CBORFactory f = new CBORFactory();
+
+    /*
+    /**********************************************************************
+    /* First regular, read-it-all access, from non-chunked
+    /**********************************************************************
+     */
+
+    // [dataformats-binary#186]
+    public void testCorruptVeryLongBinary() throws Exception {
+        // Let's do about 2 GB to likely trigger failure
+        _testCorruptLong(1999999999, 95000);
+    }
+
+    // [dataformats-binary#186]
+    public void testCorruptQuiteLongBinary() throws Exception {
+        // Value below limit for chunked handling
+        _testCorruptLong(CBORParser.LONGEST_NON_CHUNKED_BINARY >> 1, 37);
+    }
+
+    private void _testCorruptLong(int allegedLength, int actualIncluded) throws Exception
+    {
+        JsonParser p = f.createParser(_createBrokenDoc(allegedLength, actualIncluded));
+        assertEquals(JsonToken.VALUE_EMBEDDED_OBJECT, p.nextToken());
+        try {
+            p.getBinaryValue();
+            fail("Should fail");
+        } catch (JsonProcessingException e) {
+            verifyException(e, "Unexpected end-of-input for Binary value");
+            verifyException(e, "expected "+allegedLength+" bytes, only found "+actualIncluded);
+        }
+    }
+
+    /*
+    /**********************************************************************
+    /* And then "streaming" access
+    /**********************************************************************
+     */
+
+    // [dataformats-binary#186]
+    public void testQuiteLongStreaming() throws Exception
+    {
+        final CBORFactory f = new CBORFactory();
+        // Can try bit shorter here, like 500 megs
+        final int allegedLength = 500000000;
+
+        JsonParser p = f.createParser(_createBrokenDoc(allegedLength, 72000));
+        assertEquals(JsonToken.VALUE_EMBEDDED_OBJECT, p.nextToken());
+        try {
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            p.readBinaryValue(bytes);
+            fail("Should fail");
+        } catch (JsonProcessingException e) {
+            verifyException(e, "Unexpected end-of-input for Binary value");
+            verifyException(e, "expected "+allegedLength+" bytes, only found 72000");
+        }
+    }
+
+    private byte[] _createBrokenDoc(int allegedLength, int actualIncluded) throws Exception
+    {
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+
+        if (allegedLength > 0xFFFF) {
+            bytes.write((byte) (CBORConstants.PREFIX_TYPE_BYTES | CBORConstants.SUFFIX_UINT32_ELEMENTS));
+            bytes.write((byte) (allegedLength >> 24));
+            bytes.write((byte) (allegedLength >> 16));
+            bytes.write((byte) (allegedLength >> 8));
+            bytes.write((byte) allegedLength);
+        } else { // assume shorter
+            bytes.write((byte) (CBORConstants.PREFIX_TYPE_BYTES | CBORConstants.SUFFIX_UINT16_ELEMENTS));
+            bytes.write((byte) (allegedLength >> 8));
+            bytes.write((byte) allegedLength);
+        }
+        // but only include couple of bytes
+        for (int i = 0; i < actualIncluded; ++i) {
+            bytes.write((byte) i);
+        }
+        return bytes.toByteArray();
+    }
+
+}
\ No newline at end of file
