CVE-2024-21909 (C#, published 2024-01-03T16:15Z)
Description: value: PeterO.Cbor versions 4.0.0 through 4.5.0 are vulnerable to a denial of 
service vulnerability. An attacker may trigger the denial of service 
condition by providing crafted data to the DecodeFromBytes or other 
decoding mechanisms in PeterO.Cbor. Depending on the usage of the 
library, an unauthenticated and remote attacker may be able to cause the
 denial of service condition.

Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-407 \u2014 Inefficient Algorithmic Complexity
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.
Repository: https://github.com/peteroupc/CBOR
Files changed:
CBOR/PeterO/Cbor/CBORObject.cs
CBOR/PeterO/Cbor/PropertyMap.cs
CBORTest/CBORGenerator.cs
CBORTest/CBORObjectTest.cs
CBORTest/CBORTest.csproj
CBORTest/CBORTestCommon.cs
CBORTest/TestCommon.cs
Commit message: fix algorithmic complexity issue; add regression tests


Vulnerable code:
      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();
        this.dict = new Dictionary<TKey, TValue>();
      4, 5, 6, 6, 7, 7, 7, 7, 7, 7,
      if (majorType == 3 || majorType == 2) {
        }
        if (depth > 6) {
      } else if (majorType == 4 || majorType == 5) {
          this.Generate(r, depth + 1, bs);
      if (majorType == 6) {
    <TargetFramework>netcoreapp2.1</TargetFramework>
      CBORObject cborRet = CBORObject.NewMap();
    private static bool ByteArraysEqual(byte[] arr1, byte[] arr2) {

Fixed code:
      var sortedASet = new List<CBORObject>(PropertyMap.GetSortedKeys(mapA));
      var sortedBSet = new List<CBORObject>(PropertyMap.GetSortedKeys(mapB));
        this.dict = new SortedDictionary<TKey, TValue>();
      public ICollection<TKey> SortedKeys {
        get {
           return this.dict.Keys;
        }
      }

    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");
    }

      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 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));
      }
    }
      if (depth > 6) {
        majorType = valueMajorTypesHighDepth[r.GetInt32(
              valueMajorTypesHighDepth.Length)];
      }
      if (majorType == 3 || majorType == 2) { // Byte and text strings
        } else if (depth > 6) {
        } else if (depth > 2) {
          len = r.GetInt32(16) + 1;
      } else if (majorType == 4 || majorType == 5) { // Arrays and maps
        } 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);
          if (depth > 6) {
            this.GenerateSmall(r, depth + 1, bs);
          } else {
            this.Generate(r, depth + 1, bs);
          }
      if (majorType == 6) { // Tags
[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);
}
    <TargetFramework>netcoreapp5.0</TargetFramework>
      CBORObject cborRet = rand.GetInt32(100) < 30 ?
         CBORObject.NewOrderedMap() : CBORObject.NewMap();
    public static bool ByteArraysEqual(byte[] arr1, byte[] arr2) {