CVE-2024-21909 (published 2024-01-03T16:15Z)

PeterO.Cbor versions 4.0.0 through 4.5.0 are vulnerable to a denial of \nservice vulnerability. An attacker may trigger the denial of service \ncondition by providing crafted data to the DecodeFromBytes or other \ndecoding mechanisms in PeterO.Cbor. Depending on the usage of the \nlibrary, an unauthenticated and remote attacker may be able to cause the\n denial of service condition.\n

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-407
cwe_name: Inefficient Algorithmic Complexity
description: An algorithm in a product has an inefficient worst-case computational complexity that may be detrimental to system performance and can be triggered by an attacker, typically using crafted manipulations that ensure that the worst case is being reached.
extended_description: 
            
         
         

Fix information:
repo_url: https://github.com/peteroupc/CBOR
commit_details: repo_url: https://github.com/peteroupc/CBOR
author: Peter Occil
author_date: 2022-01-18 04:56:22-05:00
msg: fix algorithmic complexity issue; add regression tests
repository: repo_url: https://github.com/peteroupc/CBOR
repo_name: peteroupc/CBOR
description: A C# implementation of Concise Binary Object Representation (RFC 8949).
repo_language: C#
owner: peteroupc
file_changes: filename: CBORObject.cs
change_type: ModificationType.MODIFY
diff: @@ -7942,12 +7942,8 @@ public sealed partial class CBORObject : IComparable<CBORObject>,
       if (listACount != listBCount) {
         return listACount < listBCount ? -1 : 1;
       }
-      var sortedASet = new List<CBORObject>(mapA.Keys);
-      var sortedBSet = new List<CBORObject>(mapB.Keys);
-      // DebugUtility.Log("---sorting mapA's keys");
-      sortedASet.Sort();
-      // DebugUtility.Log("---sorting mapB's keys");
-      sortedBSet.Sort();
+      var sortedASet = new List<CBORObject>(PropertyMap.GetSortedKeys(mapA));
+      var sortedBSet = new List<CBORObject>(PropertyMap.GetSortedKeys(mapB));
       // DebugUtility.Log("---done sorting");
       listACount = sortedASet.Count;
       listBCount = sortedBSet.Count;

programming_language: C#
filename: PropertyMap.cs
change_type: ModificationType.MODIFY
diff: @@ -35,7 +35,7 @@ private sealed class OrderedDictionary<TKey, TValue> :
       private readonly IDictionary<TKey, TValue> dict;
       private readonly LinkedList<TKey> list;
       public OrderedDictionary() {
-        this.dict = new Dictionary<TKey, TValue>();
+        this.dict = new SortedDictionary<TKey, TValue>();
         this.list = new LinkedList<TKey>();
       }
       public void Add(KeyValuePair<TKey, TValue> kvp) {
@@ -179,6 +179,12 @@ private sealed class OrderedDictionary<TKey, TValue> :
         }
       }
 
+      public ICollection<TKey> SortedKeys {
+        get {
+           return this.dict.Keys;
+        }
+      }
+
       public ICollection<TValue> Values {
         get {
           return new ValueWrapper<TKey, TValue>(this.dict, this.list);
@@ -895,6 +901,21 @@ private sealed class PropertyData {
           Convert.ToInt32(value, CultureInfo.InvariantCulture));
     }
 
+    public static ICollection<TKey>
+    GetSortedKeys<TKey, TValue>(
+      IDictionary<TKey, TValue> dict) {
+      var odict = dict as OrderedDictionary<TKey, TValue>;
+      if (odict != null) {
+        return odict.SortedKeys;
+      }
+      var sdict = dict as SortedDictionary<TKey, TValue>;
+      if (sdict != null) {
+        return sdict.Keys;
+      }
+      throw new InvalidOperationException("Internal error: Map doesn't" +
+"\u0020support sorted keys");
+    }
+
     public static ICollection<KeyValuePair<TKey, TValue>>
     GetEntries<TKey, TValue>(
       IDictionary<TKey, TValue> dict) {

programming_language: C#
filename: CBORGenerator.cs
change_type: ModificationType.MODIFY
diff: @@ -84,7 +84,13 @@ private sealed class ByteWriter {
     private static int[]
     valueMajorTypes = {
       0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4,
-      4, 5, 6, 6, 7, 7, 7, 7, 7, 7,
+      4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 7, 7, 7, 7, 7, 7,
+    };
+
+    private static int[]
+    valueMajorTypesHighDepth = {
+      0, 1, 2, 3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+      5, 5, 5, 5, 5, 5, 6, 7,
     };
 
     private static int[] valueMajorTypesHighLength = {
@@ -130,20 +136,50 @@ private sealed class ByteWriter {
       }
     }
 
+    private void GenerateSmall(IRandomGenExtended r, int depth, ByteWriter bs) {
+      int v = r.GetInt32(100);
+      if (v < 25) {
+        GenerateArgument(r, 0, r.GetInt32(100), bs);
+      } else if (v < 35) {
+        bs.Write(0x41);
+      bs.Write(0x20);
+      } else if (v < 45) {
+        bs.Write(0x41);
+      bs.Write(0x20);
+      } else if (v < 50) {
+        bs.Write(0x81);
+      this.GenerateSmall(r, depth + 1, bs);
+      } else if (v < 53) {
+        bs.Write(0xa2);
+      bs.Write(0xf7);
+    bs.Write(0xf6);
+  this.GenerateSmall(r, depth + 1, bs);
+  bs.Write(0xf5);
+      } else if (v < 80) {
+        bs.Write(r.GetInt32(0x40));
+      } else if (v < 100) {
+        bs.Write(r.GetInt32(0x60));
+      }
+    }
     private void Generate(IRandomGenExtended r, int depth, ByteWriter bs) {
       int majorType = valueMajorTypes[r.GetInt32(valueMajorTypes.Length)];
+      if (depth > 6) {
+        majorType = valueMajorTypesHighDepth[r.GetInt32(
+              valueMajorTypesHighDepth.Length)];
+      }
       if (bs.ByteLength > 2000000) {
         majorType = valueMajorTypesHighLength[r.GetInt32(
               valueMajorTypesHighLength.Length)];
       }
-      if (majorType == 3 || majorType == 2) {
+      if (majorType == 3 || majorType == 2) { // Byte and text strings
         int len = r.GetInt32(1000);
         if (r.GetInt32(50) == 0 && depth < 2) {
           var v = (long)r.GetInt32(100000) * r.GetInt32(100000);
           len = (int)(v / 100000);
-        }
-        if (depth > 6) {
+        } else if (depth > 6) {
           len = r.GetInt32(100) == 0 ? 1 : 0;
+        } else if (depth > 2) {
+          len = r.GetInt32(16) + 1;
         }
         // TODO: Ensure key uniqueness
         if (r.GetInt32(2) == 0) {
@@ -174,11 +210,18 @@ private sealed class ByteWriter {
           }
         }
         return;
-      } else if (majorType == 4 || majorType == 5) {
+      } else if (majorType == 4 || majorType == 5) { // Arrays and maps
         int len = r.GetInt32(8);
         if (r.GetInt32(50) == 0 && depth < 2) {
           var v = (long)r.GetInt32(1000) * r.GetInt32(1000);
           len = (int)(v / 1000);
+        } else if (depth > 6) {
+          len = r.GetInt32(100) == 0 ? 1 : 0;
+        } else if (depth > 2) {
+          len = r.GetInt32(3) + 1;
+        }
+        if (depth > 6) {
+          len = r.GetInt32(100) < 50 ? 1 : (r.GetInt32(100) < 10 ? 2 : 0);
         }
         bool indefiniteLength = r.GetInt32(2) == 0;
         if (indefiniteLength) {
@@ -187,7 +230,11 @@ private sealed class ByteWriter {
           GenerateArgument(r, majorType, len, bs);
         }
         for (int i = 0; i < len; ++i) {
-          this.Generate(r, depth + 1, bs);
+          if (depth > 6) {
+            this.GenerateSmall(r, depth + 1, bs);
+          } else {
+            this.Generate(r, depth + 1, bs);
+          }
           if (majorType == 5) {
             this.Generate(r, depth + 1, bs);
           }
@@ -229,7 +276,7 @@ private sealed class ByteWriter {
           }
           break;
       }
-      if (majorType == 6) {
+      if (majorType == 6) { // Tags
         this.Generate(r, depth + 1, bs);
       }
     }

programming_language: C#
filename: CBORObjectTest.cs
change_type: ModificationType.MODIFY
diff: @@ -9721,6 +9721,145 @@ public sealed class NestedPODClass {
       Assert.IsTrue(cbor.AsDoubleValue() == Double.NegativeInfinity);
     }
 
+[Test]
+public void TestRoundTripRegressions() {
+{
+var options = new CBOREncodeOptions("allowduplicatekeys=1;keepkeyorder=1");
+var bytes = new byte[] {
+  (byte)0xba, 0x00, 0x00, 0x00, 0x03,
+  (byte)0xf9,
+  (byte)0x83, 0x1d,
+  (byte)0xda,
+  (byte)0xb6,
+  (byte)0xda, 0x50, 0x56, 0x1a, 0x50,
+  (byte)0xe3, 0x2c, 0x7a, 0x16,
+  (byte)0xfa, 0x50, 0x32, 0x73, 0x07,
+  (byte)0xfa, (byte)0xb9, 0x2d, 0x73, (byte)0xce, 0x38, (byte)0xd0,
+};
+CBORTestCommon.AssertRoundTrip(CBORObject.DecodeFromBytes(bytes, options));
+}
+{
+var options = new CBOREncodeOptions("allowduplicatekeys=1;keepkeyorder=1");
+var bytes = new byte[] {
+  (byte)0xbf,
+  (byte)0x9f,
+  (byte)0xbf, 0x39, 0x20,
+  (byte)0x8f, 0x4a, 0x1f, 0x46, 0x26, 0x0b, 0x3e, 0x72, 0x2c, 0x7f, 0x11,
+  0x2e, 0x39,
+  (byte)0x9d,
+  (byte)0xba, 0x1a, 0x11,
+  (byte)0x8d,
+  (byte)0xc0,
+  (byte)0xb4, 0x38,
+  (byte)0xb6,
+  (byte)0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+  (byte)0xd8, 0x3b,
+  (byte)0x99, 0x00, 0x02, 0x3b, 0x05,
+  (byte)0xbb,
+  (byte)0xea,
+  (byte)0x8e, 0x4b,
+  (byte)0xd3, 0x5e, 0x22,
+  (byte)0x9f, 0x59, 0x00, 0x00,
+  (byte)0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x41, 0x20,
+  (byte)0xbf, 0x1a, 0x00, 0x00, 0x00, 0x61,
+  (byte)0xb9, 0x00, 0x01, 0x1a, 0x00, 0x00, 0x00, 0x0e,
+  (byte)0xba, 0x00, 0x00, 0x00, 0x00,
+  (byte)0xff,
+  (byte)0xff,
+  (byte)0xff,
+  (byte)0xd8, 0x22,
+  (byte)0xf8,
+  (byte)0x93,
+  (byte)0xd9,
+  (byte)0xaf, 0x33, 0x19,
+  (byte)0xf0,
+  (byte)0xf0,
+  (byte)0xf9,
+  (byte)0x85,
+  (byte)0x93,
+  (byte)0x99, 0x00, 0x01, 0x3a,
+  (byte)0xb5,
+  (byte)0xfb, 0x4d, 0x43,
+  (byte)0x98, 0x00,
+  (byte)0xff, (byte)0xfa, (byte)0xb0, (byte)0xb4, (byte)0xdc, 0x6d,
+  (byte)0xff,
+};
+CBORTestCommon.AssertRoundTrip(CBORObject.DecodeFromBytes(bytes, options));
+}
+{
+var options = new CBOREncodeOptions("allowduplicatekeys=1;keepkeyorder=1");
+var bytes = new byte[] {
+  (byte)0xdb, 0x0d,
+  (byte)0xcb, 0x5d, 0x78,
+  (byte)0x92,
+  (byte)0xc2,
+  (byte)0xc7, 0x2b,
+  (byte)0xb9, 0x00, 0x02, 0x39,
+  (byte)0xee,
+  (byte)0xa0, (byte)0xa0, 0x1a, 0x0e, (byte)0xd9, (byte)0xec, (byte)0xca,
+  (byte)0xf2,
+};
+CBORTestCommon.AssertRoundTrip(CBORObject.DecodeFromBytes(bytes, options));
+}
+{
+var options = new CBOREncodeOptions("allowduplicatekeys=1;keepkeyorder=1");
+var bytes = new byte[] {
+  (byte)0xbf,
+  (byte)0xfb,
+  (byte)0xb1, 0x21,
+  (byte)0x93,
+  (byte)0x8c,
+  (byte)0xc6,
+  (byte)0xf3,
+  (byte)0xcf,
+  (byte)0xb7, (byte)0xf8, 0x76, 0x18, (byte)0xda, 0x39, 0x60, (byte)0xf4,
+  (byte)0xff,
+};
+CBORTestCommon.AssertRoundTrip(CBORObject.DecodeFromBytes(bytes, options));
+}
+{
+var options = new CBOREncodeOptions("allowduplicatekeys=1;keepkeyorder=1");
+var bytes = new byte[] {
+  (byte)0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x02, (byte)0xf0, 0x0d, 0x2a, 0x21,
+};
+CBORTestCommon.AssertRoundTrip(CBORObject.DecodeFromBytes(bytes, options));
+}
+{
+var options = new CBOREncodeOptions("allowduplicatekeys=1;keepkeyorder=1");
+var bytes = new byte[] {
+  (byte)0xba, 0x00, 0x00, 0x00, 0x02,
+  (byte)0xf9, 0x48, 0x37,
+  (byte)0xda,
+  (byte)0xb5, 0x72,
+  (byte)0xcf,
+  (byte)0xf8, 0x31, 0x3b, 0x06, 0x78,
+  (byte)0xdb, 0x44, 0x7d, (byte)0xba, (byte)0xbd, 0x7d, 0x39, (byte)0x98,
+  (byte)0xb9,
+};
+CBORTestCommon.AssertRoundTrip(CBORObject.DecodeFromBytes(bytes, options));
+}
+}
+[Test]
+public void TestMapCompareRegressions() {
+  CBORObject m1, m2;
+  m1 = CBORObject.NewMap().Add(3, 4).Add(1, 2);
+  m2 = CBORObject.NewOrderedMap().Add(3, 4).Add(1, 2);
+  Assert.AreEqual(0, m1.CompareTo(m2));
+  TestCommon.CompareTestEqualAndConsistent(m1, m2);
+  m1 = CBORObject.NewMap().Add(3, 2).Add(1, 2);
+  m2 = CBORObject.NewOrderedMap().Add(3, 4).Add(1, 2);
+  TestCommon.CompareTestLess(m1, m2);
+  m1 = CBORObject.NewMap().Add(3, 7).Add(1, 2);
+  m2 = CBORObject.NewOrderedMap().Add(3, 4).Add(1, 2);
+  TestCommon.CompareTestGreater(m1, m2);
+  m1 = CBORObject.NewMap().Add(3, 4).Add(1, 0);
+  m2 = CBORObject.NewOrderedMap().Add(3, 4).Add(1, 2);
+  TestCommon.CompareTestLess(m1, m2);
+  m1 = CBORObject.NewMap().Add(3, 4).Add(1, 7);
+  m2 = CBORObject.NewOrderedMap().Add(3, 4).Add(1, 2);
+  TestCommon.CompareTestGreater(m1, m2);
+}
     [Test]
     public void TestToObject_TypeMapper() {
       var mapper = new CBORTypeMapper()

programming_language: C#
filename: CBORTest.csproj
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,6 @@
 <Project Sdk='Microsoft.NET.Sdk'>
   <PropertyGroup>
-    <TargetFramework>netcoreapp2.1</TargetFramework>
+    <TargetFramework>netcoreapp5.0</TargetFramework>
   </PropertyGroup>
   <PropertyGroup Condition=' &apos;$(Configuration)&apos;==&apos;Debug&apos; '>
     <DebugType>full</DebugType>

programming_language: C#
filename: CBORTestCommon.cs
change_type: ModificationType.MODIFY
diff: @@ -119,7 +119,8 @@ internal static class CBORTestCommon {
     public static CBORObject RandomCBORMap(IRandomGenExtended rand, int depth) {
       int x = rand.GetInt32(100);
       int count = (x < 80) ? 2 : ((x < 93) ? 1 : ((x < 98) ? 0 : 10));
-      CBORObject cborRet = CBORObject.NewMap();
+      CBORObject cborRet = rand.GetInt32(100) < 30 ?
+         CBORObject.NewOrderedMap() : CBORObject.NewMap();
       for (var i = 0; i < count; ++i) {
         CBORObject key = RandomCBORObject(rand, depth + 1);
         CBORObject value = RandomCBORObject(rand, depth + 1);

programming_language: Java
filename: TestCommon.cs
change_type: ModificationType.MODIFY
diff: @@ -761,7 +761,7 @@ public static void CompareTestLessEqual<T>(T o1, T o2, string msg)
       return true;
     }
 
-    private static bool ByteArraysEqual(byte[] arr1, byte[] arr2) {
+    public static bool ByteArraysEqual(byte[] arr1, byte[] arr2) {
       if (arr1 == null) {
         return arr2 == null;
       }

programming_language: Java