[NRBF] Address issues discovered by Threat Model #106629
[NRBF] Address issues discovered by Threat Model #106629adamsitnik merged 13 commits intodotnet:mainfrom
Conversation
…es, as it's most likely corrupted/tampered/malicious data and could be used as a vector of attack.
|
Note regarding the |
1 similar comment
|
Note regarding the |
There was a problem hiding this comment.
Based on what we have discussed and what @bartonjs wrote here: #103713 (comment)
I believe the type names and assembly names should not be provided in the exception messages.
| private static long GetJaggedArrayTotalElementsCount(BinaryArrayRecord jaggedArrayRecord) | ||
| { | ||
| long result = 0; | ||
| Queue<BinaryArrayRecord>? jaggedArrayRecords = null; |
There was a problem hiding this comment.
The decoder does not perform any recursion when decoding the jagged array. So does this method, but at a cost of potential Queue allocation.
…rray without being marked as jagged
src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/BinaryArrayRecord.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/BinaryArrayRecord.cs
Outdated
Show resolved
Hide resolved
- rename TotalElementsCount to FlattenedLength - Ensure lack of recursive call - add a comment explaining null nuances
- don't include member name in the exception message - remove unused resource - make the argument int rather than an object to make it clear it's never a string
| <data name="Serialization_UnexpectedNullRecordCount" xml:space="preserve"> | ||
| <value>Unexpected Null Record count.</value> | ||
| </data> | ||
| <data name="Serialization_MaxArrayLength" xml:space="preserve"> |
There was a problem hiding this comment.
this resource was not being used for a while
| // It is possible to have binary array records have an element type of array without being marked as jagged. | ||
| || TypeName.GetElementType().IsArray; |
There was a problem hiding this comment.
@JeremyKuhne this handles the scenario you have mentioned offline (there is also a test for that). Thank you again for pointing this out!
src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/BinaryArrayRecord.cs
Outdated
Show resolved
Hide resolved
| case SerializationRecordType.ObjectNullMultiple: | ||
| // All nulls need to be included, as it's another form of possible attack. | ||
| checked | ||
| { | ||
| result += ((NullsRecord)item).NullCount; | ||
| } | ||
| break; | ||
| default: | ||
| result++; | ||
| break; | ||
| } |
There was a problem hiding this comment.
Don't the arrays already know their lengths? I'd expect FlattenedLength to be a very simple
long length = here.Length;
foreach (element in here.Children)
{
if (element.IsArray)
{
length += element.FlattenedLength;
}
}
return length;(or the queue variant)
And that approach would already have counted the null elements. Ensuring that the number of records matches the length is a different problem (one I feel GetArray tries to solve?).
There was a problem hiding this comment.
@bartonjs You are right. This suggestion has greatly simplified the code and solved the other problem (arrays not being included themselves)
| case SerializationRecordType.ArraySingleString: | ||
| ArrayRecord nestedArrayRecord = (ArrayRecord)record; | ||
| if (nestedArrayRecord.IsJagged) | ||
| { | ||
| (jaggedArrayRecords ??= new()).Enqueue((BinaryArrayRecord)nestedArrayRecord); | ||
| } | ||
| else | ||
| { | ||
| Debug.Assert(nestedArrayRecord is not BinaryArrayRecord, "Ensure lack of recursive call"); | ||
| checked | ||
| { | ||
| // In theory somebody could create a payload that would represent | ||
| // a very nested array with total elements count > long.MaxValue. | ||
| result += nestedArrayRecord.FlattenedLength; | ||
| } | ||
| } | ||
| break; |
There was a problem hiding this comment.
This isn't counting the arrays themselves. Assuming that's intentional, you should have a comment at the top of the method (or before this switch) that explains what does, and what doesn't count.
object[][] objs = new object[1_000_000][];
objs.Fill(Array.Empty<object>());Doesn't feel like it should have a smaller count than
object[][] objs = new object[1_000_000][];
objs.Fill(null);And right now it feels like the first one says 0, and the second says a million.
There was a problem hiding this comment.
FWIW, I can understand not counting the arrays as answering how many int values might come out of an int[][][]; but then it gets weird when the nulls get counted, because they're not int values.
So I guess there's a bit of "what is this number supposed to mean?". The comment can explain that, and then the code can be judged against the comment.
There was a problem hiding this comment.
Good point, I've fixed that.
| } | ||
| else | ||
| { | ||
| Debug.Assert(nestedArrayRecord is not BinaryArrayRecord, "Ensure lack of recursive call"); |
There was a problem hiding this comment.
I can read the assert, but I don't understand it. Why can't a BinaryArrayRecord be nested within another BinaryArrayRecord? Why would that necessarily mean that a recursive call happened?
There was a problem hiding this comment.
I've eliminated the potential recursive call and added a comment.
# Conflicts: # src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/RectangularArrayRecord.cs
94f6c04 to
8a61178
Compare
|
@MihuBot fuzz NrbfDecoder |
# Conflicts: # src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/RectangularArrayRecord.cs
|
/ba-g DeadLetter is unrelated |
* introduce ArrayRecord.FlattenedLength * do not include invalid Type or Assembly names in the exception messages, as it's most likely corrupted/tampered/malicious data and could be used as a vector of attack. * It is possible to have binary array records have an element type of array without being marked as jagged
* introduce ArrayRecord.FlattenedLength * do not include invalid Type or Assembly names in the exception messages, as it's most likely corrupted/tampered/malicious data and could be used as a vector of attack. * It is possible to have binary array records have an element type of array without being marked as jagged
* [NRBF] Don't use Unsafe.As when decoding DateTime(s) (#105749) * Add NrbfDecoder Fuzzer (#107385) * [NRBF] Fix bugs discovered by the fuzzer (#107368) * bug #1: don't allow for values out of the SerializationRecordType enum range * bug #2: throw SerializationException rather than KeyNotFoundException when the referenced record is missing or it points to a record of different type * bug #3: throw SerializationException rather than FormatException when it's being thrown by BinaryReader (or sth else that we use) * bug #4: document the fact that IOException can be thrown * bug #5: throw SerializationException rather than OverflowException when parsing the decimal fails * bug #6: 0 and 17 are illegal values for PrimitiveType enum * bug #7: throw SerializationException when a surrogate character is read (so far an ArgumentException was thrown) # Conflicts: # src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/NrbfDecoder.cs * [NRBF] throw SerializationException when a surrogate character is read (#107532) (so far an ArgumentException was thrown) * [NRBF] Fuzzing non-seekable stream input (#107605) * [NRBF] More bug fixes (#107682) - Don't use `Debug.Fail` not followed by an exception (it may cause problems for apps deployed in Debug) - avoid Int32 overflow - throw for unexpected enum values just in case parsing has not rejected them - validate the number of chars read by BinaryReader.ReadChars - pass serialization record id to ex message - return false rather than throw EndOfStreamException when provided Stream has not enough data - don't restore the position in finally - limit max SZ and MD array length to Array.MaxLength, stop using LinkedList<T> as List<T> will be able to hold all elements now - remove internal enum values that were always illegal, but needed to be handled everywhere - Fix DebuggerDisplay * [NRBF] Comments and bug fixes from internal code review (#107735) * copy comments and asserts from Levis internal code review * apply Levis suggestion: don't store Array.MaxLength as a const, as it may change in the future * add missing and fix some of the existing comments * first bug fix: SerializationRecord.TypeNameMatches should throw ArgumentNullException for null Type argument * second bug fix: SerializationRecord.TypeNameMatches should know the difference between SZArray and single-dimension, non-zero offset arrays (example: int[] and int[*]) * third bug fix: don't cast bytes to booleans * fourth bug fix: don't cast bytes to DateTimes * add one test case that I've forgot in previous PR # Conflicts: # src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/SerializationRecord.cs * [NRBF] Address issues discovered by Threat Model (#106629) * introduce ArrayRecord.FlattenedLength * do not include invalid Type or Assembly names in the exception messages, as it's most likely corrupted/tampered/malicious data and could be used as a vector of attack. * It is possible to have binary array records have an element type of array without being marked as jagged --------- Co-authored-by: Buyaa Namnan <bunamnan@microsoft.com>
* introduce ArrayRecord.FlattenedLength * do not include invalid Type or Assembly names in the exception messages, as it's most likely corrupted/tampered/malicious data and could be used as a vector of attack. * It is possible to have binary array records have an element type of array without being marked as jagged
fixes #106644