From d7f81790c816959342f242fc6957c42d0473ce18 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Mon, 22 Jul 2024 10:27:25 -0700 Subject: [PATCH 1/5] Base 64 decoder, reject input when unused bits are not 0 --- .../tests/Base64/Base64DecoderUnitTests.cs | 36 ++++++--- .../Base64Url/Base64UrlDecoderUnitTests.cs | 73 ++++++++++++------- .../Base64UrlUnicodeAPIsUnitTests.cs | 22 +++--- .../Base64Url/Base64UrlValidationUnitTests.cs | 4 +- .../Text/Base64Helper/Base64DecoderHelper.cs | 8 +- .../Base64Helper/Base64ValidatorHelper.cs | 32 +++++--- .../Text/Base64Url/Base64UrlValidator.cs | 9 +-- 7 files changed, 114 insertions(+), 70 deletions(-) diff --git a/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs b/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs index b31393846c7466..b2775040df0819 100644 --- a/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs +++ b/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs @@ -179,6 +179,7 @@ public void DecodingOutputTooSmall() { Span source = new byte[12]; Base64TestHelper.InitializeDecodableBytes(source); + source[9] = 65; // make sure unused bits set to 0 source[10] = Base64TestHelper.EncodingPad; source[11] = Base64TestHelper.EncodingPad; @@ -193,6 +194,7 @@ public void DecodingOutputTooSmall() { Span source = new byte[12]; Base64TestHelper.InitializeDecodableBytes(source); + source[10] = 77; // make sure unused bits set to 0 source[11] = Base64TestHelper.EncodingPad; Span decodedBytes = new byte[7]; @@ -270,6 +272,22 @@ public void BasicDecodingWithFinalBlockTrueKnownInputDone(string inputString, in Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, expectedWritten, source, decodedBytes)); } + [Theory] + [InlineData("AR==")] + [InlineData("AQJ=")] + [InlineData("AQIDBB==")] + [InlineData("AQIDBAV=")] + [InlineData("AQIDBAUHCAkKCwwNDz==")] + [InlineData("AQIDBAUHCAkKCwwNDxD=")] + public void BasicDecodingWithNonZeroUnusedBits(string inputString) + { + Span source = Encoding.ASCII.GetBytes(inputString); + Span decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)]; + + Assert.False(Base64.IsValid(inputString)); + Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int _)); + } + [Theory] [InlineData("A", 0, 0)] [InlineData("A===", 0, 0)] @@ -468,10 +486,9 @@ public void DecodingInvalidBytesPadding(bool isFinalBlock) // The last byte or the last 2 bytes being the padding character is valid, if isFinalBlock = true { - Span source = new byte[] { 50, 50, 50, 50, 80, 80, 80, 80 }; + Span source = new byte[] { 50, 50, 50, 50, 80, 65, + Base64TestHelper.EncodingPad, Base64TestHelper.EncodingPad }; // valid input - "2222PA==" Span decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)]; - source[6] = Base64TestHelper.EncodingPad; - source[7] = Base64TestHelper.EncodingPad; // valid input - "2222PP==" OperationStatus expectedStatus = isFinalBlock ? OperationStatus.Done : OperationStatus.InvalidData; int expectedConsumed = isFinalBlock ? source.Length : 4; @@ -482,9 +499,9 @@ public void DecodingInvalidBytesPadding(bool isFinalBlock) Assert.Equal(expectedWritten, decodedByteCount); Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, expectedWritten, source, decodedBytes)); - source = new byte[] { 50, 50, 50, 50, 80, 80, 80, 80 }; + source = new byte[] { 50, 50, 50, 50, 80, 80, 77, 80 }; decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)]; - source[7] = Base64TestHelper.EncodingPad; // valid input - "2222PPP=" + source[7] = Base64TestHelper.EncodingPad; // valid input - "2222PPM=" expectedConsumed = isFinalBlock ? source.Length : 4; expectedWritten = isFinalBlock ? 5 : 3; @@ -661,9 +678,8 @@ public void DecodeInPlaceInvalidBytesPadding() // The last byte or the last 2 bytes being the padding character is valid { - Span buffer = new byte[] { 50, 50, 50, 50, 80, 80, 80, 80 }; - buffer[6] = Base64TestHelper.EncodingPad; - buffer[7] = Base64TestHelper.EncodingPad; // valid input - "2222PP==" + Span buffer = new byte[] { 50, 50, 50, 50, 80, 65, + Base64TestHelper.EncodingPad, Base64TestHelper.EncodingPad }; // valid input - "2222PA==" string sourceString = Encoding.ASCII.GetString(buffer.ToArray()); Assert.Equal(OperationStatus.Done, Base64.DecodeFromUtf8InPlace(buffer, out int bytesWritten)); Assert.Equal(4, bytesWritten); @@ -672,8 +688,8 @@ public void DecodeInPlaceInvalidBytesPadding() } { - Span buffer = new byte[] { 50, 50, 50, 50, 80, 80, 80, 80 }; - buffer[7] = Base64TestHelper.EncodingPad; // valid input - "2222PPP=" + Span buffer = new byte[] { 50, 50, 50, 50, 80, 80, 77, 80 }; + buffer[7] = Base64TestHelper.EncodingPad; // valid input - "2222PPM=" string sourceString = Encoding.ASCII.GetString(buffer.ToArray()); Assert.Equal(OperationStatus.Done, Base64.DecodeFromUtf8InPlace(buffer, out int bytesWritten)); Assert.Equal(5, bytesWritten); diff --git a/src/libraries/System.Memory/tests/Base64Url/Base64UrlDecoderUnitTests.cs b/src/libraries/System.Memory/tests/Base64Url/Base64UrlDecoderUnitTests.cs index 1a321a7ba9544b..e06212e2571824 100644 --- a/src/libraries/System.Memory/tests/Base64Url/Base64UrlDecoderUnitTests.cs +++ b/src/libraries/System.Memory/tests/Base64Url/Base64UrlDecoderUnitTests.cs @@ -23,6 +23,7 @@ public void BasicDecoding() Span source = new byte[numBytes]; Base64TestHelper.InitializeUrlDecodableBytes(source, numBytes); + source[numBytes - 1] = 65; // make sure unused bits set 0 Span decodedBytes = new byte[Base64Url.GetMaxDecodedLength(source.Length)]; Assert.Equal(OperationStatus.Done, Base64Url.DecodeFromUtf8(source, decodedBytes, out int consumed, out int decodedByteCount)); @@ -46,6 +47,7 @@ public void BasicDecodingByteArrayReturnOverload() Span source = new byte[numBytes]; Base64TestHelper.InitializeUrlDecodableBytes(source, numBytes); + source[numBytes - 1] = 65; // make sure unused bits set 0 Span decodedBytes = Base64Url.DecodeFromUtf8(source); Assert.Equal(decodedBytes.Length, Base64Url.GetMaxDecodedLength(source.Length)); @@ -197,6 +199,7 @@ public void DecodingOutputTooSmall() { Span source = new byte[12]; Base64TestHelper.InitializeUrlDecodableBytes(source); + source[9] = 65; // make sure unused bits set 0 source[10] = Base64TestHelper.EncodingPad; source[11] = Base64TestHelper.EncodingPad; @@ -211,6 +214,7 @@ public void DecodingOutputTooSmall() { Span source = new byte[12]; Base64TestHelper.InitializeUrlDecodableBytes(source); + source[10] = 77; // make sure unused bits set 0 source[11] = Base64TestHelper.EncodingPad; Span decodedBytes = new byte[7]; @@ -287,6 +291,22 @@ public void BasicDecodingWithFinalBlockTrueKnownInputDone(string inputString, in Assert.True(Base64TestHelper.VerifyUrlDecodingCorrectness(inputString.Length, expectedWritten, source, decodedBytes)); } + [Theory] + [InlineData("AR")] + [InlineData("AQJ")] + [InlineData("AQIDBB%%")] + [InlineData("AQIDBAV%")] + [InlineData("AQIDBAUHCAkKCwwNDz")] + [InlineData("AQIDBAUHCAkKCwwNDxD")] + public void BasicDecodingWithNonZeroUnusedBits(string inputString) + { + Span source = Encoding.ASCII.GetBytes(inputString); + Span decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)]; + + Assert.False(Base64.IsValid(inputString)); + Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int _)); + } + [Theory] [InlineData("A", 0, 0, OperationStatus.InvalidData)] [InlineData("A===", 0, 0, OperationStatus.InvalidData)] @@ -460,7 +480,7 @@ public void DecodingInvalidBytes(bool isFinalBlock) // When isFinalBlock = true input that is not a multiple of 4 is invalid for Base64, but valid for Base64Url if (isFinalBlock) { - Span source = "2222PPP"u8.ToArray(); // incomplete input + Span source = "2222PPM"u8.ToArray(); // incomplete input Span decodedBytes = new byte[Base64Url.GetMaxDecodedLength(source.Length)]; Assert.Equal(5, Base64Url.DecodeFromUtf8(source, decodedBytes)); Assert.True(Base64TestHelper.VerifyUrlDecodingCorrectness(7, 5, source, decodedBytes)); @@ -517,10 +537,9 @@ public void DecodingInvalidBytesPadding(bool isFinalBlock) // The last byte or the last 2 bytes being the padding character is valid, if isFinalBlock = true { - Span source = new byte[] { 50, 50, 50, 50, 80, 80, 80, 80 }; + Span source = new byte[] { 50, 50, 50, 50, 80, 65, + Base64TestHelper.EncodingPad, Base64TestHelper.EncodingPad }; // valid input - "2222PA==" Span decodedBytes = new byte[Base64Url.GetMaxDecodedLength(source.Length)]; - source[6] = Base64TestHelper.EncodingPad; - source[7] = Base64TestHelper.EncodingPad; // valid input - "2222PP==" OperationStatus expectedStatus = isFinalBlock ? OperationStatus.Done : OperationStatus.InvalidData; int expectedConsumed = isFinalBlock ? source.Length : 4; @@ -531,9 +550,9 @@ public void DecodingInvalidBytesPadding(bool isFinalBlock) Assert.Equal(expectedWritten, decodedByteCount); Assert.True(Base64TestHelper.VerifyUrlDecodingCorrectness(expectedConsumed, expectedWritten, source, decodedBytes)); - source = new byte[] { 50, 50, 50, 50, 80, 80, 80, 80 }; + source = new byte[] { 50, 50, 50, 50, 80, 80, 77, 80 }; decodedBytes = new byte[Base64Url.GetMaxDecodedLength(source.Length)]; - source[7] = Base64TestHelper.UrlEncodingPad; // valid input - "2222PPP=" + source[7] = Base64TestHelper.UrlEncodingPad; // valid input - "2222PPM=" expectedConsumed = isFinalBlock ? source.Length : 4; expectedWritten = isFinalBlock ? 5 : 3; @@ -685,9 +704,8 @@ public void DecodeInPlaceInvalidBytesPaddingThrowsFormatException() // The last byte or the last 2 bytes being the padding character is valid { - Span buffer = new byte[] { 50, 50, 50, 50, 80, 80, 80, 80 }; - buffer[6] = Base64TestHelper.UrlEncodingPad; - buffer[7] = Base64TestHelper.EncodingPad; // valid input - "2222PP==" + Span buffer = new byte[] { 50, 50, 50, 50, 80, 65, + Base64TestHelper.UrlEncodingPad, Base64TestHelper.EncodingPad }; // valid input - "2222PA==" string sourceString = Encoding.ASCII.GetString(buffer.ToArray()); int bytesWritten = Base64Url.DecodeFromUtf8InPlace(buffer); @@ -696,8 +714,7 @@ public void DecodeInPlaceInvalidBytesPaddingThrowsFormatException() } { - Span buffer = new byte[] { 50, 50, 50, 50, 80, 80, 80, 80 }; - buffer[7] = Base64TestHelper.EncodingPad; // valid input - "2222PPP=" + Span buffer = new byte[] { 50, 50, 50, 50, 80, 80, 77, Base64TestHelper.EncodingPad }; // valid input - "2222PPM=" string sourceString = Encoding.ASCII.GetString(buffer.ToArray()); int bytesWritten = Base64Url.DecodeFromUtf8InPlace(buffer); @@ -707,7 +724,7 @@ public void DecodeInPlaceInvalidBytesPaddingThrowsFormatException() // The last byte or the last 2 bytes being the padding character is valid { - Span buffer = new byte[] { 50, 50, 50, 50, 80, 80 }; // valid input without padding "2222PP" + Span buffer = new byte[] { 50, 50, 50, 50, 80, 65 }; // valid input without padding "2222PA" string sourceString = Encoding.ASCII.GetString(buffer.ToArray()); int bytesWritten = Base64Url.DecodeFromUtf8InPlace(buffer); @@ -775,12 +792,12 @@ public void DecodingInPlaceWithOnlyCharsToBeIgnored(string utf8WithCharsToBeIgno } [Theory] - [InlineData(new byte[] { 0xa, 0xa, 0x2d, 0x2d }, 251)] - [InlineData(new byte[] { 0xa, 0x5f, 0xa, 0x2d }, 255)] - [InlineData(new byte[] { 0x5f, 0x5f, 0xa, 0xa }, 255)] - [InlineData(new byte[] { 0x70, 0xa, 0x61, 0xa }, 165)] - [InlineData(new byte[] { 0xa, 0x70, 0xa, 0x61, 0xa }, 165)] - [InlineData(new byte[] { 0x70, 0xa, 0x61, 0xa, 0x3d, 0x3d }, 165)] + [InlineData(new byte[] { 0xa, 0xa, 0x2d, 0x77 }, 251)] + [InlineData(new byte[] { 0xa, 0x5f, 0xa, 0x77 }, 255)] + [InlineData(new byte[] { 0x5f, 0x77, 0xa, 0xa }, 255)] + [InlineData(new byte[] { 0x70, 0xa, 0x51, 0xa }, 165)] + [InlineData(new byte[] { 0xa, 0x70, 0xa, 0x51, 0xa }, 165)] + [InlineData(new byte[] { 0x70, 0xa, 0x51, 0xa, 0x3d, 0x3d }, 165)] public void DecodingLessThan4BytesWithWhiteSpaces(byte[] utf8Bytes, byte decoded) { Assert.True(Base64Url.IsValid(utf8Bytes, out int decodedLength)); @@ -802,12 +819,12 @@ public void DecodingLessThan4BytesWithWhiteSpaces(byte[] utf8Bytes, byte decoded } [Theory] - [InlineData(new char[] { '\r', '\r', '-', '-' }, 251)] - [InlineData(new char[] { '\r', '_', '\r', '-' }, 255)] - [InlineData(new char[] { '_', '_', '\r', '\r' }, 255)] - [InlineData(new char[] { 'p', '\r', 'a', '\r' }, 165)] - [InlineData(new char[] { '\r', 'p', '\r', 'a', '\r' }, 165)] - [InlineData(new char[] { 'p', '\r', 'a', '\r', '=', '=' }, 165)] + [InlineData(new char[] { '\r', '\r', '-', 'w' }, 251)] + [InlineData(new char[] { '\r', '_', '\r', 'w' }, 255)] + [InlineData(new char[] { '_', 'w', '\r', '\r' }, 255)] + [InlineData(new char[] { 'p', '\r', 'Q', '\r' }, 165)] + [InlineData(new char[] { '\r', 'p', '\r', 'Q', '\r' }, 165)] + [InlineData(new char[] { 'p', '\r', 'Q', '\r', '=', '=' }, 165)] public void DecodingLessThan4CharsWithWhiteSpaces(char[] utf8Bytes, byte decoded) { Assert.True(Base64Url.IsValid(utf8Bytes, out int decodedLength)); @@ -825,8 +842,8 @@ public void DecodingLessThan4CharsWithWhiteSpaces(char[] utf8Bytes, byte decoded } [Theory] - [InlineData(new byte[] { 0x4a, 0x74, 0xa, 0x4a, 0x4a, 0x74, 0xa, 0x4a }, new byte[] { 38, 210, 73, 180 })] - [InlineData(new byte[] { 0xa, 0x2d, 0x56, 0xa, 0xa, 0xa, 0x2d, 0x4a, 0x4a, 0x4a, }, new byte[] { 249, 95, 137, 36 })] + [InlineData(new byte[] { 0x4a, 0x74, 0xa, 0x4a, 0x4a, 0x74, 0xa, 0x41 }, new byte[] { 38, 210, 73, 180 })] + [InlineData(new byte[] { 0xa, 0x2d, 0x56, 0xa, 0xa, 0xa, 0x2d, 0x4a, 0x4a, 0x41, }, new byte[] { 249, 95, 137, 36 })] public void DecodingNotMultipleOf4WithWhiteSpace(byte[] utf8Bytes, byte[] decoded) { Assert.True(Base64Url.IsValid(utf8Bytes, out int decodedLength)); @@ -847,8 +864,8 @@ public void DecodingNotMultipleOf4WithWhiteSpace(byte[] utf8Bytes, byte[] decode } [Theory] - [InlineData(new char[] { 'J', 't', '\r', 'J', 'J', 't', '\r', 'J' }, new byte[] { 38, 210, 73, 180 })] - [InlineData(new char[] { '\r', '-', 'V', '\r', '\r', '\r', '-', 'J', 'J', 'J', }, new byte[] { 249, 95, 137, 36 })] + [InlineData(new char[] { 'J', 't', '\r', 'J', 'J', 't', '\r', 'A' }, new byte[] { 38, 210, 73, 180 })] + [InlineData(new char[] { '\r', '-', 'V', '\r', '\r', '\r', '-', 'J', 'J', 'A', }, new byte[] { 249, 95, 137, 36 })] public void DecodingNotMultipleOf4CharsWithWhiteSpace(char[] utf8Bytes, byte[] decoded) { Assert.True(Base64Url.IsValid(utf8Bytes, out int decodedLength)); diff --git a/src/libraries/System.Memory/tests/Base64Url/Base64UrlUnicodeAPIsUnitTests.cs b/src/libraries/System.Memory/tests/Base64Url/Base64UrlUnicodeAPIsUnitTests.cs index adbf17eae10047..0d39bc7854bc24 100644 --- a/src/libraries/System.Memory/tests/Base64Url/Base64UrlUnicodeAPIsUnitTests.cs +++ b/src/libraries/System.Memory/tests/Base64Url/Base64UrlUnicodeAPIsUnitTests.cs @@ -80,6 +80,7 @@ public void DecodeWithLargeSpan() Span source = new char[numBytes]; Base64TestHelper.InitializeUrlDecodableChars(source, numBytes); + source[numBytes - 1] = 'A'; // make sure unused bits set 0 Span decodedBytes = new byte[Base64Url.GetMaxDecodedLength(source.Length)]; Assert.Equal(OperationStatus.Done, Base64Url.DecodeFromChars(source, decodedBytes, out int consumed, out int decodedByteCount)); @@ -262,45 +263,46 @@ public static void Roundtrip() } [Fact] - public static void PartialRoundtripWithoutPadding() + public static void RoundtripWithoutPadding() { - string input = "ab"; + string input = "ag"; Verify(input, result => { Assert.Equal(1, result.Length); string roundtrippedString = Base64Url.EncodeToString(result); - Assert.NotEqual(input, roundtrippedString); - Assert.Equal(input[0], roundtrippedString[0]); + Assert.Equal(input, roundtrippedString); }); } [Fact] - public static void PartialRoundtripWithPadding2() + public static void RoundtripWithPadding2() { - string input = "ab=="; + string input = "ag=="; Verify(input, result => { Assert.Equal(1, result.Length); string roundtrippedString = Base64Url.EncodeToString(result); - Assert.NotEqual(input, roundtrippedString); + Assert.NotEqual(input, roundtrippedString); // Padding character omitted Assert.Equal(input[0], roundtrippedString[0]); + Assert.Equal(input[1], roundtrippedString[1]); }); } [Fact] - public static void PartialRoundtripWithPadding1() + public static void RoundtripWithPadding1() { - string input = "789="; + string input = "788="; Verify(input, result => { Assert.Equal(2, result.Length); string roundtrippedString = Base64Url.EncodeToString(result); - Assert.NotEqual(input, roundtrippedString); + Assert.NotEqual(input, roundtrippedString); // Padding character omitted Assert.Equal(input[0], roundtrippedString[0]); Assert.Equal(input[1], roundtrippedString[1]); + Assert.Equal(input[2], roundtrippedString[2]); }); } diff --git a/src/libraries/System.Memory/tests/Base64Url/Base64UrlValidationUnitTests.cs b/src/libraries/System.Memory/tests/Base64Url/Base64UrlValidationUnitTests.cs index 410aec37d88306..dfb834abd63863 100644 --- a/src/libraries/System.Memory/tests/Base64Url/Base64UrlValidationUnitTests.cs +++ b/src/libraries/System.Memory/tests/Base64Url/Base64UrlValidationUnitTests.cs @@ -222,7 +222,7 @@ public void ValidateWithOnlyCharsToBeIgnoredChars(string utf8WithByteToBeIgnored [InlineData("YQ== ", 1)] [InlineData("YQ%%", 1)] [InlineData("YWI%", 2)] - [InlineData("YW% ", 1)] + [InlineData("YQ% ", 1)] public void ValidateWithPaddingReturnsCorrectCountBytes(string utf8WithByteToBeIgnored, int expectedLength) { byte[] utf8BytesWithByteToBeIgnored = UTF8Encoding.UTF8.GetBytes(utf8WithByteToBeIgnored); @@ -248,7 +248,7 @@ public void ValidateWithPaddingReturnsCorrectCountBytes(string utf8WithByteToBeI [InlineData("YQ== ", 1)] [InlineData("YQ%%", 1)] [InlineData("YWI%", 2)] - [InlineData("YW% ", 1)] + [InlineData("YQ% ", 1)] public void ValidateWithPaddingReturnsCorrectCountChars(string utf8WithByteToBeIgnored, int expectedLength) { ReadOnlySpan utf8BytesWithByteToBeIgnored = utf8WithByteToBeIgnored.ToArray(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs index c75252b90394ae..f9eabb672f2a81 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs @@ -200,7 +200,7 @@ internal static unsafe OperationStatus DecodeFrom(TBase64Deco i0 |= i2; - if (i0 < 0) + if (i0 < 0 || (i2 & 0xc0) != 0) // also check 2 unused lower bits are set to 0 { goto InvalidDataExit; } @@ -216,7 +216,7 @@ internal static unsafe OperationStatus DecodeFrom(TBase64Deco } else { - if (i0 < 0) + if (i0 < 0 || (i0 & 0xF000) != 0) // also check 4 unused lower bits are set to 0 { goto InvalidDataExit; } @@ -415,7 +415,7 @@ internal static unsafe OperationStatus DecodeFromUtf8InPlace(TBa i0 |= i2; - if (i0 < 0) + if (i0 < 0 || (i2 & 0xc0) != 0) // also check 2 unused lower bits are set to 0 { goto InvalidExit; } @@ -426,7 +426,7 @@ internal static unsafe OperationStatus DecodeFromUtf8InPlace(TBa } else { - if (i0 < 0) + if (i0 < 0 || (i0 & 0xF000) != 0) // also check 4 unused lower bits are set to 0 { goto InvalidExit; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs index 0e26aa4995d706..83fdf84277d571 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs @@ -9,23 +9,29 @@ internal static partial class Base64Helper { internal static bool IsValid(TBase64Validatable validatable, ReadOnlySpan base64Text, out int decodedLength) where TBase64Validatable : IBase64Validatable + where T : struct { int length = 0, paddingCount = 0; + T lastChar = default; if (!base64Text.IsEmpty) { #if NET - while (true) + while (!base64Text.IsEmpty) { - int index = validatable.IndexOfAnyExcept(base64Text); if ((uint)index >= (uint)base64Text.Length) { length += base64Text.Length; + lastChar = base64Text[base64Text.Length - 1]; break; } length += index; + if (index > 0) + { + lastChar = base64Text[index - 1]; + } T charToValidate = base64Text[index]; base64Text = base64Text.Slice(index + 1); @@ -65,6 +71,7 @@ internal static bool IsValid(TBase64Validatable validatab if (value >= 0) // valid char { length++; + lastChar = charToValidate; continue; } if (validatable.IsWhiteSpace(charToValidate)) @@ -101,6 +108,15 @@ internal static bool IsValid(TBase64Validatable validatab } } + int decoded = validatable.DecodeValue(lastChar); + if (paddingCount == 1 && (decoded & 0x03) != 0 || + paddingCount == 2 && (decoded & 0x0F) != 0) + { + // unused lower bits are not 0, reject input + decodedLength = 0; + return false; + } + length += paddingCount; break; } @@ -125,9 +141,8 @@ internal interface IBase64Validatable { #if NET int IndexOfAnyExcept(ReadOnlySpan span); -#else - int DecodeValue(T value); #endif + int DecodeValue(T value); bool IsWhiteSpace(T value); bool IsEncodingPad(T value); bool ValidateAndDecodeLength(int length, int paddingCount, out int decodedLength); @@ -137,9 +152,9 @@ internal interface IBase64Validatable { #if NET private static readonly SearchValues s_validBase64Chars = SearchValues.Create("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); - public int IndexOfAnyExcept(ReadOnlySpan span) => span.IndexOfAnyExcept(s_validBase64Chars); -#else +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] public int DecodeValue(char value) { if (value > byte.MaxValue) @@ -150,7 +165,6 @@ public int DecodeValue(char value) return default(Base64DecoderByte).DecodingMap[value]; } -#endif public bool IsWhiteSpace(char value) => Base64Helper.IsWhiteSpace(value); public bool IsEncodingPad(char value) => value == EncodingPad; public bool ValidateAndDecodeLength(int length, int paddingCount, out int decodedLength) => @@ -161,11 +175,9 @@ public bool ValidateAndDecodeLength(int length, int paddingCount, out int decode { #if NET private static readonly SearchValues s_validBase64Chars = SearchValues.Create(default(Base64EncoderByte).EncodingMap); - public int IndexOfAnyExcept(ReadOnlySpan span) => span.IndexOfAnyExcept(s_validBase64Chars); -#else - public int DecodeValue(byte value) => default(Base64DecoderByte).DecodingMap[value]; #endif + public int DecodeValue(byte value) => default(Base64DecoderByte).DecodingMap[value]; public bool IsWhiteSpace(byte value) => Base64Helper.IsWhiteSpace(value); public bool IsEncodingPad(byte value) => value == EncodingPad; [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Url/Base64UrlValidator.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Url/Base64UrlValidator.cs index ea785fc2ca7420..8b8f57955d77f7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Url/Base64UrlValidator.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Url/Base64UrlValidator.cs @@ -57,9 +57,9 @@ public static bool IsValid(ReadOnlySpan utf8Base64UrlText, out int decoded { #if NET private static readonly SearchValues s_validBase64UrlChars = SearchValues.Create("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"); - public int IndexOfAnyExcept(ReadOnlySpan span) => span.IndexOfAnyExcept(s_validBase64UrlChars); -#else +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] public int DecodeValue(char value) { if (value > byte.MaxValue) @@ -70,7 +70,6 @@ public int DecodeValue(char value) return default(Base64UrlDecoderByte).DecodingMap[value]; } -#endif public bool IsWhiteSpace(char value) => Base64Helper.IsWhiteSpace(value); public bool IsEncodingPad(char value) => value == Base64Helper.EncodingPad || value == UrlEncodingPad; [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -82,11 +81,9 @@ public bool ValidateAndDecodeLength(int length, int paddingCount, out int decode { #if NET private static readonly SearchValues s_validBase64UrlChars = SearchValues.Create(default(Base64UrlEncoderByte).EncodingMap); - public int IndexOfAnyExcept(ReadOnlySpan span) => span.IndexOfAnyExcept(s_validBase64UrlChars); -#else - public int DecodeValue(byte value) => default(Base64UrlDecoderByte).DecodingMap[value]; #endif + public int DecodeValue(byte value) => default(Base64UrlDecoderByte).DecodingMap[value]; public bool IsWhiteSpace(byte value) => Base64Helper.IsWhiteSpace(value); public bool IsEncodingPad(byte value) => value == Base64Helper.EncodingPad || value == UrlEncodingPad; [MethodImpl(MethodImplOptions.AggressiveInlining)] From 0e6fd01798ec0483d0280110f1400ebf7ab49ade Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Mon, 22 Jul 2024 15:54:31 -0700 Subject: [PATCH 2/5] Update invalid json test value, fix issue in Base64Url validation --- .../tests/Base64/Base64DecoderUnitTests.cs | 3 +- .../Base64Url/Base64UrlDecoderUnitTests.cs | 9 +++-- .../Base64Url/Base64UrlValidationUnitTests.cs | 10 +++-- .../Base64Helper/Base64ValidatorHelper.cs | 39 +++++++++++-------- .../Text/Base64Url/Base64UrlValidator.cs | 24 +++++++++--- .../JsonBase64TestData.cs | 8 ++-- 6 files changed, 58 insertions(+), 35 deletions(-) diff --git a/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs b/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs index b2775040df0819..2a24c23e82d7ea 100644 --- a/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs +++ b/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs @@ -285,7 +285,8 @@ public void BasicDecodingWithNonZeroUnusedBits(string inputString) Span decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)]; Assert.False(Base64.IsValid(inputString)); - Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int _)); + Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(source, decodedBytes, out int _, out int _)); + Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8InPlace(source, out int _)); } [Theory] diff --git a/src/libraries/System.Memory/tests/Base64Url/Base64UrlDecoderUnitTests.cs b/src/libraries/System.Memory/tests/Base64Url/Base64UrlDecoderUnitTests.cs index e06212e2571824..2e85953179a3c1 100644 --- a/src/libraries/System.Memory/tests/Base64Url/Base64UrlDecoderUnitTests.cs +++ b/src/libraries/System.Memory/tests/Base64Url/Base64UrlDecoderUnitTests.cs @@ -300,11 +300,12 @@ public void BasicDecodingWithFinalBlockTrueKnownInputDone(string inputString, in [InlineData("AQIDBAUHCAkKCwwNDxD")] public void BasicDecodingWithNonZeroUnusedBits(string inputString) { - Span source = Encoding.ASCII.GetBytes(inputString); - Span decodedBytes = new byte[Base64.GetMaxDecodedFromUtf8Length(source.Length)]; + byte[] source = Encoding.ASCII.GetBytes(inputString); + Span decodedBytes = new byte[Base64Url.GetMaxDecodedLength(source.Length)]; - Assert.False(Base64.IsValid(inputString)); - Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(source, decodedBytes, out int consumed, out int _)); + Assert.False(Base64Url.IsValid(inputString)); + Assert.Equal(OperationStatus.InvalidData, Base64Url.DecodeFromUtf8(source, decodedBytes, out int _, out int _)); + Assert.Throws(() => Base64Url.DecodeFromUtf8InPlace(source)); } [Theory] diff --git a/src/libraries/System.Memory/tests/Base64Url/Base64UrlValidationUnitTests.cs b/src/libraries/System.Memory/tests/Base64Url/Base64UrlValidationUnitTests.cs index dfb834abd63863..0ee3abcd9153a2 100644 --- a/src/libraries/System.Memory/tests/Base64Url/Base64UrlValidationUnitTests.cs +++ b/src/libraries/System.Memory/tests/Base64Url/Base64UrlValidationUnitTests.cs @@ -39,6 +39,7 @@ public void BasicValidationBytes() Span source = new byte[numBytes]; Base64TestHelper.InitializeUrlDecodableBytes(source, numBytes); + source[numBytes - 1] = 65; // make sure unused bits set 0 Assert.True(Base64Url.IsValid(source)); Assert.True(Base64Url.IsValid(source, out int decodedLength)); @@ -60,6 +61,7 @@ public void BasicValidationChars() Span source = new byte[numBytes]; Base64TestHelper.InitializeUrlDecodableBytes(source, numBytes); + source[numBytes - 1] = 65; // make sure unused bits set 0 Span chars = source .ToArray() .Select(Convert.ToChar) @@ -259,8 +261,8 @@ public void ValidateWithPaddingReturnsCorrectCountChars(string utf8WithByteToBeI } [Theory] - [InlineData("YWJ", true, 2)] - [InlineData("YW", true, 1)] + [InlineData("YWI", true, 2)] + [InlineData("YQ", true, 1)] [InlineData("Y", false, 0)] public void SmallSizeBytes(string utf8Text, bool isValid, int expectedDecodedLength) { @@ -272,8 +274,8 @@ public void SmallSizeBytes(string utf8Text, bool isValid, int expectedDecodedLen } [Theory] - [InlineData("YWJ", true, 2)] - [InlineData("YW", true, 1)] + [InlineData("YWI", true, 2)] + [InlineData("YQ", true, 1)] [InlineData("Y", false, 0)] public void SmallSizeChars(string utf8Text, bool isValid, int expectedDecodedLength) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs index 83fdf84277d571..d15d92d907b818 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs @@ -108,20 +108,11 @@ internal static bool IsValid(TBase64Validatable validatab } } - int decoded = validatable.DecodeValue(lastChar); - if (paddingCount == 1 && (decoded & 0x03) != 0 || - paddingCount == 2 && (decoded & 0x0F) != 0) - { - // unused lower bits are not 0, reject input - decodedLength = 0; - return false; - } - length += paddingCount; break; } - if (!validatable.ValidateAndDecodeLength(length, paddingCount, out decodedLength)) + if (!validatable.ValidateAndDecodeLength(lastChar, length, paddingCount, out decodedLength)) { goto Fail; } @@ -141,19 +132,21 @@ internal interface IBase64Validatable { #if NET int IndexOfAnyExcept(ReadOnlySpan span); -#endif +#else int DecodeValue(T value); +#endif bool IsWhiteSpace(T value); bool IsEncodingPad(T value); - bool ValidateAndDecodeLength(int length, int paddingCount, out int decodedLength); + bool ValidateAndDecodeLength(T lastChar, int length, int paddingCount, out int decodedLength); } internal readonly struct Base64CharValidatable : IBase64Validatable { #if NET private static readonly SearchValues s_validBase64Chars = SearchValues.Create("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); + public int IndexOfAnyExcept(ReadOnlySpan span) => span.IndexOfAnyExcept(s_validBase64Chars); -#endif +#else [MethodImpl(MethodImplOptions.AggressiveInlining)] public int DecodeValue(char value) { @@ -165,26 +158,38 @@ public int DecodeValue(char value) return default(Base64DecoderByte).DecodingMap[value]; } +#endif public bool IsWhiteSpace(char value) => Base64Helper.IsWhiteSpace(value); public bool IsEncodingPad(char value) => value == EncodingPad; - public bool ValidateAndDecodeLength(int length, int paddingCount, out int decodedLength) => - default(Base64ByteValidatable).ValidateAndDecodeLength(length, paddingCount, out decodedLength); + public bool ValidateAndDecodeLength(char lastChar, int length, int paddingCount, out int decodedLength) => + default(Base64ByteValidatable).ValidateAndDecodeLength((byte)lastChar, length, paddingCount, out decodedLength); } internal readonly struct Base64ByteValidatable : IBase64Validatable { #if NET private static readonly SearchValues s_validBase64Chars = SearchValues.Create(default(Base64EncoderByte).EncodingMap); + public int IndexOfAnyExcept(ReadOnlySpan span) => span.IndexOfAnyExcept(s_validBase64Chars); -#endif +#else public int DecodeValue(byte value) => default(Base64DecoderByte).DecodingMap[value]; +#endif public bool IsWhiteSpace(byte value) => Base64Helper.IsWhiteSpace(value); public bool IsEncodingPad(byte value) => value == EncodingPad; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ValidateAndDecodeLength(int length, int paddingCount, out int decodedLength) + public bool ValidateAndDecodeLength(byte lastChar, int length, int paddingCount, out int decodedLength) { if (length % 4 == 0) { + int decoded = default(Base64DecoderByte).DecodingMap[lastChar]; + if (paddingCount == 1 && (decoded & 0x03) != 0 || + paddingCount == 2 && (decoded & 0x0F) != 0) + { + // unused lower bits are not 0, reject input + decodedLength = 0; + return false; + } + // Remove padding to get exact length. decodedLength = (int)((uint)length / 4 * 3) - paddingCount; return true; diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Url/Base64UrlValidator.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Url/Base64UrlValidator.cs index 8b8f57955d77f7..fffa0d026c73ca 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Url/Base64UrlValidator.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Url/Base64UrlValidator.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.CompilerServices; +using static System.Buffers.Text.Base64Helper; namespace System.Buffers.Text { @@ -57,8 +58,9 @@ public static bool IsValid(ReadOnlySpan utf8Base64UrlText, out int decoded { #if NET private static readonly SearchValues s_validBase64UrlChars = SearchValues.Create("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"); + public int IndexOfAnyExcept(ReadOnlySpan span) => span.IndexOfAnyExcept(s_validBase64UrlChars); -#endif +#else [MethodImpl(MethodImplOptions.AggressiveInlining)] public int DecodeValue(char value) { @@ -70,24 +72,27 @@ public int DecodeValue(char value) return default(Base64UrlDecoderByte).DecodingMap[value]; } +#endif public bool IsWhiteSpace(char value) => Base64Helper.IsWhiteSpace(value); public bool IsEncodingPad(char value) => value == Base64Helper.EncodingPad || value == UrlEncodingPad; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ValidateAndDecodeLength(int length, int paddingCount, out int decodedLength) => - default(Base64UrlByteValidatable).ValidateAndDecodeLength(length, paddingCount, out decodedLength); + public bool ValidateAndDecodeLength(char lastChar, int length, int paddingCount, out int decodedLength) => + default(Base64UrlByteValidatable).ValidateAndDecodeLength((byte)lastChar, length, paddingCount, out decodedLength); } private readonly struct Base64UrlByteValidatable : Base64Helper.IBase64Validatable { #if NET private static readonly SearchValues s_validBase64UrlChars = SearchValues.Create(default(Base64UrlEncoderByte).EncodingMap); + public int IndexOfAnyExcept(ReadOnlySpan span) => span.IndexOfAnyExcept(s_validBase64UrlChars); -#endif +#else public int DecodeValue(byte value) => default(Base64UrlDecoderByte).DecodingMap[value]; +#endif public bool IsWhiteSpace(byte value) => Base64Helper.IsWhiteSpace(value); public bool IsEncodingPad(byte value) => value == Base64Helper.EncodingPad || value == UrlEncodingPad; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ValidateAndDecodeLength(int length, int paddingCount, out int decodedLength) + public bool ValidateAndDecodeLength(byte lastChar, int length, int paddingCount, out int decodedLength) { // Padding is optional for Base64Url, so need to account remainder. If remainder is 1, then it's invalid. #if NET @@ -109,6 +114,15 @@ public bool ValidateAndDecodeLength(int length, int paddingCount, out int decode decodedLength = (length >> 2) * 3 + (remainder > 0 ? remainder - 1 : 0) - paddingCount; #endif + int decoded = default(Base64DecoderByte).DecodingMap[lastChar]; + if ((remainder == 3 || paddingCount == 1) && (decoded & 0x03) != 0 || + (remainder == 2 || paddingCount == 2) && (decoded & 0x0F) != 0) + { + // unused lower bits are not 0, reject input + decodedLength = 0; + return false; + } + return true; } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonBase64TestData.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonBase64TestData.cs index 1a84468d9423c1..ceb51019bc0b77 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonBase64TestData.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonBase64TestData.cs @@ -9,7 +9,7 @@ internal class JsonBase64TestData { public static IEnumerable ValidBase64Tests() { - yield return new object[] { "\"ABC=\"" }; + yield return new object[] { "\"ABA=\"" }; yield return new object[] { "\"AB+D\"" }; yield return new object[] { "\"ABCD\"" }; yield return new object[] { "\"ABC/\"" }; @@ -19,9 +19,9 @@ public static IEnumerable ValidBase64Tests() public static IEnumerable InvalidBase64Tests() { - yield return new object[] { "\"ABC===\"" }; - yield return new object[] { "\"ABC\"" }; - yield return new object[] { "\"ABC!\"" }; + yield return new object[] { "\"ABA===\"" }; + yield return new object[] { "\"ABA\"" }; + yield return new object[] { "\"ABA!\"" }; yield return new object[] { GenerateRandomInvalidLargeString(includeEscapedCharacter: true) }; yield return new object[] { GenerateRandomInvalidLargeString(includeEscapedCharacter: false) }; } From f072f6e49c29ab19caa425e7e809e5bc64cb0bec Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Mon, 22 Jul 2024 21:18:54 -0700 Subject: [PATCH 3/5] Apply feedbacks --- .../tests/Base64Url/Base64UrlDecoderUnitTests.cs | 2 +- .../Buffers/Text/Base64Helper/Base64DecoderHelper.cs | 8 ++++---- .../Buffers/Text/Base64Helper/Base64ValidatorHelper.cs | 4 ++-- .../System/Buffers/Text/Base64Url/Base64UrlValidator.cs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Memory/tests/Base64Url/Base64UrlDecoderUnitTests.cs b/src/libraries/System.Memory/tests/Base64Url/Base64UrlDecoderUnitTests.cs index 2e85953179a3c1..c43af5769df061 100644 --- a/src/libraries/System.Memory/tests/Base64Url/Base64UrlDecoderUnitTests.cs +++ b/src/libraries/System.Memory/tests/Base64Url/Base64UrlDecoderUnitTests.cs @@ -303,7 +303,7 @@ public void BasicDecodingWithNonZeroUnusedBits(string inputString) byte[] source = Encoding.ASCII.GetBytes(inputString); Span decodedBytes = new byte[Base64Url.GetMaxDecodedLength(source.Length)]; - Assert.False(Base64Url.IsValid(inputString)); + Assert.False(Base64Url.IsValid(inputString.AsSpan())); Assert.Equal(OperationStatus.InvalidData, Base64Url.DecodeFromUtf8(source, decodedBytes, out int _, out int _)); Assert.Throws(() => Base64Url.DecodeFromUtf8InPlace(source)); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs index f9eabb672f2a81..f85de1d7c93674 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs @@ -200,7 +200,7 @@ internal static unsafe OperationStatus DecodeFrom(TBase64Deco i0 |= i2; - if (i0 < 0 || (i2 & 0xc0) != 0) // also check 2 unused lower bits are set to 0 + if ((i0 & 0x800000c0) != 0) // also check 2 unused lower bits are set to 0 { goto InvalidDataExit; } @@ -216,7 +216,7 @@ internal static unsafe OperationStatus DecodeFrom(TBase64Deco } else { - if (i0 < 0 || (i0 & 0xF000) != 0) // also check 4 unused lower bits are set to 0 + if ((i0 & 0x8000F000) != 0) // also check 4 unused lower bits are set to 0 { goto InvalidDataExit; } @@ -415,7 +415,7 @@ internal static unsafe OperationStatus DecodeFromUtf8InPlace(TBa i0 |= i2; - if (i0 < 0 || (i2 & 0xc0) != 0) // also check 2 unused lower bits are set to 0 + if ((i0 & 0x800000c0) != 0) // also check 2 unused lower bits are set to 0 { goto InvalidExit; } @@ -426,7 +426,7 @@ internal static unsafe OperationStatus DecodeFromUtf8InPlace(TBa } else { - if (i0 < 0 || (i0 & 0xF000) != 0) // also check 4 unused lower bits are set to 0 + if ((i0 & 0x8000F000) != 0) // if negative or 4 unused bits are not 0. { goto InvalidExit; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs index d15d92d907b818..d03394dfcc1299 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs @@ -182,8 +182,8 @@ public bool ValidateAndDecodeLength(byte lastChar, int length, int paddingCount, if (length % 4 == 0) { int decoded = default(Base64DecoderByte).DecodingMap[lastChar]; - if (paddingCount == 1 && (decoded & 0x03) != 0 || - paddingCount == 2 && (decoded & 0x0F) != 0) + if ((paddingCount == 1 && (decoded & 0x03) != 0) || + (paddingCount == 2 && (decoded & 0x0F) != 0)) { // unused lower bits are not 0, reject input decodedLength = 0; diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Url/Base64UrlValidator.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Url/Base64UrlValidator.cs index fffa0d026c73ca..bc95ab0f054cd2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Url/Base64UrlValidator.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Url/Base64UrlValidator.cs @@ -115,8 +115,8 @@ public bool ValidateAndDecodeLength(byte lastChar, int length, int paddingCount, decodedLength = (length >> 2) * 3 + (remainder > 0 ? remainder - 1 : 0) - paddingCount; #endif int decoded = default(Base64DecoderByte).DecodingMap[lastChar]; - if ((remainder == 3 || paddingCount == 1) && (decoded & 0x03) != 0 || - (remainder == 2 || paddingCount == 2) && (decoded & 0x0F) != 0) + if (((remainder == 3 || paddingCount == 1) && (decoded & 0x03) != 0) || + ((remainder == 2 || paddingCount == 2) && (decoded & 0x0F) != 0)) { // unused lower bits are not 0, reject input decodedLength = 0; From 8feb8f634e89918815b00f6ba13dc715f8537e42 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Mon, 22 Jul 2024 21:24:01 -0700 Subject: [PATCH 4/5] Update comments --- .../System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs index f85de1d7c93674..798aaa954d2fbb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs @@ -200,7 +200,7 @@ internal static unsafe OperationStatus DecodeFrom(TBase64Deco i0 |= i2; - if ((i0 & 0x800000c0) != 0) // also check 2 unused lower bits are set to 0 + if ((i0 & 0x800000c0) != 0) // if negative or 4 unused bits are not 0. { goto InvalidDataExit; } @@ -216,7 +216,7 @@ internal static unsafe OperationStatus DecodeFrom(TBase64Deco } else { - if ((i0 & 0x8000F000) != 0) // also check 4 unused lower bits are set to 0 + if ((i0 & 0x8000F000) != 0) // if negative or 4 unused bits are not 0. { goto InvalidDataExit; } @@ -415,7 +415,7 @@ internal static unsafe OperationStatus DecodeFromUtf8InPlace(TBa i0 |= i2; - if ((i0 & 0x800000c0) != 0) // also check 2 unused lower bits are set to 0 + if ((i0 & 0x800000c0) != 0) // if negative or 4 unused bits are not 0. { goto InvalidExit; } From ff21defdfc5faf4aeb066087bfd4d4ea4e2d4b8c Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Thu, 25 Jul 2024 21:53:20 -0700 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Stephen Toub --- .../System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs | 4 ++-- .../System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs index 798aaa954d2fbb..97765e340acff0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs @@ -200,7 +200,7 @@ internal static unsafe OperationStatus DecodeFrom(TBase64Deco i0 |= i2; - if ((i0 & 0x800000c0) != 0) // if negative or 4 unused bits are not 0. + if ((i0 & 0x800000c0) != 0) // if negative or 2 unused bits are not 0. { goto InvalidDataExit; } @@ -415,7 +415,7 @@ internal static unsafe OperationStatus DecodeFromUtf8InPlace(TBa i0 |= i2; - if ((i0 & 0x800000c0) != 0) // if negative or 4 unused bits are not 0. + if ((i0 & 0x800000c0) != 0) // if negative or 2 unused bits are not 0. { goto InvalidExit; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs index d03394dfcc1299..6ab37cfc49436f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64ValidatorHelper.cs @@ -28,7 +28,7 @@ internal static bool IsValid(TBase64Validatable validatab } length += index; - if (index > 0) + if (index != 0) { lastChar = base64Text[index - 1]; }