diff --git a/src/libraries/System.Formats.Cbor/src/Resources/Strings.resx b/src/libraries/System.Formats.Cbor/src/Resources/Strings.resx index 2b05ed271d1c4f..332df9fc8f5384 100644 --- a/src/libraries/System.Formats.Cbor/src/Resources/Strings.resx +++ b/src/libraries/System.Formats.Cbor/src/Resources/Strings.resx @@ -183,6 +183,9 @@ CBOR initial byte contains invalid integer encoding + + CBOR initial byte contains reserved additional information. + The current CBOR map contains an incomplete key/value pair. @@ -225,6 +228,9 @@ Not a valid tagged unix time encoding. + + The decoded Unix time is outside the range of DateTimeOffset. + Not a valid tagged bignum encoding. diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Simple.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Simple.cs index 228ae0bb78d9d4..c4e77634e5f7ec 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Simple.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Simple.cs @@ -179,6 +179,12 @@ public CborSimpleValue ReadSimpleValue() AdvanceDataItemCounters(); return (CborSimpleValue)value; default: + // Additional information values 28-30 are reserved: https://www.rfc-editor.org/rfc/rfc8949.html#section-3.3 + if (header.AdditionalInfo is > CborAdditionalInfo.Additional64BitData and < CborAdditionalInfo.IndefiniteLength) + { + throw new CborContentException(SR.Cbor_Reader_InvalidCbor_InvalidSimpleValueEncoding); + } + throw new InvalidOperationException(SR.Cbor_Reader_NotASimpleValueEncoding); } } diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs index 2ed46e1704dc1c..62dbb2adfba9df 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs @@ -90,7 +90,9 @@ public DateTimeOffset ReadDateTimeOffset() /// The decoded value. /// The next data item does not have the correct major type. /// -or- - /// The next date item does not have the correct semantic tag. + /// The next date item does not have the correct semantic tag. + /// -or- + /// The decoded Unix time is outside the range supported by . /// The next value has an invalid CBOR encoding. /// -or- /// There was an unexpected end of CBOR encoding data. @@ -131,9 +133,15 @@ public DateTimeOffset ReadUnixTimeSeconds() throw new CborContentException(SR.Cbor_Reader_InvalidUnixTimeEncoding); } } - catch + catch (Exception e) { RestoreCheckpoint(in checkpoint); + + if (e is ArgumentOutOfRangeException or OverflowException) + { + throw new InvalidOperationException(SR.Cbor_Reader_UnixTimeOutOfRange, e); + } + throw; } } diff --git a/src/libraries/System.Formats.Cbor/tests/Reader/CborReaderTests.Helpers.cs b/src/libraries/System.Formats.Cbor/tests/Reader/CborReaderTests.Helpers.cs index 54875325efd7a2..d5dc3cef682c1e 100644 --- a/src/libraries/System.Formats.Cbor/tests/Reader/CborReaderTests.Helpers.cs +++ b/src/libraries/System.Formats.Cbor/tests/Reader/CborReaderTests.Helpers.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.Linq; using System.Numerics; using Test.Cryptography; @@ -310,8 +311,21 @@ public static void VerifyMap(CborReader reader, object[] expectedValues, bool ex { "bf01ff", // indefinite-length map key missing a value "daffffffffff", // tag followed by break byte + "fc", // reserved additional information in major type 7 + "fd", + "fe", + "81fc", // reserved initial byte in an array + "a101fd", // reserved initial byte as a map value + "c1fe", // tag followed by a reserved initial byte }; public static string[] InvalidCborValues => TruncatedCborValues.Concat(MalformedCborValues).ToArray(); + + public static IEnumerable ReservedSimpleValueInputs => + from mode in new[] { CborConformanceMode.Lax, CborConformanceMode.Strict, CborConformanceMode.Canonical, CborConformanceMode.Ctap2Canonical } + from isFinalBlock in new[] { true, false } + where isFinalBlock || mode == CborConformanceMode.Lax + from hexEncoding in new[] { "fc", "fd", "fe" } + select new object[] { mode, isFinalBlock, hexEncoding }; } } diff --git a/src/libraries/System.Formats.Cbor/tests/Reader/CborReaderTests.Simple.cs b/src/libraries/System.Formats.Cbor/tests/Reader/CborReaderTests.Simple.cs index 8ef7500feb4cf5..4b3f28fd5f013b 100644 --- a/src/libraries/System.Formats.Cbor/tests/Reader/CborReaderTests.Simple.cs +++ b/src/libraries/System.Formats.Cbor/tests/Reader/CborReaderTests.Simple.cs @@ -179,6 +179,32 @@ public static void ReadSimpleValue_UnsupportedRanges_UnsupportedConformance_Shou Assert.Throws(() => reader.ReadSimpleValue()); } + [Theory] + [MemberData(nameof(ReservedSimpleValueInputs))] + public static void ReadSimpleValue_ReservedAdditionalInfo_ShouldThrowCborContentException(CborConformanceMode conformanceMode, bool isFinalBlock, string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding, new CborReaderOptions { ConformanceMode = conformanceMode }, isFinalBlock); + + Assert.Throws(() => reader.ReadSimpleValue()); + Assert.Equal(encoding.Length, reader.BytesRemaining); + Assert.Equal(0, reader.CurrentDepth); + } + + [Fact] + public static void ReadSimpleValue_EndOfIndefiniteLengthArray_ShouldThrowInvalidOperationException() + { + var reader = new CborReader("9fff".HexToByteArray()); + reader.ReadStartArray(); + + Assert.Throws(() => reader.ReadSimpleValue()); + Assert.Equal(1, reader.BytesRemaining); + Assert.Equal(CborReaderState.EndArray, reader.PeekState()); + + reader.ReadEndArray(); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); + } + [Theory] [InlineData("01")] // integer [InlineData("40")] // empty text string @@ -186,6 +212,9 @@ public static void ReadSimpleValue_UnsupportedRanges_UnsupportedConformance_Shou [InlineData("80")] // [] [InlineData("a0")] // {} [InlineData("c202")] // tagged value + [InlineData("f93c00")] // half-precision float + [InlineData("fa3f800000")] // single-precision float + [InlineData("fb3ff0000000000000")] // double-precision float public static void ReadSimpleValue_InvalidTypes_ShouldThrowInvalidOperationException(string hexEncoding) { byte[] encoding = hexEncoding.HexToByteArray(); diff --git a/src/libraries/System.Formats.Cbor/tests/Reader/CborReaderTests.SkipValue.cs b/src/libraries/System.Formats.Cbor/tests/Reader/CborReaderTests.SkipValue.cs index da346f6afa3b67..272c147c284637 100644 --- a/src/libraries/System.Formats.Cbor/tests/Reader/CborReaderTests.SkipValue.cs +++ b/src/libraries/System.Formats.Cbor/tests/Reader/CborReaderTests.SkipValue.cs @@ -73,6 +73,61 @@ public static void SkipValue_InvalidFormat_ShouldThrowCborContentException(strin Assert.Equal(encoding.Length, reader.BytesRemaining); } + [Theory] + [MemberData(nameof(ReservedSimpleValueInputs))] + public static void SkipValue_ReservedAdditionalInfo_ShouldThrowCborContentException(CborConformanceMode conformanceMode, bool isFinalBlock, string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var options = new CborReaderOptions { ConformanceMode = conformanceMode, AllowMultipleRootLevelValues = true }; + var reader = new CborReader(encoding, options, isFinalBlock); + + foreach (bool disableConformanceModeChecks in new[] { false, true }) + { + Assert.Throws(() => reader.SkipValue(disableConformanceModeChecks)); + Assert.Equal(encoding.Length, reader.BytesRemaining); + Assert.Equal(0, reader.CurrentDepth); + + Assert.Throws(() => reader.TrySkipValue(disableConformanceModeChecks)); + Assert.Equal(encoding.Length, reader.BytesRemaining); + Assert.Equal(0, reader.CurrentDepth); + + Assert.Throws(() => reader.ReadEncodedValue(disableConformanceModeChecks)); + Assert.Equal(encoding.Length, reader.BytesRemaining); + Assert.Equal(0, reader.CurrentDepth); + } + } + + [Theory] + [MemberData(nameof(ReservedSimpleValueInputs))] + public static void SkipToParent_ReservedAdditionalInfo_ShouldThrowCborContentExceptionAndPreserveState(CborConformanceMode conformanceMode, bool isFinalBlock, string hexEncoding) + { + byte[] encoding = $"a201018102{hexEncoding}".HexToByteArray(); // { 1: 1, [2]: } + var reader = new CborReader(encoding, new CborReaderOptions { ConformanceMode = conformanceMode }, isFinalBlock); + reader.ReadStartMap(); + Assert.Equal(1, reader.ReadInt32()); + Assert.Equal(1, reader.ReadInt32()); + int bytesRemaining = reader.BytesRemaining; + + foreach (bool disableConformanceModeChecks in new[] { false, true }) + { + Assert.Throws(() => reader.SkipToParent(disableConformanceModeChecks)); + Assert.Equal(bytesRemaining, reader.BytesRemaining); + Assert.Equal(1, reader.CurrentDepth); + Assert.Equal(CborReaderState.StartArray, reader.PeekState()); + + Assert.Throws(() => reader.TrySkipToParent(disableConformanceModeChecks)); + Assert.Equal(bytesRemaining, reader.BytesRemaining); + Assert.Equal(1, reader.CurrentDepth); + Assert.Equal(CborReaderState.StartArray, reader.PeekState()); + } + + Assert.Equal(1, reader.ReadStartArray()); + Assert.Equal(2, reader.ReadInt32()); + reader.ReadEndArray(); + Assert.Throws(() => reader.ReadSimpleValue()); + Assert.Equal(1, reader.BytesRemaining); + } + [Theory] [InlineData("61ff")] [InlineData("62f090")] @@ -364,6 +419,12 @@ public static void TrySkipValue_FinalBlock_InvalidValue_ShouldThrowCborContentEx [InlineData("daffffffffff")] // tag followed by break byte [InlineData("1c")] // reserved additional information value [InlineData("ff")] // break byte at the root context + [InlineData("fc")] + [InlineData("fd")] + [InlineData("fe")] + [InlineData("81fc")] + [InlineData("a101fd")] + [InlineData("c1fe")] public static void TrySkipValue_NotFinalBlock_MalformedData_ShouldThrowCborContentException(string hexEncoding) { byte[] encoding = hexEncoding.HexToByteArray(); diff --git a/src/libraries/System.Formats.Cbor/tests/Reader/CborReaderTests.Tag.cs b/src/libraries/System.Formats.Cbor/tests/Reader/CborReaderTests.Tag.cs index 85535aec0fbdb4..c701bb00b1b738 100644 --- a/src/libraries/System.Formats.Cbor/tests/Reader/CborReaderTests.Tag.cs +++ b/src/libraries/System.Formats.Cbor/tests/Reader/CborReaderTests.Tag.cs @@ -298,6 +298,8 @@ public static void ReadDateTimeOffset_StrictConformance_OnError_ShouldPreserveRe [InlineData("1970-01-01T00:00:00Z", "c100")] [InlineData("1969-12-31T23:59:59Z", "c120")] [InlineData("1960-01-01T00:00:00Z", "c13a12cff77f")] + [InlineData("0001-01-01T00:00:00Z", "c13b0000000e7791f6ff")] + [InlineData("9999-12-31T23:59:59Z", "c11b0000003afff4417f")] public static void ReadUnixTimeSeconds_SingleValue_HappyPath(string expectedValueString, string hexEncoding) { DateTimeOffset expectedValue = DateTimeOffset.Parse(expectedValueString); @@ -334,6 +336,74 @@ public static void ReadUnixTimeSeconds_InvalidFloatPayload_ShouldThrowCborConten Assert.Equal(encoding.Length, reader.BytesRemaining); } + [Theory] + [InlineData("c11b7fffffffffffffff")] // long.MaxValue + [InlineData("c13b7fffffffffffffff")] // long.MinValue + [InlineData("c13b0000000e7791f700")] // one second before DateTimeOffset.MinValue + [InlineData("c11b0000003afff44180")] // one second after DateTimeOffset.MaxValue + [InlineData("c1fb425d1a94a2000000")] // 5e11 seconds, within TimeSpan's range + [InlineData("c1fbc2374876e8000000")] // -1e11 seconds, within TimeSpan's range + public static void ReadUnixTimeSeconds_OutOfDateTimeRange_ShouldThrowInvalidOperationException(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + InvalidOperationException exception = Assert.Throws(() => reader.ReadUnixTimeSeconds()); + Assert.IsType(exception.InnerException); + Assert.Equal(encoding.Length, reader.BytesRemaining); + Assert.Equal(0, reader.CurrentDepth); + Assert.Equal(CborReaderState.Tag, reader.PeekState()); + + reader.SkipValue(); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); + } + + [Theory] + [InlineData("c11bffffffffffffffff")] // ulong.MaxValue + [InlineData("c13bffffffffffffffff")] // -1 - ulong.MaxValue + [InlineData("c1fb7e37e43c8800759c")] // 1e300 seconds + [InlineData("c1fbfe37e43c8800759c")] // -1e300 seconds + [InlineData("c1fa7f7fffff")] // float.MaxValue seconds + [InlineData("c1faff7fffff")] // -float.MaxValue seconds + public static void ReadUnixTimeSeconds_OutOfNumericRange_ShouldThrowInvalidOperationException(string hexEncoding) + { + byte[] encoding = hexEncoding.HexToByteArray(); + var reader = new CborReader(encoding); + + InvalidOperationException exception = Assert.Throws(() => reader.ReadUnixTimeSeconds()); + Assert.IsType(exception.InnerException); + Assert.Equal(encoding.Length, reader.BytesRemaining); + Assert.Equal(0, reader.CurrentDepth); + Assert.Equal(CborReaderState.Tag, reader.PeekState()); + + reader.SkipValue(); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); + } + + [Theory] + [InlineData("c11b7fffffffffffffff")] + [InlineData("c1fb425d1a94a2000000")] + [InlineData("c11bffffffffffffffff")] + public static void ReadUnixTimeSeconds_OutOfRangeMapKey_ShouldPreserveReaderState(string hexEncoding) + { + byte[] encoding = $"a20101{hexEncoding}02".HexToByteArray(); + var reader = new CborReader(encoding, CborConformanceMode.Strict); + reader.ReadStartMap(); + Assert.Equal(1, reader.ReadInt32()); + Assert.Equal(1, reader.ReadInt32()); + int bytesRemaining = reader.BytesRemaining; + + Assert.Throws(() => reader.ReadUnixTimeSeconds()); + Assert.Equal(bytesRemaining, reader.BytesRemaining); + Assert.Equal(1, reader.CurrentDepth); + Assert.Equal(CborReaderState.Tag, reader.PeekState()); + + reader.SkipValue(); + Assert.Equal(2, reader.ReadInt32()); + reader.ReadEndMap(); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); + } + [Theory] [InlineData("01")] // numeric value without tag [InlineData("c301")] // non-datetime tag @@ -456,6 +526,9 @@ public static void ReadDecimal_SingleValue_HappyPath(string expectedStringValue, [Theory] [InlineData("c482181d02")] // 2 * 10^29 [InlineData("c482381c02")] // 2 * 10^-29 + [InlineData("c4821903e801")] // 1 * 10^1000 + [InlineData("c4821bffffffffffffffff01")] // exponent exceeds long.MaxValue + [InlineData("c4823bffffffffffffffff01")] // exponent is less than long.MinValue [InlineData("c48201c24cffffffffffffffffffffffff")] // decimal.MaxValue * 10^1 [InlineData("c48200c24d01000000000000000000000000")] // (decimal.MaxValue + 1) * 10^0 [InlineData("c48200c34cffffffffffffffffffffffff")] // (decimal.MinValue - 1) * 10^0 @@ -465,6 +538,11 @@ public static void ReadDecimal_LargeValues_ShouldThrowOverflowException(string h var reader = new CborReader(encoding); Assert.Throws(() => reader.ReadDecimal()); Assert.Equal(encoding.Length, reader.BytesRemaining); + Assert.Equal(0, reader.CurrentDepth); + Assert.Equal(CborReaderState.Tag, reader.PeekState()); + + reader.SkipValue(); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); } [Theory]