implement appropriate rollback semantics for key conformance validation
authorEirik Tsarpalis <eirik.tsarpalis@gmail.com>
Mon, 27 Apr 2020 21:52:06 +0000 (22:52 +0100)
committerEirik Tsarpalis <eirik.tsarpalis@gmail.com>
Wed, 29 Apr 2020 14:30:11 +0000 (15:30 +0100)
src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborReaderTests.Map.cs
src/libraries/System.Security.Cryptography.Encoding/tests/Cbor.Tests/CborWriterTests.Map.cs
src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.Map.cs
src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborReader.cs
src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.Map.cs
src/libraries/System.Security.Cryptography.Encoding/tests/Cbor/CborWriter.cs

index 23c4eab..c9dd43d 100644 (file)
@@ -191,7 +191,17 @@ namespace System.Security.Cryptography.Encoding.Tests.Cbor
             reader.ReadStartMap();
             Helpers.VerifyValue(reader, dupeKey);
             reader.ReadInt32();
+
+            int bytesRead = reader.BytesRead;
+            int bytesRemaining = reader.BytesRemaining;
+            CborReaderState state = reader.PeekState();
+
             Assert.Throws<FormatException>(() => Helpers.VerifyValue(reader, dupeKey));
+
+            // ensure reader state is preserved
+            Assert.Equal(bytesRead, reader.BytesRead);
+            Assert.Equal(bytesRemaining, reader.BytesRemaining);
+            Assert.Equal(state, reader.PeekState());
         }
 
         [Theory]
@@ -227,8 +237,17 @@ namespace System.Security.Cryptography.Encoding.Tests.Cbor
                 reader.ReadInt32(); // value is always an integer
             }
 
+            int bytesRead = reader.BytesRead;
+            int bytesRemaining = reader.BytesRemaining;
+            CborReaderState state = reader.PeekState();
+
             // the final element violates sorting invariant
             Assert.Throws<FormatException>(() => Helpers.VerifyValue(reader, keySequence.Last()));
+
+            // ensure reader state is preserved
+            Assert.Equal(bytesRead, reader.BytesRead);
+            Assert.Equal(bytesRemaining, reader.BytesRemaining);
+            Assert.Equal(state, reader.PeekState());
         }
 
         [Theory]
index 1ee3c57..69fc246 100644 (file)
@@ -160,6 +160,43 @@ namespace System.Security.Cryptography.Encoding.Tests.Cbor
         }
 
         [Theory]
+        [InlineData(CborConformanceLevel.Strict, 42)]
+        [InlineData(CborConformanceLevel.Rfc7049Canonical, 42)]
+        [InlineData(CborConformanceLevel.Ctap2Canonical, 42)]
+        [InlineData(CborConformanceLevel.Strict, "foobar")]
+        [InlineData(CborConformanceLevel.Rfc7049Canonical, "foobar")]
+        [InlineData(CborConformanceLevel.Ctap2Canonical, "foobar")]
+        [InlineData(CborConformanceLevel.Strict, new object[] { new object[] { "x", "y" } })]
+        [InlineData(CborConformanceLevel.Rfc7049Canonical, new object[] { new object[] { "x", "y" } })]
+        [InlineData(CborConformanceLevel.Ctap2Canonical, new object[] { new object[] { "x", "y" } })]
+        internal static void WriteMap_DuplicateKeys_StrictConformance_ShouldBeRecoverableError(CborConformanceLevel level, object dupeKey)
+        {
+            byte[] expected = PerformWrite(attemptDuplicateWrite: false);
+            byte[] actual = PerformWrite(attemptDuplicateWrite: true);
+            Assert.Equal(expected.ByteArrayToHex(), actual.ByteArrayToHex());
+
+            byte[] PerformWrite(bool attemptDuplicateWrite)
+            {
+                using var writer = new CborWriter(level);
+                writer.WriteStartMap(2);
+                Helpers.WriteValue(writer, dupeKey);
+                writer.WriteInt32(0);
+
+                if (attemptDuplicateWrite)
+                {
+                    Assert.Throws<InvalidOperationException>(() => Helpers.WriteValue(writer, dupeKey));
+                }
+
+                // wrap dupe key in an array to satisfy key sorting & uniqueness constraints
+                Helpers.WriteValue(writer, new object[] { dupeKey }); 
+                writer.WriteInt32(0);
+                writer.WriteEndMap();
+
+                return writer.ToArray();
+            }
+        }
+
+        [Theory]
         [InlineData(0)]
         [InlineData(1)]
         [InlineData(3)]
index 21206ec..74e818e 100644 (file)
@@ -115,10 +115,12 @@ namespace System.Security.Cryptography.Encoding.Tests.Cbor
                 int cmp = CborConformanceLevelHelpers.CompareEncodings(previousKeyEncoding, currentKeyEncoding, ConformanceLevel);
                 if (cmp > 0)
                 {
+                    ResetBuffer(currentKeyRange.Offset);
                     throw new FormatException("CBOR map keys are not in sorted encoding order.");
                 }
                 else if (cmp == 0 && CborConformanceLevelHelpers.RequiresUniqueKeys(ConformanceLevel))
                 {
+                    ResetBuffer(currentKeyRange.Offset);
                     throw new FormatException("CBOR map contains duplicate keys.");
                 }
             }
@@ -134,6 +136,7 @@ namespace System.Security.Cryptography.Encoding.Tests.Cbor
 
             if (!previousKeys.Add(currentKeyRange))
             {
+                ResetBuffer(currentKeyRange.Offset);
                 throw new FormatException("CBOR map contains duplicate keys.");
             }
         }
index 3a1eb3a..20ea757 100644 (file)
@@ -361,6 +361,14 @@ namespace System.Security.Cryptography.Encoding.Tests.Cbor
             _cachedState = CborReaderState.Unknown;
         }
 
+        private void ResetBuffer(int position)
+        {
+            _buffer = _originalBuffer.Slice(position);
+            _bytesRead = position;
+            // invalidate the state cache
+            _cachedState = CborReaderState.Unknown;
+        }
+
         private void EnsureBuffer(int length)
         {
             if (_buffer.Length < length)
index d5225b3..df6a485 100644 (file)
@@ -67,7 +67,7 @@ namespace System.Security.Cryptography.Encoding.Tests.Cbor
         {
             Debug.Assert(_currentKeyOffset != null && _currentValueOffset == null);
 
-            _currentValueOffset = _offset;
+            int currentValueOffset = _offset;
 
             if (CborConformanceLevelHelpers.RequiresUniqueKeys(ConformanceLevel))
             {
@@ -75,15 +75,20 @@ namespace System.Security.Cryptography.Encoding.Tests.Cbor
 
                 (int Offset, int KeyLength, int TotalLength) currentKeyRange =
                     (_currentKeyOffset.Value,
-                     _currentValueOffset.Value - _currentKeyOffset.Value,
-                     0);
+                     currentValueOffset - _currentKeyOffset.Value,
+                     0); 
 
                 if (ranges.Contains(currentKeyRange))
                 {
-                    // TODO: check if rollback is necessary here
+                    // reset writer state to before the offending key write
+                    _buffer.AsSpan(currentKeyRange.Offset, _offset).Fill(0);
+                    _offset = currentKeyRange.Offset;
+
                     throw new InvalidOperationException("Duplicate key encoding in CBOR map.");
                 }
             }
+
+            _currentValueOffset = currentValueOffset;
         }
 
         private void HandleValueWritten()
index 097ab82..972f716 100644 (file)
@@ -161,9 +161,6 @@ namespace System.Security.Cryptography.Encoding.Tests.Cbor
 
         private void AdvanceDataItemCounters()
         {
-            _remainingDataItems--;
-            _isTagContext = false;
-
             if (_currentKeyOffset != null) // this is a map context
             {
                 if (_currentValueOffset == null)
@@ -175,6 +172,9 @@ namespace System.Security.Cryptography.Encoding.Tests.Cbor
                     HandleValueWritten();
                 }
             }
+
+            _remainingDataItems--;
+            _isTagContext = false;
         }
 
         private void WriteInitialByte(CborInitialByte initialByte)